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.odfsync.apogee.scc.operator;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024import java.util.Optional;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.avalon.framework.service.Serviceable;
029import org.apache.commons.lang3.StringUtils;
030import org.slf4j.Logger;
031
032import org.ametys.cms.contenttype.ContentAttributeDefinition;
033import org.ametys.cms.contenttype.ContentType;
034import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
035import org.ametys.cms.contenttype.RichTextAttributeDefinition;
036import org.ametys.plugins.contentio.synchronize.impl.DefaultSynchronizingContentOperator;
037import org.ametys.runtime.model.ElementDefinition;
038import org.ametys.runtime.model.ModelItem;
039import org.ametys.runtime.model.type.ModelItemTypeConstants;
040
041/**
042 * Get mapped values from Apogée to Ametys.
043 */
044public class ApogeeSynchronizingContentOperator extends DefaultSynchronizingContentOperator implements Serviceable
045{
046    /** The Apogee conversion helper */
047    protected ApogeeSynchronizingContentOperatorHelper _apogeeSCCOperatorHelper;
048    /** The content type helper */
049    protected ContentTypeExtensionPoint _cTypeEP;
050
051    @Override
052    public void service(ServiceManager manager) throws ServiceException
053    {
054        _apogeeSCCOperatorHelper = (ApogeeSynchronizingContentOperatorHelper) manager.lookup(ApogeeSynchronizingContentOperatorHelper.ROLE);
055        _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
056    }
057    
058    @Override
059    public Map<String, List<Object>> transform(ContentType cType, Map<String, List<Object>> remoteValues, Logger logger)
060    {
061        Map<String, List<Object>> result = new HashMap<>();
062        
063        for (String attributePath : remoteValues.keySet())
064        {
065            List<Object> transformedValues = remoteValues.get(attributePath);
066            if (cType.hasModelItem(attributePath))
067            {
068                ModelItem modelItem = cType.getModelItem(attributePath);
069                if (modelItem instanceof ElementDefinition)
070                {
071                    transformedValues = _transformAttribute((ElementDefinition) modelItem, transformedValues, logger);
072                }
073            }
074            result.put(attributePath, transformedValues);
075        }
076
077        return result;
078    }
079    
080    /**
081     * Transform a attribute.
082     * @param definition The definition of the attribute
083     * @param oldValues The values
084     * @param logger The logger
085     * @return The transformed values
086     */
087    protected List<Object> _transformAttribute(ElementDefinition definition, List<Object> oldValues, Logger logger)
088    {
089        List<Object> newValues = new ArrayList<>();
090        for (Object oldValue : _getValueOrValues(oldValues, definition))
091        {
092            Object newValue = _transformAttributeValue(definition, oldValue, logger);
093            
094            if (newValue != null)
095            {
096                newValues.add(newValue);
097            }
098        }
099        
100        return newValues;
101    }
102    
103    private List<Object> _getValueOrValues(List<Object> oldValues, ElementDefinition definition)
104    {
105        // Rich texts are concatenated during synchronization, the implementation of concatenation can depends of the rich text type (HTML, docbook, plain text, etc.)
106        if (!definition.isMultiple() && !(definition instanceof RichTextAttributeDefinition))
107        {
108            Object firstValue = _getFirstValue(oldValues);
109            if (firstValue == null)
110            {
111                return List.of();
112            }
113            return List.of(firstValue);
114        }
115        return oldValues;
116    }
117    
118    /**
119     * Transform Apogée value to the required type by Ametys.
120     * @param definition The element definition of the attribute
121     * @param value The Apogée value
122     * @param logger The logger
123     * @return a transformed value for Ametys
124     */
125    protected Object _transformAttributeValue(ElementDefinition definition, Object value, Logger logger)
126    {
127        if (value != null)
128        {
129            if (definition instanceof ContentAttributeDefinition)
130            {
131                return _transformToContentId(value, (ContentAttributeDefinition) definition, logger);
132            }
133            else if (definition.getType().getId().equals(ModelItemTypeConstants.DOUBLE_TYPE_ID))
134            {
135                return _transformToDouble(value, definition, logger);
136            }
137            else if (definition.getType().getId().equals(ModelItemTypeConstants.BOOLEAN_TYPE_ID))
138            {
139                return _transformToBoolean(value, definition, logger);
140            }
141        }
142        return value;
143    }
144    
145    /**
146     * Get the first value of the attributeValues list.
147     * @param attributeValues The list of possible values
148     * @return The first value as string if not empty, or <code>null</code>
149     */
150    protected String _getFirstValue(List<Object> attributeValues)
151    { 
152        return Optional
153                // Manage null attributeValues
154                .ofNullable(attributeValues)
155                // Get an empty List
156                .orElseGet(Collections::emptyList)
157                // Stream it
158                .stream()
159                // Remove null values
160                .filter(Objects::nonNull)
161                // Object as string
162                .map(Object::toString)
163                // Remove empty values
164                .filter(StringUtils::isNotEmpty)
165                // Get the first element
166                .findFirst()
167                // Or null if there is not
168                .orElse(null);
169    }
170
171    /**
172     * Transform a {@link Object} value to {@link Boolean} value. In Apogée O ("oui") means <code>true</code> and N ("non") means <code>false</code> for boolean values.
173     * @param value The value to transform
174     * @param definition The definition of the attribute
175     * @param logger The logger
176     * @return The value as {@link Boolean}, <code>false</code> if there is no value
177     */
178    protected Boolean _transformToBoolean(Object value, ElementDefinition definition, Logger logger)
179    {
180        return String.valueOf(value).equals("O");
181    }
182    
183    /**
184     * Transform a {@link Object} value to {@link Double} value and manage the errors.
185     * @param value The value to transform
186     * @param definition The definition of the attribute
187     * @param logger The logger
188     * @return The value as {@link Double} or <code>null</code> if it fails
189     */
190    protected Double _transformToDouble(Object value, ElementDefinition definition, Logger logger)
191    {
192        try
193        {
194            return Double.valueOf(String.valueOf(value));
195        }
196        catch (NumberFormatException e)
197        {
198            logger.warn("The value '{}' for attribute '{}' cannot be transformed to double value.", value, definition.getPath());
199        }
200        
201        return null;
202    }
203    
204    /**
205     * Transform a {@link Object} value to a content ID, use Apogée conversion if needed.
206     * @param value The value to transform
207     * @param definition The definition of the attribute
208     * @param logger The logger
209     * @return The corresponding contentId of the value
210     */
211    protected String _transformToContentId(Object value, ContentAttributeDefinition definition, Logger logger)
212    {
213        if (_cTypeEP.getExtension(definition.getContentTypeId()).isReferenceTable())
214        {
215            String referenceTableEntryId = _apogeeSCCOperatorHelper.getReferenceTableEntryId(definition.getContentTypeId(), String.valueOf(value));
216            if (referenceTableEntryId == null)
217            {
218                logger.warn("The Apogée code '{}' doesn't have a corresponding Ametys value for attribute '{}'", value, definition.getPath());
219            }
220            
221            return referenceTableEntryId;
222        }
223        
224        return String.valueOf(value);
225    }
226}
227