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