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