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}