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 synchronization */
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    /** Search model configuration for search tool */
092    protected SCCSearchModelConfiguration _searchModelConfiguration;
093
094    public void service(ServiceManager manager) throws ServiceException
095    {
096        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
097        _sccHelper = (SynchronizableContentsCollectionHelper) manager.lookup(SynchronizableContentsCollectionHelper.ROLE);
098        _contentDAO = (ContentDAO) manager.lookup(ContentDAO.ROLE);
099        _contentTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
100    }
101    
102    @Override
103    public void configure(Configuration configuration) throws ConfigurationException
104    {
105        configureStaticParams(configuration);
106        configureDataSource(configuration);
107        _searchModelConfiguration = new SCCSearchModelConfiguration();
108        configureSearchModel();
109    }
110
111    /**
112     * Called in {@link #configure(Configuration)} for first configurations needed.
113     * @param configuration Configuration to read
114     * @throws ConfigurationException If an error occurs
115     */
116    protected void configureStaticParams(Configuration configuration) throws ConfigurationException
117    {
118        _id = configuration.getAttribute("id");
119        _label = I18nizableText.parseI18nizableText(configuration.getChild("label"), null);
120        _contentType = configuration.getChild("contentType").getValue();
121        _removalSync = configuration.getChild("removalSync").getValueAsBoolean(false);
122        _ignoreRestrictions = configuration.getChild("ignoreRestrictions").getValueAsBoolean(true);
123        _workflowName = configuration.getChild("workflowName").getValue();
124        _initialActionId = configuration.getChild("initialActionId").getValueAsInteger();
125        _synchronizeActionId = configuration.getChild("synchronizeActionId").getValueAsInteger();
126        _validateActionId = configuration.getChild("validateActionId").getValueAsInteger();
127        _contentPrefix = configuration.getChild("contentPrefix").getValue();
128        _restrictedField = configuration.getChild("restrictedField").getValue(null);
129        _validateAfterImport = configuration.getChild("validateAfterImport").getValueAsBoolean(false);
130        _reportMails = configuration.getChild("reportMails").getValue("");
131        _synchronizingContentOperator = configuration.getChild("contentOperator").getValue();
132        _modelId = configuration.getChild("model").getAttribute("id");
133        _languages = _parseLanguages(configuration.getChild("languages"));
134        _modelParamValues = _parseParameters(configuration.getChild("model"));
135        _synchronizeExistingContentsOnly = configuration.getChild("synchronizeExistingContentsOnly").getValueAsBoolean(false);
136    }
137
138    /**
139     * Configure the data source parameters.
140     * @param configuration Configuration to read
141     * @throws ConfigurationException If an error occurs
142     */
143    protected abstract void configureDataSource(Configuration configuration) throws ConfigurationException;
144    
145    /**
146     * Configure the search model used by SCCSearchTool.
147     */
148    protected abstract void configureSearchModel();
149    
150    @Override
151    public String getId()
152    {
153        return _id;
154    }
155    
156    @Override
157    public I18nizableText getLabel()
158    {
159        return _label;
160    }
161    
162    @Override
163    public String getContentType()
164    {
165        return _contentType;
166    }
167    
168    @Override
169    public List<String> getLanguages()
170    {
171        return _languages;
172    }
173
174    @Override
175    public String getRestrictedField()
176    {
177        return _restrictedField;
178    }
179    
180    @Override
181    public String getSynchronizeCollectionModelId()
182    {
183        return _modelId;
184    }
185
186    @Override
187    public Map<String, Object> getParameterValues()
188    {
189        return _modelParamValues;
190    }
191    
192    @Override
193    public boolean removalSync()
194    {
195        return _removalSync;
196    }
197    
198    @Override
199    public boolean ignoreRestrictions()
200    {
201        return _ignoreRestrictions;
202    }
203    
204    @Override
205    public String getWorkflowName()
206    {
207        return _workflowName;
208    }
209    
210    @Override
211    public int getInitialActionId()
212    {
213        return _initialActionId;
214    }
215    
216    @Override
217    public int getSynchronizeActionId()
218    {
219        return _synchronizeActionId;
220    }
221    
222    @Override
223    public int getValidateActionId()
224    {
225        return _validateActionId;
226    }
227    
228    @Override
229    public String getContentPrefix()
230    {
231        return _contentPrefix;
232    }
233    
234    @Override
235    public boolean validateAfterImport()
236    {
237        return _validateAfterImport;
238    }
239    
240    @Override
241    public String getReportMails()
242    {
243        return _reportMails;
244    }
245    
246    @Override
247    public String getSynchronizingContentOperator()
248    {
249        return _synchronizingContentOperator;
250    }
251    
252    @Override
253    public boolean synchronizeExistingContentsOnly()
254    {
255        return _synchronizeExistingContentsOnly;
256    }
257    
258    @Override
259    public SCCSearchModelConfiguration getSearchModelConfiguration()
260    {
261        return _searchModelConfiguration;
262    }
263
264    /**
265     * Parse parameters' values
266     * @param configuration The root configuration
267     * @return The parameters
268     * @throws ConfigurationException if an error occurred
269     */
270    protected Map<String, Object> _parseParameters(Configuration configuration) throws ConfigurationException
271    {
272        Map<String, Object> values = new LinkedHashMap<>();
273        
274        Configuration[] params = configuration.getChildren("param");
275        for (Configuration paramConfig : params)
276        {
277            values.put(paramConfig.getAttribute("name"), paramConfig.getValue(""));
278        }
279        return values;
280    }
281    
282    /**
283     * Parse languages configuration
284     * @param configuration the configuration
285     * @return the list of handled languages
286     * @throws ConfigurationException if an error occurred
287     */
288    protected List<String> _parseLanguages(Configuration configuration) throws ConfigurationException
289    {
290        List<String> languages = new ArrayList<>();
291        for (Configuration conf : configuration.getChildren("value"))
292        {
293            languages.add(conf.getValue());
294        }
295        
296        return languages;
297    }
298    
299    /**
300     * Transform the remote values to take each attribute cardinality into account 
301     * @param remoteValues the remote values
302     * @param contentTypeId the content type ID from which attributes come from
303     * @return the transformed values
304     */
305    protected Map<String, Object> _transformRemoteValuesCardinality(Map<String, List<Object>> remoteValues, String contentTypeId)
306    {
307        ContentType contentType = _contentTypeEP.getExtension(contentTypeId);
308        
309        Map<String, Object> transformedContentValues = new HashMap<>();
310        for (String attributeName : remoteValues.keySet())
311        {
312            if (contentType.hasModelItem(attributeName))
313            {
314                List<Object> attributeValues = remoteValues.get(attributeName);
315                Object transformedAttributeValue = attributeValues;
316                
317                ModelItem modelItem = contentType.getModelItem(attributeName);
318                if (modelItem instanceof ElementDefinition)
319                {
320                    ElementDefinition definition = (ElementDefinition) modelItem;
321                    if (!definition.isMultiple())
322                    {
323                        transformedAttributeValue = attributeValues.stream()
324                                                                   .filter(Objects::nonNull)
325                                                                   .findFirst()
326                                                                   .orElse(null);
327                    }
328                }
329                
330                transformedContentValues.put(attributeName, transformedAttributeValue);
331            }
332        }
333        
334        return transformedContentValues;
335    }
336}