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.contenttypeseditor; 017 018import java.io.IOException; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.Map; 022import java.util.Map.Entry; 023import java.util.Set; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.cocoon.ProcessingException; 028import org.apache.cocoon.environment.ObjectModelHelper; 029import org.apache.cocoon.environment.Request; 030import org.apache.cocoon.generation.ServiceableGenerator; 031import org.apache.cocoon.xml.AttributesImpl; 032import org.apache.cocoon.xml.XMLUtils; 033import org.apache.commons.lang.StringUtils; 034import org.xml.sax.SAXException; 035 036import org.ametys.cms.contenttype.ContentType; 037import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 038import org.ametys.cms.contenttype.MetadataDefinition; 039import org.ametys.cms.contenttype.MetadataType; 040import org.ametys.cms.contenttype.RepeaterDefinition; 041import org.ametys.core.util.I18nUtils; 042import org.ametys.runtime.i18n.I18nizableText; 043import org.ametys.runtime.parameter.Validator; 044 045/** 046 * Content types export for generate a graph 047 */ 048public class ContentTypesGraphGenerator extends ServiceableGenerator 049{ 050 051 /** The request parameter name for content ids */ 052 public static final String PARAMETER_CONTENTTYPE_IDS = "contenttype-ids"; 053 /** The request parameter name for tree view */ 054 public static final String PARAMETER_CONTENTTYPE_ISHIERARCHICALVIEW = "isHierarchicalView"; 055 /** The request parameter name for export all content types */ 056 public static final String PARAMETER_CONTENTTYPE_ALL = "all"; 057 058 private ContentTypeExtensionPoint _contentTypeEP; 059 060 private I18nUtils _i18nUtils; 061 062 @Override 063 public void service(ServiceManager serviceManager) throws ServiceException 064 { 065 super.service(serviceManager); 066 _contentTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); 067 _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE); 068 } 069 070 @Override 071 public void generate() throws IOException, SAXException, ProcessingException 072 { 073 Request request = ObjectModelHelper.getRequest(objectModel); 074 String[] contentTypeIds = request.getParameterValues(PARAMETER_CONTENTTYPE_IDS); 075 boolean isHierarchicalView = "true".equals(request.getParameter(PARAMETER_CONTENTTYPE_ISHIERARCHICALVIEW)); 076 boolean exportAllContentTypes = "true".equals(request.getParameter(PARAMETER_CONTENTTYPE_ALL)); 077 078 contentHandler.startDocument(); 079 XMLUtils.startElement(contentHandler, "content-types"); 080 081 if (exportAllContentTypes) 082 { 083 Set<String> contentTypesIds = _contentTypeEP.getExtensionsIds(); 084 _exportContentTypes(contentTypesIds, true, true); 085 } 086 else 087 { 088 if (contentTypeIds != null) 089 { 090 if (contentTypeIds.length < 2) 091 { 092 String contentTypeId = contentTypeIds[0]; 093 if (StringUtils.isBlank(contentTypeId)) 094 { 095 throw new IllegalArgumentException("Missing or empty '" + PARAMETER_CONTENTTYPE_IDS + "' parameter to export content type"); 096 } 097 098 if (isHierarchicalView) 099 { 100 Set<String> contentTypes = new HashSet<>(); 101 contentTypes.add(contentTypeId); 102 _retrieveChildrenContentTypesIds(contentTypeId, contentTypes); 103 _exportContentTypes(contentTypes, false, false); 104 } 105 else 106 { 107 ContentType contentType = _contentTypeEP.getExtension(contentTypeId); 108 _addAttributesContentType(contentType); 109 _addMetadatas(contentType); 110 XMLUtils.endElement(contentHandler, "content-type"); 111 } 112 } 113 else 114 { 115 Set<String> contentTypeIdSet = new HashSet<>(); 116 for (String contentTypeId : contentTypeIds) 117 { 118 if (StringUtils.isBlank(contentTypeId)) 119 { 120 throw new IllegalArgumentException("Missing or empty '" + PARAMETER_CONTENTTYPE_IDS + "' parameter to export content type"); 121 } 122 contentTypeIdSet.add(contentTypeId); 123 if (isHierarchicalView) 124 { 125 _retrieveChildrenContentTypesIds(contentTypeId, contentTypeIdSet); 126 } 127 } 128 _exportContentTypes(contentTypeIdSet, true, false); 129 } 130 } 131 } 132 133 XMLUtils.endElement(contentHandler, "content-types"); 134 contentHandler.endDocument(); 135 } 136 137 /** 138 * Retrieve content types ids for all children of content type with contentTypeId id 139 * @param contentTypeId The content type id of content type that we want to retrieves all children content types ids 140 * @param childrenContentTypesIds All children content types ids retrieved 141 */ 142 private void _retrieveChildrenContentTypesIds(String contentTypeId, Set<String> childrenContentTypesIds) 143 { 144 Set<String> directSubTypes = _contentTypeEP.getDirectSubTypes(contentTypeId); 145 for (String id : directSubTypes) 146 { 147 childrenContentTypesIds.add(id); 148 _retrieveChildrenContentTypesIds(id, childrenContentTypesIds); 149 } 150 } 151 152 /** 153 * Export content types 154 * @param contentTypesIds The content types ids to export 155 * @param addAssociations Allow to create a link association when a content type contains a Content metadata type 156 * @param addParents Allow to add all parents of content type even if the parent content type wasn't selected 157 * @throws SAXException If an error occured while saxing 158 */ 159 private void _exportContentTypes(Set<String> contentTypesIds, boolean addAssociations, boolean addParents) throws SAXException 160 { 161 for (String id : contentTypesIds) 162 { 163 ContentType contentType = _contentTypeEP.getExtension(id); 164 _addAttributesContentType(contentType); 165 _addMetadatas(contentType); 166 if (addAssociations) 167 { 168 _addAssociations(contentType, contentTypesIds); 169 } 170 String[] supertypeIds = contentType.getSupertypeIds(); 171 Set<ContentType> parentsContentTypes = new HashSet<>(); 172 for (String supertypeId : supertypeIds) 173 { 174 if (addParents || contentTypesIds.contains(supertypeId)) 175 { 176 parentsContentTypes.add(_contentTypeEP.getExtension(supertypeId)); 177 } 178 } 179 _addParents(parentsContentTypes); 180 XMLUtils.endElement(contentHandler, "content-type"); 181 } 182 } 183 184 private void _addAttributesContentType(ContentType contentType) throws SAXException 185 { 186 AttributesImpl attrs = new AttributesImpl(); 187 attrs.addCDATAAttribute("name", _getContentTypeName(contentType.getLabel())); 188 attrs.addCDATAAttribute("id", _getIdForGraph(contentType.getId())); 189 attrs.addCDATAAttribute("abstract", Boolean.toString(contentType.isAbstract())); 190 attrs.addCDATAAttribute("private", Boolean.toString(contentType.isPrivate())); 191 attrs.addCDATAAttribute("mixin", Boolean.toString(contentType.isMixin())); 192 attrs.addCDATAAttribute("referencetable", Boolean.toString(contentType.isReferenceTable())); 193 attrs.addCDATAAttribute("multilingual", Boolean.toString(contentType.isMultilingual())); 194 XMLUtils.startElement(contentHandler, "content-type", attrs); 195 } 196 197 private void _addMetadatas(ContentType contentType) throws SAXException 198 { 199 XMLUtils.startElement(contentHandler, "metadatas"); 200 Set<String> metadataNames = contentType.getMetadataNames(); 201 for (String string : metadataNames) 202 { 203 MetadataDefinition metadataDefinition = contentType.getMetadataDefinition(string); 204 _addMetadata(metadataDefinition, contentType); 205 } 206 XMLUtils.endElement(contentHandler, "metadatas"); 207 } 208 209 private void _addMetadata(MetadataDefinition metadataDefinition, ContentType contentType) throws SAXException 210 { 211 String referenceContentType = metadataDefinition.getReferenceContentType(); 212 if (referenceContentType.equals(contentType.getId())) 213 { 214 MetadataType metadataType = metadataDefinition.getType(); 215 String type = metadataType.name().toLowerCase(); 216 String name = _i18nUtils.translate(metadataDefinition.getLabel()); 217 name = name == null ? metadataDefinition.getLabel().toString() : name; 218 219 AttributesImpl attrs = new AttributesImpl(); 220 attrs.addCDATAAttribute("multiple", Boolean.toString(metadataDefinition.isMultiple())); 221 attrs.addCDATAAttribute("mandatory", Boolean.toString(_isMandatoryMetadata(metadataDefinition))); 222 if (metadataType == MetadataType.COMPOSITE) 223 { 224 if (metadataDefinition instanceof RepeaterDefinition) 225 { 226 type = "repeater"; 227 } 228 attrs.addCDATAAttribute("type", type); 229 XMLUtils.startElement(contentHandler, "metadata", attrs); 230 XMLUtils.createElement(contentHandler, "name", name); 231 Set<String> metadataNames = metadataDefinition.getMetadataNames(); 232 for (String metadata : metadataNames) 233 { 234 MetadataDefinition childMetadataDefinition = metadataDefinition.getMetadataDefinition(metadata); 235 _addMetadata(childMetadataDefinition, contentType); 236 } 237 XMLUtils.endElement(contentHandler, "metadata"); 238 } 239 else 240 { 241 if (metadataType == MetadataType.CONTENT) 242 { 243 String linkedContentTypeId = metadataDefinition.getContentType(); 244 if (linkedContentTypeId != null) 245 { 246 ContentType linkedContent = _contentTypeEP.getExtension(metadataDefinition.getContentType()); 247 attrs.addCDATAAttribute("content-type", _getContentTypeName(linkedContent.getLabel())); 248 } 249 else 250 { 251 attrs.addCDATAAttribute("content-type", type); 252 } 253 attrs.addCDATAAttribute("invert", _getInvert(metadataDefinition)); 254 } 255 attrs.addCDATAAttribute("type", type); 256 XMLUtils.createElement(contentHandler, "metadata", attrs, name); 257 } 258 } 259 } 260 261 private void _addParents(Set<ContentType> parentsContentTypes) throws SAXException 262 { 263 XMLUtils.startElement(contentHandler, "parents"); 264 for (ContentType contentType : parentsContentTypes) 265 { 266 AttributesImpl attrs = new AttributesImpl(); 267 attrs.addCDATAAttribute("id", _getIdForGraph(contentType.getId())); 268 XMLUtils.createElement(contentHandler, "parent", attrs, _getContentTypeName(contentType.getLabel())); 269 } 270 XMLUtils.endElement(contentHandler, "parents"); 271 } 272 273 private void _addAssociations(ContentType contentType, Set<String> contentTypesIds) throws SAXException 274 { 275 Map<ContentType, String> linkedContents = new HashMap<>(); 276 277 XMLUtils.startElement(contentHandler, "associations"); 278 279 Set<String> metadataNames = contentType.getMetadataNames(); 280 for (String string : metadataNames) 281 { 282 MetadataDefinition metadataDefinition = contentType.getMetadataDefinition(string); 283 String referenceContentType = metadataDefinition.getReferenceContentType(); 284 if (referenceContentType.equals(contentType.getId())) 285 { 286 _retrieveLinkedContents(metadataDefinition, contentTypesIds, linkedContents); 287 } 288 } 289 for (Entry<ContentType, String> linkedContent : linkedContents.entrySet()) 290 { 291 AttributesImpl attrs = new AttributesImpl(); 292 attrs.addCDATAAttribute("cardinality", linkedContent.getValue()); 293 XMLUtils.createElement(contentHandler, "association", attrs, _getIdForGraph(linkedContent.getKey().getId())); 294 } 295 296 XMLUtils.endElement(contentHandler, "associations"); 297 } 298 299 private void _retrieveLinkedContents(MetadataDefinition metadataDefinition, Set<String> contentTypesIds, Map<ContentType, String> linkedContents) 300 { 301 if (metadataDefinition.getType() == MetadataType.COMPOSITE) 302 { 303 Set<String> compositeMetadataNames = metadataDefinition.getMetadataNames(); 304 for (String compositeMetadata : compositeMetadataNames) 305 { 306 MetadataDefinition childMetadataDefinition = metadataDefinition.getMetadataDefinition(compositeMetadata); 307 _retrieveLinkedContents(childMetadataDefinition, contentTypesIds, linkedContents); 308 } 309 } 310 else if (metadataDefinition.getType() == MetadataType.CONTENT) 311 { 312 String contentTypeId = metadataDefinition.getContentType(); 313 if (contentTypeId != null) 314 { 315 if (contentTypesIds.contains(contentTypeId)) 316 { 317 ContentType linkedContent = _contentTypeEP.getExtension(contentTypeId); 318 if (!linkedContents.containsKey(linkedContent)) 319 { 320 linkedContents.put(linkedContent, getCardinality(metadataDefinition)); 321 } 322 else 323 { 324 linkedContents.put(linkedContent, ""); 325 } 326 } 327 } 328 } 329 } 330 331 private String getCardinality(MetadataDefinition metadataDefinition) 332 { 333 String cardinality = ""; 334 cardinality += _isMandatoryMetadata(metadataDefinition) ? "1" : "0"; 335 if (metadataDefinition.isMultiple()) 336 { 337 cardinality += "..*"; 338 } 339 else 340 { 341 if (!_isMandatoryMetadata(metadataDefinition)) 342 { 343 cardinality += "..1"; 344 } 345 } 346 return cardinality; 347 } 348 349 private String _getInvert(MetadataDefinition metadataDefinition) 350 { 351 String invert = metadataDefinition.getInvertRelationPath(); 352 if (invert == null) 353 { 354 invert = ""; 355 } 356 else 357 { 358 ContentType linkedContent = _contentTypeEP.getExtension(metadataDefinition.getContentType()); 359 MetadataDefinition linkedMetadataDefinition = linkedContent.getMetadataDefinitionByPath(invert); 360 invert = _getContentTypeName(linkedMetadataDefinition.getLabel()); 361 } 362 return invert; 363 } 364 365 private String _getContentTypeName(I18nizableText labelContentType) 366 { 367 String translatedLabel = _i18nUtils.translate(labelContentType); 368 String name = translatedLabel == null ? labelContentType.toString() : translatedLabel; 369 return name; 370 } 371 372 private String _getIdForGraph(String text) 373 { 374 String result = ""; 375 result = text.replaceAll("\\s", "_"); 376 result = text.replaceAll("[^\\p{L}&&[^0-9]]", "_"); 377 return result; 378 } 379 380 private boolean _isMandatoryMetadata(MetadataDefinition metadataDefinition) 381 { 382 boolean isMandatory = false; 383 Validator validator = metadataDefinition.getValidator(); 384 if (validator != null) 385 { 386 Map<String, Object> configuration = validator.getConfiguration(); 387 if (configuration != null) 388 { 389 Object object = configuration.get("mandatory"); 390 if (object instanceof Boolean) 391 { 392 isMandatory = (boolean) object; 393 } 394 } 395 } 396 return isMandatory; 397 } 398 399}