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