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}