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}