001/* 002 * Copyright 2024 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.odfsync.pegase.ws; 017 018import java.io.IOException; 019import java.util.List; 020import java.util.Map; 021import java.util.Optional; 022import java.util.UUID; 023import java.util.stream.Collectors; 024import java.util.stream.Stream; 025 026import org.apache.avalon.framework.activity.Initializable; 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.commons.lang3.StringUtils; 032 033import org.ametys.cms.data.ContentValue; 034import org.ametys.cms.repository.Content; 035import org.ametys.core.cache.AbstractCacheManager; 036import org.ametys.core.cache.Cache; 037import org.ametys.odf.enumeration.OdfReferenceTableEntry; 038import org.ametys.runtime.config.Config; 039import org.ametys.runtime.i18n.I18nizableText; 040 041import fr.pcscol.pegase.odf.ApiException; 042import fr.pcscol.pegase.odf.api.EspacesExterneApi; 043import fr.pcscol.pegase.odf.externe.model.Espace; 044import fr.pcscol.pegase.odf.externe.model.Pageable; 045import fr.pcscol.pegase.odf.externe.model.PagedEspaces; 046 047/** 048 * Helper for import and export from Ametys to Pégase. 049 */ 050public class PegaseHelper implements Component, Serviceable, Initializable 051{ 052 /** Role */ 053 public static final String ROLE = PegaseHelper.class.getName(); 054 private static final String __CACHE_ID = PegaseHelper.class.getName() + "$pegaseEspaces"; 055 /** The attribute name for the Pégase code in ref tables mapping */ 056 private static final String __PEGASE_CODE_FOR_MAPPING = "codePegase"; 057 058 /* Components */ 059 private PegaseApiManager _pegaseApiManager; 060 private AbstractCacheManager _cacheManager; 061 062 private UUID _espaceId; 063 064 @Override 065 public void service(ServiceManager manager) throws ServiceException 066 { 067 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 068 _pegaseApiManager = (PegaseApiManager) manager.lookup(PegaseApiManager.ROLE); 069 } 070 071 public void initialize() throws Exception 072 { 073 if (!_cacheManager.hasCache(__CACHE_ID)) 074 { 075 _cacheManager.createMemoryCache(__CACHE_ID, 076 new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_CACHE_PEGASE_ESPACES_LABEL"), 077 new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_CACHE_PEGASE_ESPACES_DESCRIPTION"), 078 true, 079 null); 080 } 081 } 082 083 /** 084 * Retrieves the Id of the Pégase working space with the configured code 085 * @param structureCode The structure code 086 * @return The espace Id 087 * @throws PegaseExportException 088 */ 089 public UUID getEspaceId(String structureCode) throws PegaseExportException 090 { 091 if (_espaceId == null) 092 { 093 String espaceCode = Config.getInstance().getValue("pegase.espace.code"); 094 095 try 096 { 097 _espaceId = getEspaceIdByCode(structureCode, espaceCode); 098 099 if (_espaceId == null) 100 { 101 throw new PegaseExportException("No Pégase working space was found with the given code: '" + espaceCode + "'."); 102 } 103 } 104 catch (ApiException | IOException e) 105 { 106 throw new PegaseExportException("An error occured while trying to retrieve the pegase working space.", e); 107 } 108 } 109 110 return _espaceId; 111 } 112 113 /** 114 * Initializes the cache with the espaces Ids and codes 115 * @param structureCode The pegase structure code 116 * @throws ApiException If an error occurs while retrieving the espaces from Pégase 117 * @throws IOException If an error occurs while retrieving the Pégase API 118 */ 119 private synchronized void _lazyInitialize(String structureCode) throws ApiException, IOException 120 { 121 EspacesExterneApi espacesExterneApi = _pegaseApiManager.getEspacesExterneApi(); 122 123 // Retrieve the first page of Pégase espaces 124 PagedEspaces pagedEspaces = _getAndCachePagedEspaces(0, structureCode, espacesExterneApi); 125 126 // Go through all Pégase espaces and add them to the cache 127 int nbOfPages = pagedEspaces.getTotalPages(); 128 for (int pageIndex = 1; pageIndex < nbOfPages; pageIndex++) 129 { 130 _getAndCachePagedEspaces(pageIndex, structureCode, espacesExterneApi); 131 } 132 } 133 134 /** 135 * Gets the espaces of the given page and put them in the cache 136 * @param page The page 137 * @param structureCode The code of the Pégase structure 138 * @param espacesExterneApi The espaces Pégase API 139 * @return The PagedEspaces found at the page 140 * @throws ApiException If an error occurs 141 */ 142 private PagedEspaces _getAndCachePagedEspaces(int page, String structureCode, EspacesExterneApi espacesExterneApi) throws ApiException 143 { 144 // Pageable to retrieve the maximum number of result for the given page 145 Pageable pageable = new Pageable(); 146 pageable.setPage(page); 147 pageable.setTaille(Integer.MAX_VALUE); 148 149 // Retrieve the pagedEspaces from Pégase 150 PagedEspaces pagedEspaces = espacesExterneApi.rechercherEspaces(structureCode, pageable, null, null, true); 151 152 // Put the espaces in the cache 153 List<Espace> espaces = pagedEspaces.getItems(); 154 155 Cache<String, UUID> cache = _cacheManager.<String, UUID>get(__CACHE_ID); 156 157 for (Espace espace : espaces) 158 { 159 cache.put(espace.getCode(), espace.getId()); 160 } 161 162 return pagedEspaces; 163 } 164 165 /** 166 * Get the espace Ids by Pégase code 167 * @return the map of espaces ids by Pégase code 168 */ 169 public Map<String, UUID> getEspaceIdsByCodes() 170 { 171 return _cacheManager.<String, UUID>get(__CACHE_ID).asMap(); 172 } 173 174 /** 175 * Get the Id of an espace by its Pégase code 176 * @param structureCode The code of the structure 177 * @param code The code of the espace 178 * @return The UUID of the espace 179 * @throws ApiException If an error occurs 180 * @throws IOException If an error occurs 181 */ 182 public UUID getEspaceIdByCode(String structureCode, String code) throws ApiException, IOException 183 { 184 // If the cache has not been initialized, initialize it 185 if (!_cacheManager.<String, UUID>get(__CACHE_ID).isInitialized()) 186 { 187 _lazyInitialize(structureCode); 188 } 189 190 return _cacheManager.<String, UUID>get(__CACHE_ID).get(code); 191 } 192 193 /** 194 * Get the Pégase code of a content, defaults to the Ametys code 195 * @param content The content 196 * @return The Pégase code 197 */ 198 public String _getPegaseCodeOrCode(Content content) 199 { 200 return content.hasValue(__PEGASE_CODE_FOR_MAPPING) 201 ? content.getValue(__PEGASE_CODE_FOR_MAPPING) 202 : content.getValue(OdfReferenceTableEntry.CODE); 203 } 204 205 /** 206 * Get the Pégase codes of the values of a content's field, defaults to the Ametys code 207 * @param content The content 208 * @param dataPath The data path for the field 209 * @return The Pégase codes for the values at given data path, joined by commas 210 */ 211 public String getPegaseCodeForField(Content content, String dataPath) 212 { 213 return _getPegaseCodes(content, dataPath).collect(Collectors.joining(",")); 214 } 215 216 /** 217 * Get the Pégase code of the first value of a content's field, defaults to the Ametys code 218 * @param content The content 219 * @param dataPath The data path for the field 220 * @return The Pégase code for the first value of the content's field 221 */ 222 public String getPegaseCodeForFirstValue(Content content, String dataPath) 223 { 224 return _getPegaseCodes(content, dataPath).findFirst().orElse(null); 225 } 226 227 private Stream<String> _getPegaseCodes(Content content, String dataPath) 228 { 229 if (content.hasValue(dataPath)) 230 { 231 ContentValue[] values = content.isMultiple(dataPath) 232 ? content.<ContentValue[]>getValue(dataPath) 233 : new ContentValue[] {content.<ContentValue>getValue(dataPath)}; 234 235 return Stream.of(values) 236 .map(ContentValue::getContentIfExists) 237 .filter(Optional::isPresent) 238 .map(Optional::get) 239 .map(this::_getPegaseCodeOrCode) 240 .filter(StringUtils::isNotEmpty); 241 } 242 243 return Stream.of(); 244 } 245}