001/*
002 *  Copyright 2017 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.contentio.synchronize;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024
025import org.apache.avalon.framework.configuration.Configurable;
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031
032import org.ametys.cms.contenttype.ContentType;
033import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
034import org.ametys.cms.repository.ContentDAO;
035import org.ametys.plugins.contentio.synchronize.search.SCCSearchModelConfiguration;
036import org.ametys.plugins.repository.AmetysObjectResolver;
037import org.ametys.plugins.repository.model.CompositeDefinition;
038import org.ametys.plugins.repository.model.RepeaterDefinition;
039import org.ametys.runtime.i18n.I18nizableText;
040import org.ametys.runtime.model.ElementDefinition;
041import org.ametys.runtime.model.ModelItem;
042import org.ametys.runtime.model.ModelItemAccessor;
043
044/**
045 * Configuration is separated from {@link AbstractSynchronizableContentsCollection}
046 */
047public abstract class AbstractStaticSynchronizableContentsCollection implements SynchronizableContentsCollection, Configurable, Serviceable
048{
049    /** The Ametys object resolver */
050    protected AmetysObjectResolver _resolver;
051    /** The SCC helper */
052    protected SynchronizableContentsCollectionHelper _sccHelper;
053    /** The content DAO */
054    protected ContentDAO _contentDAO;
055    /** The content type extension point */
056    protected ContentTypeExtensionPoint _contentTypeEP;
057    
058    /** The id */
059    protected String _id;
060    /** The label */
061    protected I18nizableText _label;
062    /** The path to the metadata holding the 'restricted' property */
063    protected String _restrictedField;
064    /** The handled content type */
065    protected String _contentType;
066    /** The handled languages */
067    protected List<String> _languages;
068    /** The id of controller */
069    protected String _modelId;
070    /** The untyped values of controller's parameters */
071    protected Map<String, Object> _modelParamValues;
072    /** True if removal sync */
073    protected boolean _removalSync;
074    /** True to ignore restrictions on attributes while synchronizing */
075    protected boolean _ignoreRestrictions;
076    /** The name of the workflow */
077    protected String _workflowName;
078    /** The id of the initial action of the workflow */
079    protected int _initialActionId;
080    /** The id of the synchronize action of the workflow */
081    protected int _synchronizeActionId;
082    /** The id of the validate action of the workflow */
083    protected int _validateActionId;
084    /** The prefix of the contents */
085    protected String _contentPrefix;
086    /** True to validate contents after import */
087    protected boolean _validateAfterImport;
088    /** The report mails */
089    protected String _reportMails;
090    /** The id of the content operator to use */
091    protected String _synchronizingContentOperator;
092    /** The id of the content operator to use */
093    protected boolean _synchronizeExistingContentsOnly;
094    /** <code>true</code> to check the collection while synchronizing */
095    protected boolean _checkCollection;
096    /** List of compatible SCC with the current SCC (excepting the current one) */
097    protected List<String> _compatibleSCC;
098    /** Search model configuration for search tool */
099    protected SCCSearchModelConfiguration _searchModelConfiguration;
100
101    public void service(ServiceManager manager) throws ServiceException
102    {
103        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
104        _sccHelper = (SynchronizableContentsCollectionHelper) manager.lookup(SynchronizableContentsCollectionHelper.ROLE);
105        _contentDAO = (ContentDAO) manager.lookup(ContentDAO.ROLE);
106        _contentTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
107    }
108    
109    public void configure(Configuration configuration) throws ConfigurationException
110    {
111        configureStaticParams(configuration);
112        configureDataSource(configuration);
113        _searchModelConfiguration = new SCCSearchModelConfiguration();
114        configureSearchModel();
115    }
116
117    /**
118     * Called in {@link #configure(Configuration)} for first configurations needed.
119     * @param configuration Configuration to read
120     * @throws ConfigurationException If an error occurs
121     */
122    protected void configureStaticParams(Configuration configuration) throws ConfigurationException
123    {
124        _id = configuration.getAttribute("id");
125        _label = I18nizableText.parseI18nizableText(configuration.getChild("label"), null);
126        _contentType = configuration.getChild("contentType").getValue();
127        _removalSync = configuration.getChild("removalSync").getValueAsBoolean(false);
128        _ignoreRestrictions = configuration.getChild("ignoreRestrictions").getValueAsBoolean(true);
129        _workflowName = configuration.getChild("workflowName").getValue();
130        _initialActionId = configuration.getChild("initialActionId").getValueAsInteger();
131        _synchronizeActionId = configuration.getChild("synchronizeActionId").getValueAsInteger();
132        _validateActionId = configuration.getChild("validateActionId").getValueAsInteger();
133        _contentPrefix = configuration.getChild("contentPrefix").getValue();
134        _restrictedField = configuration.getChild("restrictedField").getValue(null);
135        _validateAfterImport = configuration.getChild("validateAfterImport").getValueAsBoolean(false);
136        _reportMails = configuration.getChild("reportMails").getValue("");
137        _synchronizingContentOperator = configuration.getChild("contentOperator").getValue();
138        _modelId = configuration.getChild("model").getAttribute("id");
139        _languages = _parseMultipleValuesConf(configuration.getChild("languages"));
140        _modelParamValues = _parseParameters(configuration.getChild("model"));
141        _synchronizeExistingContentsOnly = configuration.getChild("synchronizeExistingContentsOnly").getValueAsBoolean(false);
142        _checkCollection = configuration.getChild("checkCollection").getValueAsBoolean(true);
143        _compatibleSCC = _parseMultipleValuesConf(configuration.getChild("compatibleSCC"));
144    }
145
146    /**
147     * Configure the data source parameters.
148     * @param configuration Configuration to read
149     * @throws ConfigurationException If an error occurs
150     */
151    protected abstract void configureDataSource(Configuration configuration) throws ConfigurationException;
152    
153    /**
154     * Configure the search model used by SCCSearchTool.
155     */
156    protected abstract void configureSearchModel();
157    
158    public String getId()
159    {
160        return _id;
161    }
162    
163    public I18nizableText getLabel()
164    {
165        return _label;
166    }
167    
168    public String getContentType()
169    {
170        return _contentType;
171    }
172    
173    public List<String> getLanguages()
174    {
175        return _languages;
176    }
177    
178    public String getRestrictedField()
179    {
180        return _restrictedField;
181    }
182    
183    public String getSynchronizeCollectionModelId()
184    {
185        return _modelId;
186    }
187    
188    public Map<String, Object> getParameterValues()
189    {
190        return _modelParamValues;
191    }
192    
193    public boolean removalSync()
194    {
195        return _removalSync;
196    }
197    
198    public boolean ignoreRestrictions()
199    {
200        return _ignoreRestrictions;
201    }
202    
203    public boolean checkCollection()
204    {
205        return _checkCollection;
206    }
207    
208    public List<String> getCompatibleSCC(boolean includeCurrent)
209    {
210        if (includeCurrent)
211        {
212            List<String> compatibleSCC = new ArrayList<>(_compatibleSCC);
213            compatibleSCC.add(getId());
214            return compatibleSCC;
215        }
216        return _compatibleSCC;
217    }
218    
219    public String getWorkflowName()
220    {
221        return _workflowName;
222    }
223    
224    public int getInitialActionId()
225    {
226        return _initialActionId;
227    }
228    
229    public int getSynchronizeActionId()
230    {
231        return _synchronizeActionId;
232    }
233    
234    public int getValidateActionId()
235    {
236        return _validateActionId;
237    }
238    
239    public String getContentPrefix()
240    {
241        return _contentPrefix;
242    }
243    
244    public boolean validateAfterImport()
245    {
246        return _validateAfterImport;
247    }
248    
249    public String getReportMails()
250    {
251        return _reportMails;
252    }
253    
254    public String getSynchronizingContentOperator()
255    {
256        return _synchronizingContentOperator;
257    }
258    
259    public boolean synchronizeExistingContentsOnly()
260    {
261        return _synchronizeExistingContentsOnly;
262    }
263    
264    public SCCSearchModelConfiguration getSearchModelConfiguration()
265    {
266        return _searchModelConfiguration;
267    }
268
269    /**
270     * Parse parameters' values
271     * @param configuration The root configuration
272     * @return The parameters
273     * @throws ConfigurationException if an error occurred
274     */
275    protected Map<String, Object> _parseParameters(Configuration configuration) throws ConfigurationException
276    {
277        Map<String, Object> values = new LinkedHashMap<>();
278        
279        Configuration[] params = configuration.getChildren("param");
280        for (Configuration paramConfig : params)
281        {
282            values.put(paramConfig.getAttribute("name"), paramConfig.getValue(""));
283        }
284        return values;
285    }
286    
287    /**
288     * Parse multiple values configuration
289     * @param configuration the configuration
290     * @return the list of handled values
291     * @throws ConfigurationException if an error occurred
292     */
293    protected List<String> _parseMultipleValuesConf(Configuration configuration) throws ConfigurationException
294    {
295        List<String> values = new ArrayList<>();
296        for (Configuration conf : configuration.getChildren("value"))
297        {
298            values.add(conf.getValue());
299        }
300        return values;
301    }
302    
303    /**
304     * Transform the remote values to take each attribute cardinality into account
305     * @param remoteValues the remote values
306     * @param contentTypeId the content type ID from which attributes come from
307     * @return the transformed values
308     */
309    protected Map<String, Object> _transformRemoteValuesCardinality(Map<String, List<Object>> remoteValues, String contentTypeId)
310    {
311        ContentType contentType = _contentTypeEP.getExtension(contentTypeId);
312        return _transformRemoteValuesCardinality(remoteValues, contentType);
313    }
314    
315    @SuppressWarnings("unchecked")
316    private Map<String, Object> _transformRemoteValuesCardinality(Map<String, List<Object>> remoteValues, ModelItemAccessor modelItemAccessor)
317    {
318        Map<String, Object> transformedContentValues = new HashMap<>();
319        for (String attributeName : remoteValues.keySet())
320        {
321            if (modelItemAccessor.hasModelItem(attributeName))
322            {
323                List<Object> attributeValues = remoteValues.get(attributeName);
324                Object transformedAttributeValue = attributeValues;
325                
326                ModelItem modelItem = modelItemAccessor.getModelItem(attributeName);
327                if (modelItem instanceof ElementDefinition definition && !definition.isMultiple())
328                {
329                    transformedAttributeValue = attributeValues.stream()
330                                                               .filter(Objects::nonNull)
331                                                               .findFirst()
332                                                               .orElse(null);
333                }
334                else if (modelItem instanceof CompositeDefinition composite)
335                {
336                    transformedAttributeValue = attributeValues.stream()
337                                                               .filter(Objects::nonNull)
338                                                               .findFirst()
339                                                               .map(object -> (Map<String, List<Object>>) object)
340                                                               .map(values -> _transformRemoteValuesCardinality(values, composite))
341                                                               .orElse(null);
342                }
343                else if (modelItem instanceof RepeaterDefinition repeater)
344                {
345                    transformedAttributeValue = attributeValues.stream()
346                                                               .filter(Objects::nonNull)
347                                                               .map(object -> (Map<String, List<Object>>) object)
348                                                               .map(values -> _transformRemoteValuesCardinality(values, repeater))
349                                                               .toList();
350                }
351                
352                transformedContentValues.put(attributeName, transformedAttributeValue);
353            }
354        }
355        
356        return transformedContentValues;
357    }
358}