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