001/*
002 *  Copyright 2021 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.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;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.commons.lang3.StringUtils;
031import org.slf4j.Logger;
032
033import org.ametys.cms.contenttype.ContentAttributeDefinition;
034import org.ametys.cms.contenttype.ContentType;
035import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
036import org.ametys.cms.data.type.ModelItemTypeConstants;
037import org.ametys.plugins.contentio.synchronize.impl.DefaultSynchronizingContentOperator;
038import org.ametys.runtime.model.ElementDefinition;
039import org.ametys.runtime.model.ModelItem;
040
041/**
042 * The abstract ODF synchronizing content operator. Each implementation can override this to add its helper role and specific mappings.
043 */
044public abstract class AbstractODFSynchronizingContentOperator extends DefaultSynchronizingContentOperator implements Serviceable
045{
046    /** The connector conversion helper */
047    protected ODFSynchronizingContentOperatorHelper _odfSCCOperatorHelper;
048    
049    /** The content type extension point */
050    protected ContentTypeExtensionPoint _contentTypeEP;
051
052    @Override
053    public void service(ServiceManager manager) throws ServiceException
054    {
055        _odfSCCOperatorHelper = (ODFSynchronizingContentOperatorHelper) manager.lookup(getHelperRole());
056        _contentTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
057    }
058
059    /**
060     * Get the SCC operator helper for the current implementation.
061     * @return a SCC operator helper role
062     */
063    protected abstract String getHelperRole();
064    
065    @Override
066    public Map<String, List<Object>> transform(ContentType contentType, Map<String, List<Object>> remoteValues, Logger logger)
067    {
068        Map<String, List<Object>> result = new HashMap<>();
069        
070        for (String attributePath : remoteValues.keySet())
071        {
072            List<Object> transformedValues = new ArrayList<>(remoteValues.get(attributePath));
073            
074            if (contentType.hasModelItem(attributePath))
075            {
076                ModelItem modelItem = contentType.getModelItem(attributePath);
077                transformedValues = _transformAttributeValues(modelItem, transformedValues, logger);
078            }
079            
080            result.put(attributePath, transformedValues);
081        }
082
083        return result;
084    }
085    
086    /**
087     * Transform the given attribute values
088     * @param definition The definition of the attribute
089     * @param values The values to transform
090     * @param logger The logger
091     * @return The transformed values
092     */
093    protected List<Object> _transformAttributeValues(ModelItem definition, List<Object> values, Logger logger)
094    {
095        if (definition instanceof ContentAttributeDefinition)
096        {
097            return _transformContentAttributeValues((ContentAttributeDefinition) definition, values, logger);
098        }
099        else if (ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID.equals(definition.getType().getId()))
100        {
101            return _transformRichTextAttributeValues((ElementDefinition) definition, values, logger);
102        }
103        
104        return values;
105    }
106    
107    /**
108     * Transform the given values to content IDs, using Apogée conversion if needed.
109     * @param definition The definition of the attribute
110     * @param values The values to transform
111     * @param logger The logger
112     * @return The corresponding content IDs of the values
113     */
114    protected List<Object> _transformContentAttributeValues(ContentAttributeDefinition definition, List<Object> values, Logger logger)
115    {
116        if (_contentTypeEP.getExtension(definition.getContentTypeId()).isReferenceTable())
117        {
118            return values.stream()
119                         .filter(String.class::isInstance)
120                         .map(String.class::cast)
121                         .map(v -> _getReferenceTableEntryId(definition, v, logger))
122                         .filter(Objects::nonNull)
123                         .collect(Collectors.toList());
124        }
125        
126        return values;
127    }
128    
129    /**
130     * Get the id of content associated with the given attribute value
131     * @param definition The definition of the attribute
132     * @param value The attribute value
133     * @param logger The logger
134     * @return The id of content or null if no match found
135     */
136    protected String _getReferenceTableEntryId(ContentAttributeDefinition definition, String value, Logger logger)
137    {
138        String referenceTableEntryId = _odfSCCOperatorHelper.getReferenceTableEntryId(definition.getContentTypeId(), value);
139        if (referenceTableEntryId == null)
140        {
141            logger.warn("The connector code '{}' doesn't have a corresponding Ametys value for attribute '{}'", value, definition.getPath());
142        }
143        
144        return referenceTableEntryId;
145    }
146    
147    /**
148     * Transform the given values to concatenate them and get the final rich text value
149     * @param definition The definition of the attribute
150     * @param values The values to transform
151     * @param logger The logger
152     * @return The concatenated values
153     */
154    protected List<Object> _transformRichTextAttributeValues(ElementDefinition definition, List<Object> values, Logger logger)
155    {
156        List<String> strValues = values.stream()
157                                       .filter(Objects::nonNull)
158                                       .map(Object::toString)
159                                       .filter(StringUtils::isNotBlank)
160                                       .collect(Collectors.toList());
161        
162        String concatenated = StringUtils.join(strValues, "\r\n");
163        
164        return List.of(concatenated);
165    }
166    
167    /**
168     * Get the first value as a string.
169     * @param values The list of values to iterate on
170     * @return The first value as a string or null
171     */
172    protected String _getFirstValueAsString(List<Object> values)
173    {
174        return Optional
175                // Manage null attributeValues
176                .ofNullable(values)
177                // Get an empty List
178                .orElseGet(Collections::emptyList)
179                // Stream it
180                .stream()
181                // Remove null values
182                .filter(Objects::nonNull)
183                // Object as string
184                .map(Object::toString)
185                // Remove empty values
186                .filter(StringUtils::isNotEmpty)
187                // Get the first element
188                .findFirst()
189                // Or null if there is not
190                .orElse(null);
191    }
192}