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