001/* 002 * Copyright 2014 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 */ 016 017package org.ametys.cms.content.references; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.HashMap; 022import java.util.Map; 023 024import org.apache.avalon.framework.component.Component; 025import org.apache.avalon.framework.logger.AbstractLogEnabled; 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; 030 031import org.ametys.cms.contenttype.ContentConstants; 032import org.ametys.cms.contenttype.ContentTypesHelper; 033import org.ametys.cms.contenttype.MetadataDefinition; 034import org.ametys.cms.contenttype.MetadataType; 035import org.ametys.cms.contenttype.RepeaterDefinition; 036import org.ametys.cms.repository.Content; 037import org.ametys.plugins.repository.metadata.CompositeMetadata; 038import org.ametys.plugins.repository.metadata.RichText; 039 040/** 041 * The outgoing references extractor is a class that analyzes a content to extract the list of outgoing references in the metadata. 042 * Theses references should be stored on the content to allow research or post-processing later (example: consistency analyzer). 043 */ 044public class OutgoingReferencesExtractor extends AbstractLogEnabled implements Serviceable, Component 045{ 046 /** Avalon role */ 047 public static final String ROLE = OutgoingReferencesExtractor.class.getName(); 048 /** Constant for the content type for outgoing references */ 049 public static final String OUTGOING_REFERENCE_TYPE_CONTENT = "content"; 050 051 /** Constants for the explorer type for outgoing references */ 052 private static final String __OUTGOING_REFERENCE_TYPE_EXPLORER = "explorer"; 053 054 /** Helper for content types */ 055 protected ContentTypesHelper _contentTypesHelper; 056 057 @Override 058 public void service(ServiceManager serviceManager) throws ServiceException 059 { 060 _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE); 061 } 062 063 /** 064 * This method analyzes the content to return the list of outgoing references grouped by metadata path. 065 * @param content The content to analyze 066 * @return A map where keys are metadata path and values are outgoing references for this metadata. Can not be null. 067 */ 068 public Map<String, OutgoingReferences> getOutgoingReferences(Content content) 069 { 070 return _analyzeCompositeMetadata(content, null, content.getMetadataHolder(), StringUtils.EMPTY); 071 } 072 073 /** 074 * Analyze a composite metadata to extract the outgoing references 075 * @param content The current content 076 * @param parentMetadataDefinition The parent metadata definition (can be null, at the root level) 077 * @param metadataHolder The composite metadata to analyze. Cannot be null 078 * @param metadataPathPrefix The path of the current metadata holder 079 * @return The outgoing references, indexed by metadata path. 080 */ 081 protected Map<String, OutgoingReferences> _analyzeCompositeMetadata(Content content, MetadataDefinition parentMetadataDefinition, CompositeMetadata metadataHolder, String metadataPathPrefix) 082 { 083 Map<String, OutgoingReferences> outgoingReferencesByPath = new HashMap<>(); 084 085 for (String metadataName : metadataHolder.getMetadataNames()) 086 { 087 MetadataDefinition metadataDefinition = _getMetadataDefinition(content, parentMetadataDefinition, metadataName); 088 if (metadataDefinition != null) // Filter out metadata not declared in content type's definition 089 { 090 OutgoingReferences outgoingReferences = null; 091 092 String metadataPath = metadataPathPrefix + metadataName; 093 MetadataType type = metadataDefinition.getType(); 094 095 switch (type) 096 { 097 case COMPOSITE: 098 if (metadataDefinition instanceof RepeaterDefinition) 099 { 100 CompositeMetadata repeaterHolder = metadataHolder.getCompositeMetadata(metadataName); 101 for (String entry : repeaterHolder.getMetadataNames()) 102 { 103 CompositeMetadata entryMetadataHolder = repeaterHolder.getCompositeMetadata(entry); 104 105 Map<String, OutgoingReferences> entryOutgoingReferences = _analyzeCompositeMetadata(content, metadataDefinition, entryMetadataHolder, metadataPath + '[' + entry + ']' + ContentConstants.METADATA_PATH_SEPARATOR); 106 outgoingReferencesByPath.putAll(entryOutgoingReferences); 107 } 108 } 109 else 110 { 111 Map<String, OutgoingReferences> compOutgoingReferences = _analyzeCompositeMetadata(content, metadataDefinition, metadataHolder.getCompositeMetadata(metadataName), metadataPath + ContentConstants.METADATA_PATH_SEPARATOR); 112 outgoingReferencesByPath.putAll(compOutgoingReferences); 113 } 114 break; 115 case FILE: 116 // Could be a binary or a string. 117 if (!CompositeMetadata.MetadataType.BINARY.equals(metadataHolder.getType(metadataName))) 118 { 119 outgoingReferences = _analyzeResourceFileMetadata(metadataDefinition, metadataHolder.getString(metadataName)); 120 } 121 break; 122 case REFERENCE: 123 CompositeMetadata compositeReference = metadataHolder.getCompositeMetadata(metadataName); 124 outgoingReferences = _analyzeReferenceMetadata(metadataDefinition, compositeReference); 125 break; 126 case RICH_TEXT: 127 outgoingReferences = _analyzeMetadata(metadataDefinition, metadataHolder.getRichText(metadataName)); 128 break; 129 case CONTENT: 130 outgoingReferences = _analyzeContentMetadata(metadataDefinition, metadataHolder.getStringArray(metadataName)); 131 break; 132 case BOOLEAN: 133 case LONG: 134 case DOUBLE: 135 case DATE: 136 case DATETIME: 137 case GEOCODE: 138 case STRING: 139 case MULTILINGUAL_STRING: 140 case USER: 141 case SUB_CONTENT: 142 case BINARY: 143 break; 144 default: 145 getLogger().warn( 146 "The outgoing references extractor does not handle metadata of type '" + type + "'.\nSkipping extraction process for the metadata '" 147 + metadataDefinition.getId() + "'."); 148 break; 149 } 150 151 // Update outgoing references by path map. 152 if (outgoingReferences != null) 153 { 154 outgoingReferencesByPath.put(metadataPath, outgoingReferences); 155 } 156 } 157 } 158 159 return outgoingReferencesByPath; 160 } 161 162 /** 163 * Analyze a rich text metadata. 164 * Delegates processing to the rich text outgoing references extractor of the metadata definition 165 * @param metadataDefinition The metadata definition corresponding to the current metadata 166 * @param metadata The metadata to analyze. Cannot be null 167 * @return The corresponding outgoing references. 168 */ 169 protected OutgoingReferences _analyzeMetadata(MetadataDefinition metadataDefinition, RichText metadata) 170 { 171 return metadataDefinition.getRichTextOutgoingReferencesExtractor().getOutgoingReferences(metadata); 172 } 173 174 /** 175 * Analyze a string file metadata. 176 * @param metadataDefinition The metadata definition corresponding to the current metadata 177 * @param metadata The metadata to analyze. Cannot be null 178 * @return The corresponding outgoing references. 179 */ 180 protected OutgoingReferences _analyzeResourceFileMetadata(MetadataDefinition metadataDefinition, String metadata) 181 { 182 if (StringUtils.isNotEmpty(metadata)) 183 { 184 OutgoingReferences references = new OutgoingReferences(); 185 186 ArrayList<String> referenceValues = new ArrayList<>(1); 187 referenceValues.add(metadata); 188 189 references.put(__OUTGOING_REFERENCE_TYPE_EXPLORER, referenceValues); 190 191 return references; 192 } 193 194 return null; 195 } 196 197 /** 198 * Analyze a reference metadata. 199 * @param metadataDefinition The metadata definition corresponding to the current metadata 200 * @param metadata The reference metadata 201 * @return The corresponding outgoing references. 202 */ 203 protected OutgoingReferences _analyzeReferenceMetadata(MetadataDefinition metadataDefinition, CompositeMetadata metadata) 204 { 205 OutgoingReferences references = new OutgoingReferences(); 206 207 String value = metadata.getString("value"); 208 209 references.put(metadata.getString("type"), Arrays.asList(new String[] {value})); 210 return references; 211 } 212 213 /** 214 * Analyze a content metadata. 215 * @param metadataDefinition The metadata definition corresponding to the current metadata 216 * @param contentIds The metadata value to analyze. Cannot be null 217 * @return The corresponding outgoing references. 218 */ 219 protected OutgoingReferences _analyzeContentMetadata(MetadataDefinition metadataDefinition, String[] contentIds) 220 { 221 OutgoingReferences references = new OutgoingReferences(); 222 references.put(OUTGOING_REFERENCE_TYPE_CONTENT, Arrays.asList(contentIds)); 223 return references; 224 } 225 226 /** 227 * Retrieves a sub metadata definition from a content type or 228 * a parent metadata definition. 229 * @param content the content. 230 * @param parentMetadataDefinition the parent metadata definition. 231 * @param metadataName the metadata name. 232 * @return the metadata definition found or <code>null</code> otherwise. 233 */ 234 protected MetadataDefinition _getMetadataDefinition(Content content, MetadataDefinition parentMetadataDefinition, String metadataName) 235 { 236 if (parentMetadataDefinition == null) 237 { 238 return _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes()); 239 } 240 else 241 { 242 return parentMetadataDefinition.getMetadataDefinition(metadataName); 243 } 244 } 245}