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.pegase.scc;
017
018import java.util.ArrayList;
019import java.util.List;
020import java.util.Map;
021import java.util.Objects;
022import java.util.Optional;
023import java.util.stream.Collectors;
024import java.util.stream.Stream;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.commons.lang3.StringUtils;
029import org.slf4j.Logger;
030
031import org.ametys.cms.contenttype.ContentAttributeDefinition;
032import org.ametys.cms.contenttype.ContentType;
033import org.ametys.cms.repository.Content;
034import org.ametys.cms.repository.ContentQueryHelper;
035import org.ametys.cms.repository.ContentTypeExpression;
036import org.ametys.odf.courselist.CourseList.ChoiceType;
037import org.ametys.odf.orgunit.OrgUnitFactory;
038import org.ametys.odf.program.ProgramFactory;
039import org.ametys.odf.program.SubProgramFactory;
040import org.ametys.plugins.odfsync.scc.operator.AbstractODFSynchronizingContentOperator;
041import org.ametys.plugins.odfsync.utils.ContentWorkflowDescription;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.plugins.repository.query.expression.AndExpression;
044import org.ametys.plugins.repository.query.expression.Expression.Operator;
045import org.ametys.plugins.repository.query.expression.StringExpression;
046
047/**
048 * Pegase synchronizing content operator extending {@link AbstractODFSynchronizingContentOperator} because we keep some mapping mechanisms.
049 */
050public class PegaseSynchronizingContentOperator extends AbstractODFSynchronizingContentOperator
051{
052    private AmetysObjectResolver _resolver;
053
054    @Override
055    public void service(ServiceManager manager) throws ServiceException
056    {
057        super.service(manager);
058        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
059    }
060    
061    @Override
062    protected String getHelperRole()
063    {
064        return "org.ametys.plugins.odfsync.pegase.scc.operator.PegaseSynchronizingContentOperatorHelper";
065    }
066    
067    @Override
068    public Map<String, List<Object>> transform(ContentType obsoleteCType, Map<String, List<Object>> remoteValues, Logger logger)
069    {
070        // Get the right content type from the category field
071        ContentType contentType = Optional.of(remoteValues)
072            .map(v -> v.get("workflowDescription"))
073            .map(l -> l.get(0))
074            .map(ContentWorkflowDescription.class::cast)
075            .map(ContentWorkflowDescription::getContentType)
076            .map(_contentTypeEP::getExtension)
077            .orElse(null);
078        
079        if (contentType == null)
080        {
081            return remoteValues;
082        }
083        
084        transformSpecificValues(contentType.getId(), remoteValues);
085        
086        return super.transform(contentType, remoteValues, logger);
087    }
088
089    /**
090     * Transform values specific to the content type
091     * @param contentTypeId The content type Id
092     * @param remoteValues The remote values
093     */
094    protected void transformSpecificValues(String contentTypeId, Map<String, List<Object>> remoteValues)
095    {
096        transformStringValues(remoteValues);
097        transformSpecificValuesByContentType(contentTypeId, remoteValues);
098        
099        // Always transform courseList values in case we have courses not under course list
100        transformSpecificCourseListValues(remoteValues);
101    }
102    
103    /**
104     * Transform the string values to remove empty values
105     * @param remoteValues The remote values to transform
106     */
107    protected void transformStringValues(Map<String, List<Object>> remoteValues)
108    {
109        // Trim to null every string values
110        for (List<Object> values : remoteValues.values())
111        {
112            if (values != null)
113            {
114                values.stream()
115                    .map(value -> value instanceof String stringValue
116                                ? StringUtils.trimToNull(stringValue)
117                                : value);
118            }
119        }
120    }
121    
122    /**
123     * Transform values specific to the content type
124     * @param contentTypeId The content type Id
125     * @param remoteValues The remote values
126     */
127    protected void transformSpecificValuesByContentType(String contentTypeId, Map<String, List<Object>> remoteValues)
128    {
129        switch (contentTypeId)
130        {
131            case ProgramFactory.PROGRAM_CONTENT_TYPE:
132            case SubProgramFactory.SUBPROGRAM_CONTENT_TYPE:
133                transformSpecificAbstractProgramValues(remoteValues);
134                break;
135            default:
136                // Nothing to do
137        }
138    }
139    
140    /**
141     * Transform values specific to abstract programs
142     * @param remoteValues The remote values
143     */
144    protected void transformSpecificAbstractProgramValues(Map<String, List<Object>> remoteValues)
145    {
146        // Transform ects for AbstractProgram (ECTS value is a double, transform it to String with a Long appearance, 5.0 -> "5")
147        List<Object> ects = Optional.ofNullable(remoteValues.get("ects"))
148                .map(List::stream)
149                .orElseGet(Stream::empty)
150                .filter(Objects::nonNull)
151                .map(Object::toString)
152                .filter(StringUtils::isNotEmpty)
153                .map(Double::valueOf)
154                .map(Double::longValue)
155                .map(String::valueOf)
156                .collect(Collectors.toList());
157        
158        if (ects.isEmpty())
159        {
160            remoteValues.remove("ects");
161        }
162        else
163        {
164            remoteValues.put("ects", ects);
165        }
166    }
167    
168    /**
169     * Transform values specific to course lists
170     * @param remoteValues The remote values
171     */
172    protected void transformSpecificCourseListValues(Map<String, List<Object>> remoteValues)
173    {
174        // Set the choiceType for CourseList
175        String choiceType = remoteValues.get("min") != null && remoteValues.get("max") != null
176                ? ChoiceType.CHOICE.toString()
177                : (Optional.ofNullable(_getFirstValueAsString(remoteValues.remove("mandatory")))
178                        .map(Boolean::parseBoolean)
179                        .orElse(false)
180                    ? ChoiceType.MANDATORY.toString()
181                    : ChoiceType.OPTIONAL.toString()
182                );
183        
184        remoteValues.put("choiceType", List.of(choiceType));
185    }
186    
187    @Override
188    protected List<Object> _transformContentAttributeValues(ContentAttributeDefinition definition, List<Object> values, Logger logger)
189    {
190        if (definition.getName().equals("orgUnit"))
191        {
192            List<Object> result = new ArrayList<>();
193            
194            for (Object value : values)
195            {
196                if (value != null)
197                {
198                    ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, OrgUnitFactory.ORGUNIT_CONTENT_TYPE);
199                    StringExpression valueExpr = new StringExpression("pegaseCode", Operator.EQ, (String) value);
200                    
201                    String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, valueExpr));
202    
203                    String orgUnitId = _resolver.<Content>query(xpathQuery).stream()
204                                                .findFirst()
205                                                .map(Content::getId)
206                                                .orElse(null);
207                    
208                    if (orgUnitId != null)
209                    {
210                        result.add(orgUnitId);
211                    }
212                }
213            }
214
215            return result;
216        }
217        
218        return super._transformContentAttributeValues(definition, values, logger);
219    }
220}