001/* 002 * Copyright 2010 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.cms.contenttype; 017 018import java.io.File; 019import java.io.FileInputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.LinkedHashMap; 028import java.util.List; 029import java.util.Map; 030import java.util.Optional; 031import java.util.Set; 032import java.util.regex.Pattern; 033import java.util.stream.Collectors; 034 035import org.apache.avalon.framework.activity.Disposable; 036import org.apache.avalon.framework.component.ComponentException; 037import org.apache.avalon.framework.configuration.Configuration; 038import org.apache.avalon.framework.configuration.ConfigurationException; 039import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 040import org.apache.avalon.framework.context.Context; 041import org.apache.avalon.framework.context.ContextException; 042import org.apache.avalon.framework.context.Contextualizable; 043import org.apache.avalon.framework.service.ServiceException; 044import org.apache.avalon.framework.service.ServiceManager; 045import org.apache.avalon.framework.thread.ThreadSafe; 046import org.apache.cocoon.Constants; 047import org.apache.commons.lang3.RandomStringUtils; 048import org.apache.commons.lang3.StringUtils; 049import org.apache.excalibur.source.Source; 050import org.xml.sax.SAXException; 051 052import org.ametys.cms.content.references.RichTextOutgoingReferencesExtractor; 053import org.ametys.cms.content.referencetable.HierarchicalReferenceTablesHelper; 054import org.ametys.cms.contenttype.ContentTypesHelper.ViewConfigurations; 055import org.ametys.cms.contenttype.ContentTypesHelper.ViewConfigurationsByType; 056import org.ametys.cms.data.type.ModelItemTypeConstants; 057import org.ametys.cms.data.type.ModelItemTypeExtensionPoint; 058import org.ametys.cms.model.ContentRestrictedCompositeDefinition; 059import org.ametys.cms.model.parsing.ContentRestrictedCompositeDefinitionParser; 060import org.ametys.cms.model.parsing.ContentRestrictedRepeaterDefinitionParser; 061import org.ametys.cms.model.properties.ElementRefProperty; 062import org.ametys.cms.model.properties.Property; 063import org.ametys.cms.model.restrictions.ContentRestrictedModelItemHelper; 064import org.ametys.cms.model.restrictions.RestrictedModelItem; 065import org.ametys.cms.model.restrictions.Restrictions; 066import org.ametys.cms.repository.Content; 067import org.ametys.cms.transformation.RichTextTransformer; 068import org.ametys.cms.transformation.docbook.DocbookOutgoingReferencesExtractor; 069import org.ametys.cms.transformation.docbook.DocbookTransformer; 070import org.ametys.plugins.repository.AmetysRepositoryException; 071import org.ametys.runtime.i18n.I18nizableText; 072import org.ametys.runtime.model.ElementDefinition; 073import org.ametys.runtime.model.Enumerator; 074import org.ametys.runtime.model.ModelHelper.ConfigurationAndPluginName; 075import org.ametys.runtime.model.ModelItem; 076import org.ametys.runtime.model.ModelItemContainer; 077import org.ametys.runtime.model.ModelItemGroup; 078import org.ametys.runtime.model.TemporaryViewReference; 079import org.ametys.runtime.model.View; 080import org.ametys.runtime.model.ViewElement; 081import org.ametys.runtime.model.ViewItem; 082import org.ametys.runtime.model.ViewItemAccessor; 083import org.ametys.runtime.model.exception.UndefinedItemPathException; 084import org.ametys.runtime.model.type.ElementType; 085import org.ametys.runtime.parameter.Validator; 086import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 087 088/** 089 * Type of content which is retrieved from a XML configuration. 090 * TODO document xml configuration 091 * ... 092 * Provides access based on rights and current workflow steps.<p> 093 * It used a configuration file with the following format: 094 * <code><br> 095 * <restrict-to><br> 096 * [<right type="read|write" id="RIGHT_ID"/>]* 097 * <!-- logical OR between several right id of the same type --><br> 098 * [<workflow type="read|write" step="3"/>]* 099 * <!-- logical OR between several workflow step of the same type --><br> 100 * [<cannot type="read|write"/>]*<br> 101 * </restrict-to><br> 102 * </code> 103 */ 104public class DefaultContentType extends AbstractContentTypeDescriptor implements ContentType, Contextualizable, ThreadSafe, Disposable 105{ 106 /** Suffix for global validator role. */ 107 protected static final String __GLOBAL_VALIDATOR_ROLE_PREFIX = "_globalvalidator"; 108 109 static Pattern __annotationNamePattern; 110 111 /** Model items */ 112 protected Map<String, ModelItem> _modelItems = new LinkedHashMap<>(); 113 /** The properties references as: Impacted ContentType -> local IndexingField name -> path to impacted content. */ 114 protected Map<String, Map<String, List<String>>> _propertiesReferences = new HashMap<>(); 115 /** The right needed to create a content of this type, or null if no right is needed. */ 116 protected String _right; 117 /** The abstract property */ 118 protected boolean _abstract; 119 /** Configured workflow names of the content type or its supertypes, can be empty */ 120 protected Set<String> _configuredWorkflowNames; 121 /** Default workflow name. */ 122 protected Optional<String> _defaultWorkflowName; 123 /** The tags */ 124 protected Set<String> _tags; 125 /** The inheritable tags */ 126 protected Set<String> _inheritableTags; 127 /** The parent attribute definition */ 128 protected ContentAttributeDefinition _parentAttributeDefinition; 129 /** Service manager. */ 130 protected ServiceManager _manager; 131 /** Avalon Context. */ 132 protected Context _context; 133 /** Cocoon Context */ 134 protected org.apache.cocoon.environment.Context _cocoonContext; 135 /** The restrictions helper */ 136 protected ContentRestrictedModelItemHelper _restrictedModelItemHelper; 137 /** Default rich text transformer. */ 138 protected RichTextTransformer _richTextTransformer; 139 /** Docbook (rich text) outgoing references extractor. */ 140 protected RichTextOutgoingReferencesExtractor _richTextOutgoingReferencesExtractor; 141 /** Potential global validators. */ 142 protected List<ContentValidator> _globalValidators; 143 /** Potentiel richtext updater */ 144 protected RichTextUpdater _richTextUpdater; 145 /** The helper component for hierarchical simple contents */ 146 protected HierarchicalReferenceTablesHelper _hierarchicalSimpleContentsHelper; 147 /** List of overridden attributes */ 148 protected List<String> _overriddenModelItems = new ArrayList<>(); 149 /** List of overridden views */ 150 protected List<String> _overriddenViews = new ArrayList<>(); 151 /** All content type's views */ 152 protected Map<String, View> _views = new LinkedHashMap<>(); 153 /** All configurations of content type's views */ 154 protected Map<String, ViewConfigurations> _viewConfigurations; 155 156 /** The parser for content attribute's definitions */ 157 protected ContentAttributeDefinitionParser _attributeDefinitionParser; 158 /** The parser for content compisite's definitions */ 159 protected ContentRestrictedCompositeDefinitionParser _compositeDefinitionParser; 160 /** The parser for content repeater's definitions */ 161 protected ContentRestrictedRepeaterDefinitionParser _repeaterDefinitionParser; 162 /** The parser for dublin core attribute's definitions */ 163 protected DublinCoreAttributeDefinitionParser _dublinCoreAttributeDefinitionParser; 164 165 // ComponentManager for validators 166 private ThreadSafeComponentManager<Validator> _validatorManager; 167 168 // ComponentManager for Global Validators 169 private ThreadSafeComponentManager<ContentValidator> _globalValidatorsManager; 170 171 private ContentTypeReservedAttributeNameExtensionPoint _contentTypeReservedAttributeNameExtensionPoint; 172 173 // ComponentManager for enumerators 174 private ThreadSafeComponentManager<Enumerator> _enumeratorManager; 175 176 // ComponentManager for properties 177 private ThreadSafeComponentManager<Property> _propertiesManager; 178 // Roles of properties to instanciate via the propertiesManager, with the property's parent 179 private Map<String, Optional<ModelItemGroup>> _propertyRoles = new HashMap<>(); 180 // Index to avoid properties with same role 181 private int _propertiesIndex; 182 183 // Content attribute types extension point 184 private ModelItemTypeExtensionPoint _contentAttributeTypeExtensionPoint; 185 186 // Content properties types extension point 187 private ModelItemTypeExtensionPoint _contentPropertyTypeExtensionPoint; 188 189 private boolean _isSimple; 190 191 private boolean _isMultilingual; 192 193 private ContentTypeOverridesExtensionPoint _contentTypeOverridesExtensionPoint; 194 195 @Override 196 public void service(ServiceManager manager) throws ServiceException 197 { 198 super.service(manager); 199 _manager = manager; 200 _richTextTransformer = (RichTextTransformer) manager.lookup(DocbookTransformer.ROLE); 201 _richTextOutgoingReferencesExtractor = (RichTextOutgoingReferencesExtractor) manager.lookup(DocbookOutgoingReferencesExtractor.ROLE); 202 _richTextUpdater = (RichTextUpdater) manager.lookup(DocbookRichTextUpdater.ROLE); 203 _hierarchicalSimpleContentsHelper = (HierarchicalReferenceTablesHelper) manager.lookup(HierarchicalReferenceTablesHelper.ROLE); 204 _restrictedModelItemHelper = (ContentRestrictedModelItemHelper) manager.lookup(ContentRestrictedModelItemHelper.ROLE); 205 _contentAttributeTypeExtensionPoint = (ModelItemTypeExtensionPoint) manager.lookup(ModelItemTypeExtensionPoint.ROLE_CONTENT_ATTRIBUTE); 206 _contentPropertyTypeExtensionPoint = (ModelItemTypeExtensionPoint) manager.lookup(ModelItemTypeExtensionPoint.ROLE_CONTENT_PROPERTY); 207 _contentTypeOverridesExtensionPoint = (ContentTypeOverridesExtensionPoint) manager.lookup(ContentTypeOverridesExtensionPoint.ROLE); 208 } 209 210 /** 211 * Get the ContentTypeReservedAttributeNameExtensionPoint instance 212 * @return the instance 213 */ 214 protected ContentTypeReservedAttributeNameExtensionPoint _getContentTypeReservedAttributeNameExtensionPoint() 215 { 216 if (_contentTypeReservedAttributeNameExtensionPoint == null) 217 { 218 try 219 { 220 _contentTypeReservedAttributeNameExtensionPoint = (ContentTypeReservedAttributeNameExtensionPoint) _manager.lookup(ContentTypeReservedAttributeNameExtensionPoint.ROLE); 221 } 222 catch (ServiceException e) 223 { 224 throw new RuntimeException(e); 225 } 226 } 227 return _contentTypeReservedAttributeNameExtensionPoint; 228 } 229 230 @Override 231 public void contextualize(Context context) throws ContextException 232 { 233 _context = context; 234 _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 235 } 236 237 @Override 238 public void dispose() 239 { 240 _validatorManager.dispose(); 241 _validatorManager = null; 242 243 _globalValidatorsManager.dispose(); 244 _globalValidatorsManager = null; 245 246 _enumeratorManager.dispose(); 247 _enumeratorManager = null; 248 249 _propertiesManager.dispose(); 250 _propertiesManager = null; 251 } 252 253 @Override 254 protected Configuration getRootConfiguration(Configuration configuration) 255 { 256 return configuration.getChild("content-type"); 257 } 258 259 /** 260 * Get the override configurations both from _override folder and extensions 261 * @return The list of the override configurations 262 * @throws ConfigurationException If an error occurs 263 */ 264 protected List<ConfigurationAndPluginName> getOverrideConfigurations() throws ConfigurationException 265 { 266 List<ConfigurationAndPluginName> overrideConfigurations = getOverrideConfigurationsFromExtensions(); 267 getOverrideConfigurationFomFolder().ifPresent(overrideConfigurations::add); 268 return overrideConfigurations; 269 } 270 271 /** 272 * Get the override configuration from _override folder 273 * @return the optional override configuration from _override folder 274 * @throws ConfigurationException if an error occurred 275 */ 276 protected Optional<ConfigurationAndPluginName> getOverrideConfigurationFomFolder() throws ConfigurationException 277 { 278 Optional<ConfigurationAndPluginName> overrideConfiguration = Optional.empty(); 279 File ctFile = new File(_cocoonContext.getRealPath("/WEB-INF/param/content-types/_override/" + _id + ".xml")); 280 281 if (ctFile.exists()) 282 { 283 try (InputStream is = new FileInputStream(ctFile)) 284 { 285 Configuration configuration = new DefaultConfigurationBuilder(true).build(is); 286 overrideConfiguration = Optional.of(new ConfigurationAndPluginName(configuration, _pluginName)); 287 } 288 catch (Exception ex) 289 { 290 Configuration configuration = overrideConfiguration.map(ConfigurationAndPluginName::configuration) 291 .orElse(null); 292 throw new ConfigurationException("Unable to parse overridden configuration for content type '" + _id + "' at WEB-INF/param/content-types/_override/" + _id + ".xml", configuration, ex); 293 } 294 } 295 296 return overrideConfiguration; 297 } 298 299 /** 300 * Get the override configurations from extensions 301 * @return The list of the override configurations from extensions 302 * @throws ConfigurationException If an error occurs 303 */ 304 protected List<ConfigurationAndPluginName> getOverrideConfigurationsFromExtensions() throws ConfigurationException 305 { 306 return _contentTypeOverridesExtensionPoint.getOverrideConfigurations(_id); 307 } 308 309 @Override 310 public void configure(Configuration configuration) throws ConfigurationException 311 { 312 _validatorManager = new ThreadSafeComponentManager<>(); 313 _validatorManager.setLogger(getLogger()); 314 _validatorManager.contextualize(_context); 315 _validatorManager.service(_manager); 316 317 _globalValidatorsManager = new ThreadSafeComponentManager<>(); 318 _globalValidatorsManager.setLogger(getLogger()); 319 _globalValidatorsManager.contextualize(_context); 320 _globalValidatorsManager.service(_manager); 321 322 _enumeratorManager = new ThreadSafeComponentManager<>(); 323 _enumeratorManager.setLogger(getLogger()); 324 _enumeratorManager.contextualize(_context); 325 _enumeratorManager.service(_manager); 326 327 _propertiesManager = new ThreadSafeComponentManager<>(); 328 _propertiesManager.setLogger(getLogger()); 329 _propertiesManager.contextualize(_context); 330 _propertiesManager.service(_manager); 331 332 Configuration rootConfiguration = getRootConfiguration(configuration); 333 334 _abstract = rootConfiguration.getAttributeAsBoolean("abstract", false); 335 336 _configureSuperTypes(rootConfiguration); 337 338 _configureLabels(rootConfiguration); 339 _configureIcons(rootConfiguration); 340 341 _configureCSSFiles(rootConfiguration); 342 343 // Tags 344 _tags = new HashSet<>(); 345 _inheritableTags = new HashSet<>(); 346 347 if (rootConfiguration.getChild("tags", false) != null) 348 { 349 if (rootConfiguration.getChild("tags").getAttributeAsBoolean("inherited", false)) 350 { 351 // Get tags from super types 352 for (String superTypeId : _superTypeIds) 353 { 354 ContentType superType = _cTypeEP.getExtension(superTypeId); 355 _tags.addAll(superType.getInheritableTags()); 356 _inheritableTags.addAll(superType.getInheritableTags()); 357 } 358 } 359 360 _configureLocalTags(rootConfiguration.getChild("tags")); 361 } 362 363 // Rights 364 _right = rootConfiguration.getChild("right").getValue(null); 365 366 _isSimple = true; 367 for (String superTypeId : _superTypeIds) 368 { 369 ContentType superType = _cTypeEP.getExtension(superTypeId); 370 if (superType == null) 371 { 372 throw new ConfigurationException("The content type '" + this.getId() + "' cannot extends the unexisting type '" + superTypeId + "'"); 373 } 374 if (!superType.isSimple()) 375 { 376 _isSimple = false; 377 break; 378 } 379 } 380 381 _configureDefaultWorkflowName(rootConfiguration); 382 383 _isMultilingual = false; 384 for (String superTypeId : _superTypeIds) 385 { 386 ContentType superType = _cTypeEP.getExtension(superTypeId); 387 if (superType.isMultilingual()) 388 { 389 _isMultilingual = true; 390 break; 391 } 392 } 393 394 // Attribute definitions 395 _configureModelItems(rootConfiguration); 396 397 // Parent content type 398 _configureParentContentType(rootConfiguration); 399 400 // Views configurations 401 _configureViewConfigurations(rootConfiguration); 402 403 _checkForReservedAttributeName(); 404 405 // Global validators 406 _configureGlobalValidators (rootConfiguration); 407 } 408 409 private void _checkForReservedAttributeName() throws ConfigurationException 410 { 411 Set<String> reservedNames = _getContentTypeReservedAttributeNameExtensionPoint().getExtensionsIds(); 412 413 List<String> usedReservedKeywords = new ArrayList<>(reservedNames); 414 usedReservedKeywords.retainAll(_modelItems.keySet()); 415 416 if (!usedReservedKeywords.isEmpty()) 417 { 418 throw new ConfigurationException("In content type '" + _id + "', one or more attributes are named with a reserved keyword: " + StringUtils.join(usedReservedKeywords, ", ") 419 + ". The reserved keywords are: {" + StringUtils.join(reservedNames, ", ") + "}"); 420 } 421 } 422 423 /** 424 * Configure attribute definitions 425 * @param mainConfig The content type configuration 426 * @throws ConfigurationException if an error occurred 427 */ 428 protected void _configureModelItems (Configuration mainConfig) throws ConfigurationException 429 { 430 _attributeDefinitionParser = new ContentAttributeDefinitionParser(_contentAttributeTypeExtensionPoint, _enumeratorManager, _validatorManager); 431 _compositeDefinitionParser = new ContentRestrictedCompositeDefinitionParser(_contentAttributeTypeExtensionPoint); 432 _repeaterDefinitionParser = new ContentRestrictedRepeaterDefinitionParser(_contentAttributeTypeExtensionPoint); 433 _dublinCoreAttributeDefinitionParser = new DublinCoreAttributeDefinitionParser(_contentAttributeTypeExtensionPoint, _enumeratorManager, _validatorManager, _dcProvider); 434 435 try 436 { 437 // First, get attributes from super type if applicable. 438 _modelItems.putAll(_contentTypesHelper.getModelItemsIndexedByName(_superTypeIds)); 439 } 440 catch (IllegalArgumentException e) 441 { 442 throw new ConfigurationException("A model item is defined in several co-super-types '", mainConfig, e); 443 } 444 445 Map<String, ConfigurationAndPluginName> modelItemConfigurations = new LinkedHashMap<>(); 446 _getApplicableModelItems(new ConfigurationAndPluginName(mainConfig, _pluginName), modelItemConfigurations, false); 447 448 List<ConfigurationAndPluginName> overrideConfigurations = getOverrideConfigurations(); 449 _getApplicableModelItems(overrideConfigurations, modelItemConfigurations, true); 450 451 // Then, parse own model items 452 _parseAllModelItems(modelItemConfigurations); 453 454 try 455 { 456 _attributeDefinitionParser.lookupComponents(); 457 _lookupProperties(); 458// _dublinCoreAttributeDefinitionParser.lookupComponents(); 459 } 460 catch (Exception e) 461 { 462 throw new ConfigurationException("Unable to lookup parameter local components", mainConfig, e); 463 } 464 } 465 466 /** 467 * Fill a map of the applicable model item configurations. 468 * @param configs the list of content type configurations. 469 * @param modelItemConfigurations the Map of attributes {@link Configuration}, indexed by name. 470 * @param allowOverride if true, encountering an attribute which has already been declared (based on its name) will replace it. Otherwise, an exception will be thrown. 471 * @throws ConfigurationException if an error occurs. 472 */ 473 protected void _getApplicableModelItems(List<ConfigurationAndPluginName> configs, Map<String, ConfigurationAndPluginName> modelItemConfigurations, boolean allowOverride) throws ConfigurationException 474 { 475 for (ConfigurationAndPluginName configuration : configs) 476 { 477 _getApplicableModelItems(configuration, modelItemConfigurations, allowOverride); 478 } 479 } 480 481 /** 482 * Fill a map of the applicable attribute configurations. 483 * @param config the content type configuration. 484 * @param modelItemConfigurations the Map of model item {@link Configuration}s, indexed by name. 485 * @param allowOverride if true, encountering a model item which has already been declared (based on its name) will replace it. Otherwise, an exception will be thrown. 486 * @throws ConfigurationException if an error occurs. 487 */ 488 protected void _getApplicableModelItems(ConfigurationAndPluginName config, Map<String, ConfigurationAndPluginName> modelItemConfigurations, boolean allowOverride) throws ConfigurationException 489 { 490 for (Configuration childConfiguration : config.configuration().getChildren()) 491 { 492 String childName = childConfiguration.getName(); 493 494 if (childName.equals("metadata") || childName.equals("repeater") || childName.equals("property")) 495 { 496 String modelItemName = childConfiguration.getAttribute("name", ""); 497 498 if (!allowOverride && modelItemConfigurations.containsKey(modelItemName)) 499 { 500 throw new ConfigurationException("Model item with name '" + modelItemName + "' is already defined", childConfiguration); 501 } 502 else if (allowOverride && modelItemConfigurations.containsKey(modelItemName)) 503 { 504 _checkModelItemsTypeAndCardinality(modelItemConfigurations.get(modelItemName).configuration(), childConfiguration); 505 } 506 507 if (allowOverride) 508 { 509 this._overriddenModelItems.add(modelItemName); 510 } 511 512 modelItemConfigurations.put(modelItemName, new ConfigurationAndPluginName(childConfiguration, config.pluginName())); 513 } 514 else if (childName.equals("dublin-core")) 515 { 516 modelItemConfigurations.put("dc", new ConfigurationAndPluginName(childConfiguration, config.pluginName())); 517 } 518 } 519 } 520 521 public List<String> getOverriddenModelItems() 522 { 523 return this._overriddenModelItems; 524 } 525 526 /** 527 * Check if all model item's types defined in first configuration are equals to those defined in second configuration 528 * @param modelItemConf1 The first configuration to compare 529 * @param modelItemConf2 The second configuration to compare 530 * @throws ConfigurationException if the types are not equals 531 */ 532 protected void _checkModelItemsTypeAndCardinality (Configuration modelItemConf1, Configuration modelItemConf2) throws ConfigurationException 533 { 534 String type = modelItemConf1.getAttribute("type", ""); 535 String overridenType = modelItemConf2.getAttribute("type", ""); 536 if (!overridenType.equals(type)) 537 { 538 throw new ConfigurationException("The type of model item '" + modelItemConf1.getAttribute("name") + "' (" + type + ") cannot be overridden to: " + overridenType); 539 540 } 541 boolean cardinality = modelItemConf1.getAttributeAsBoolean("multiple", false); 542 boolean overriddenCardinality = modelItemConf2.getAttributeAsBoolean("multiple", false); 543 if (cardinality != overriddenCardinality) 544 { 545 throw new ConfigurationException("The cardinality of model item '" + modelItemConf1.getAttribute("name") + "' (" + (cardinality ? "multiple" : "not multiple") + ") defined in content type '" + _id + "', can not be overridden to cardinality: " + (overriddenCardinality ? "multiple" : "not multiple")); 546 } 547 548 if ("composite".equals(type) || modelItemConf1.getName().equals("repeater")) 549 { 550 for (Configuration childConfig1 : modelItemConf1.getChildren()) 551 { 552 String childName = childConfig1.getName(); 553 if (childName.equals("metadata") || childName.equals("repeater") || childName.equals("property")) 554 { 555 Configuration childConfig2 = null; 556 for (Configuration conf : modelItemConf2.getChildren(childName)) 557 { 558 if (childConfig1.getAttribute("name").equals(conf.getAttribute("name"))) 559 { 560 childConfig2 = conf; 561 break; 562 } 563 } 564 565 if (childConfig2 != null) 566 { 567 _checkModelItemsTypeAndCardinality (childConfig1, childConfig2); 568 } 569 } 570 } 571 } 572 } 573 574 /** 575 * Parse all attribute configurations. 576 * @param attributeConfigurations the attribute configurations. 577 * @throws ConfigurationException if the configuration is invalid. 578 */ 579 protected void _parseAllModelItems(Map<String, ConfigurationAndPluginName> attributeConfigurations) throws ConfigurationException 580 { 581 for (ConfigurationAndPluginName childConfiguration : attributeConfigurations.values()) 582 { 583 String childConfigName = childConfiguration.configuration().getName(); 584 585 if (childConfigName.equals("metadata") || childConfigName.equals("repeater") || childConfigName.equals("property")) 586 { 587 ModelItem child = _parseModelItem(childConfiguration, null); 588 if (child != null) 589 { 590 final String childName = child.getName(); 591 if (_modelItems.containsKey(childName)) 592 { 593 _checkModelItemsTypeAndCardinality(_modelItems.get(childName), child); 594 } 595 596 _checkContentTypeSimplicity(child); 597 _modelItems.put(childName, child); 598 } 599 } 600 else if (childConfigName.equals("dublin-core")) 601 { 602 _parseDublinCoreAttributes(); 603 } 604 } 605 } 606 607 /** 608 * Parses a model item 609 * @param itemConfigurationAndPluginName configuration of the model item to parse 610 * @param parent the parent of the model item to parse. Can be <code>null</code> if the item has no parent. 611 * @return the parsed model item 612 * @throws ConfigurationException if an error occurs while the model item is parsed 613 */ 614 @SuppressWarnings("static-access") 615 protected ModelItem _parseModelItem(ConfigurationAndPluginName itemConfigurationAndPluginName, ModelItemGroup parent) throws ConfigurationException 616 { 617 ModelItem modelItem = null; 618 Configuration itemConfiguration = itemConfigurationAndPluginName.configuration(); 619 final String itemConfigName = itemConfiguration.getName(); 620 if (itemConfigName.equals("metadata")) 621 { 622 String typeId = itemConfiguration.getAttribute("type"); 623 if (ModelItemTypeConstants.COMPOSITE_TYPE_ID.equals(typeId)) 624 { 625 modelItem = _compositeDefinitionParser.parse(_manager, itemConfigurationAndPluginName.pluginName(), itemConfiguration, this, parent); 626 } 627 else 628 { 629 modelItem = _attributeDefinitionParser.parse(_manager, itemConfigurationAndPluginName.pluginName(), itemConfiguration, this, parent); 630 } 631 } 632 else if ("repeater".equals(itemConfigName)) 633 { 634 modelItem = _repeaterDefinitionParser.parse(_manager, itemConfigurationAndPluginName.pluginName(), itemConfiguration, this, parent); 635 } 636 else if ("property".equals(itemConfigName)) 637 { 638 String propertyRole = _parseProperty(itemConfiguration, ++_propertiesIndex); 639 _propertyRoles.put(propertyRole, Optional.ofNullable(parent)); 640 641 // unable to lookup to the property component yet, all properties have to be initialized at one time 642 // retrieve a null item 643 } 644 645 if (modelItem != null && modelItem instanceof ModelItemGroup) 646 { 647 for (Configuration childConfiguration : itemConfiguration.getChildren()) 648 { 649 _parseModelItem(new ConfigurationAndPluginName(childConfiguration, itemConfigurationAndPluginName.pluginName()), (ModelItemGroup) modelItem); 650 } 651 } 652 653 return modelItem; 654 } 655 656 /** 657 * Parses a property 658 * @param propertyConfiguration configuration of the property to parse 659 * @param propertyIndex index to use in the role to be able to use several times a property with the same class in a content-type. 660 * @return the role of the parsed property component 661 * @throws ConfigurationException if an error occurs while the property is parsed 662 */ 663 @SuppressWarnings("unchecked") 664 protected String _parseProperty(Configuration propertyConfiguration, int propertyIndex) throws ConfigurationException 665 { 666 String className = propertyConfiguration.getAttribute("class", null); 667 String path = propertyConfiguration.getAttribute("path", null); 668 if (className == null && path == null) 669 { 670 throw new ConfigurationException("A property defined in content type '" + this.getId() + "' does not specifiy a class not attribute path.", propertyConfiguration); 671 } 672 673 Class<? extends Property> propertyClass; 674 String propertyRole; 675 if (className != null) 676 { 677 try 678 { 679 propertyClass = (Class<? extends Property>) Class.forName(className); 680 propertyRole = getId() + "-" + className + "-" + propertyIndex; 681 } 682 catch (Exception e) 683 { 684 throw new ConfigurationException("Unable to instanciate property for class: " + className, propertyConfiguration, e); 685 } 686 } 687 else 688 { 689 propertyClass = ElementRefProperty.class; 690 propertyRole = getId() + "-" + path + "-" + propertyIndex; 691 } 692 693 _propertiesManager.addComponent(_pluginName, null, propertyRole, propertyClass, propertyConfiguration); 694 return propertyRole; 695 } 696 697 /** 698 * Check if all model item types defined in first model item are equals to those defined in second one 699 * @param item1 The first item to compare 700 * @param item2 The second item to compare 701 * @throws ConfigurationException if the types are not equals 702 */ 703 protected void _checkModelItemsTypeAndCardinality (ModelItem item1, ModelItem item2) throws ConfigurationException 704 { 705 if (item1 instanceof ElementDefinition elementDefinition1) 706 { 707 final String item1TypeId = elementDefinition1.getType().getId(); 708 final String item2TypeId = item2.getType().getId(); 709 if (!(item2 instanceof ElementDefinition elementDefinition2) || !item1TypeId.equals(item2TypeId)) 710 { 711 throw new ConfigurationException("The type of model item '" + elementDefinition1.getPath() + "' (" + item1TypeId + ") defined in content type '" + _id + "', can not be overridden to type: " + item2TypeId); 712 } 713 else 714 { 715 final boolean item1Cardinality = elementDefinition1.isMultiple(); 716 final boolean item2Cardinality = elementDefinition2.isMultiple(); 717 718 if (item1Cardinality != item2Cardinality) 719 { 720 throw new ConfigurationException("The cardinality of model item '" + elementDefinition1.getPath() + "' (" + (item1Cardinality ? "multiple" : "not multiple") + ") defined in content type '" + _id + "', can not be overridden to cardinality: " + (item2Cardinality ? "multiple" : "not multiple")); 721 } 722 } 723 } 724 else 725 { 726 if (!(item2 instanceof ModelItemGroup)) 727 { 728 throw new ConfigurationException("The item group '" + item1 + "' can not be overriden by the non item group '" + item2 + "' in content type '" + item2.getModel() + "'"); 729 } 730 731 ModelItemGroup group1 = (ModelItemGroup) item1; 732 ModelItemGroup group2 = (ModelItemGroup) item2; 733 734 for (ModelItem subItemfromGroup1 : group1.getChildren()) 735 { 736 ModelItem subItemFromGroup2 = group2.getChild(subItemfromGroup1.getName()); 737 if (subItemFromGroup2 != null) 738 { 739 _checkModelItemsTypeAndCardinality(subItemfromGroup1, subItemFromGroup2); 740 _checkContentTypeSimplicity(subItemfromGroup1); 741 } 742 } 743 } 744 } 745 746 /** 747 * Checks the given model item to determine if this content type is multilingual and/or simple 748 * All items of a simple content-type have to be a simple type (string, long, date, ..) 749 * A multilingual content type should contain at least one model item of type MULTILINGUAL-STRING 750 * @param modelItem The model item to check 751 */ 752 protected void _checkContentTypeSimplicity(ModelItem modelItem) 753 { 754 if (modelItem instanceof ModelItemGroup) 755 { 756 // If the content type contains groups, it is not simple 757 _isSimple = false; 758 } 759 else if (modelItem instanceof ElementDefinition elementDefinition) 760 { 761 ElementType type = elementDefinition.getType(); 762 if (!type.isSimple()) 763 { 764 // If there is a no simple attribute, the content type is not simple 765 _isSimple = false; 766 } 767 768 if (ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID.equals(type.getId())) 769 { 770 // If there is a multilingual-string attribute, the content type is multilingual 771 _isMultilingual = true; 772 } 773 } 774 } 775 776 /** 777 * Parse DublinCore attributes 778 * @throws ConfigurationException if the configuration is invalid 779 */ 780 @SuppressWarnings("static-access") 781 protected void _parseDublinCoreAttributes() throws ConfigurationException 782 { 783 Source src = null; 784 785 try 786 { 787 src = _srcResolver.resolveURI("resource://org/ametys/cms/dublincore/dublincore.xml"); 788 789 if (src.exists()) 790 { 791 Configuration configuration = null; 792 try (InputStream is = src.getInputStream()) 793 { 794 configuration = new DefaultConfigurationBuilder(true).build(is); 795 } 796 797 ContentRestrictedCompositeDefinition definition = new ContentRestrictedCompositeDefinition(); 798 definition.setModel(this); 799 definition.setName("dc"); 800 definition.setLabel(new I18nizableText("plugin.cms", "PLUGINS_CMS_DUBLINCORE_LABEL")); 801 definition.setDescription(new I18nizableText("plugin.cms", "PLUGINS_CMS_DUBLINCORE_DESC")); 802 definition.setType(_contentAttributeTypeExtensionPoint.getExtension(ModelItemTypeConstants.COMPOSITE_TYPE_ID)); 803 804 for (Configuration childConfiguration : configuration.getChildren()) 805 { 806 String childName = childConfiguration.getName(); 807 808 if (childName.equals("metadata")) 809 { 810 // TODO TAC, Ã faire ? 811 _dublinCoreAttributeDefinitionParser.parse(_manager, _pluginName, childConfiguration, this, definition); 812 } 813 } 814 815 _modelItems.put("dc", definition); 816 } 817 } 818 catch (IOException | SAXException e) 819 { 820 throw new ConfigurationException("Unable to parse Dublin Core metadata", e); 821 } 822 finally 823 { 824 if (src != null) 825 { 826 _srcResolver.release(src); 827 } 828 } 829 } 830 831 /** 832 * Execute the lookup on all properties 833 * @throws ConfigurationException if the configuration is invalid. 834 */ 835 @SuppressWarnings("unchecked") 836 protected void _lookupProperties() throws ConfigurationException 837 { 838 try 839 { 840 _propertiesManager.initialize(); 841 } 842 catch (Exception e) 843 { 844 throw new ConfigurationException("Unable to initialize properties manager", e); 845 } 846 847 for (String propertyRole : _propertyRoles.keySet()) 848 { 849 try 850 { 851 Property property = _propertiesManager.lookup(propertyRole); 852 String propertyName = property.getName(); 853 Optional<ModelItemGroup> optParent = _propertyRoles.get(propertyRole); 854 855 property.setModel(this); 856 property.setAvailableTypeExtensionPoint(_contentPropertyTypeExtensionPoint); 857 858 // If the property's container already has an item with the same name, check item types 859 ModelItemContainer propertyContainer = optParent.map(ModelItemContainer.class::cast) 860 .orElse(this); 861 if (propertyContainer.hasModelItem(propertyName)) 862 { 863 ModelItem item = propertyContainer.getModelItem(propertyName); 864 _checkModelItemsTypeAndCardinality(item, property); 865 } 866 867 _checkContentTypeSimplicity(property); 868 869 // Insert the property at the right place in model items hierarchy 870 optParent.ifPresentOrElse(parent -> parent.addChild(property), 871 () -> _modelItems.put(property.getName(), property)); 872 } 873 catch (ComponentException e) 874 { 875 throw new ConfigurationException("Unable to lookup property with role: '" + propertyRole + "' for content type: " + this.getId(), e); 876 } 877 } 878 } 879 880 /** 881 * Configure the default workflow name from the XML configuration. 882 * - From the overriden configuration 883 * - If not, from the current configuration 884 * - If not, from the supertypes 885 * - If it cannot be determined and the content type is a reference table, then "reference-table" 886 * - Otherwise "content" 887 * @param mainConfig The configuration 888 * @throws ConfigurationException if an exception occurs 889 */ 890 protected void _configureDefaultWorkflowName(Configuration mainConfig) throws ConfigurationException 891 { 892 _configuredWorkflowNames = getOverrideConfigurationFomFolder() 893 .map(ConfigurationAndPluginName::configuration) 894 // Override mode 895 .map(oc -> oc.getChild("default-workflow").getValue(null)) 896 // Normal mode 897 .or(() -> Optional.ofNullable(mainConfig.getChild("default-workflow").getValue(null))) 898 // Transform it in a singleton set 899 .map(Set::of) 900 // Inherited mode 901 .orElseGet(this::_getDefaultWorkflowNamesFromSupertypes); 902 903 _defaultWorkflowName = _configuredWorkflowNames.size() > 1 904 // Several workflow names returns undefined 905 ? Optional.empty() 906 : _configuredWorkflowNames.stream() 907 // One workflow name is directly returned 908 .findFirst() 909 // Otherwise default workflow : reference-table or content 910 .or(() -> Optional.of(isReferenceTable() ? "reference-table" : "content")); 911 } 912 913 private Set<String> _getDefaultWorkflowNamesFromSupertypes() 914 { 915 Set<String> defaultWorkflowNames = new HashSet<>(); 916 917 // Get tags from super types 918 for (String superTypeId : _superTypeIds) 919 { 920 ContentType superType = _cTypeEP.getExtension(superTypeId); 921 defaultWorkflowNames.addAll(superType.getConfiguredDefaultWorkflowNames()); 922 } 923 924 if (defaultWorkflowNames.size() > 1) 925 { 926 getLogger().warn("Several default workflows are defined for content type '{}' : {}.", _id, StringUtils.join(defaultWorkflowNames)); 927 } 928 929 return defaultWorkflowNames; 930 } 931 932 /** 933 * Configures the "parent" content type. 934 * This must not be confounded with the super types. (See {@link AbstractContentTypeDescriptor#_configureSuperTypes(Configuration)}) 935 * @param mainConfig The main configuration 936 * @throws ConfigurationException if an error occurred 937 */ 938 protected void _configureParentContentType(Configuration mainConfig) throws ConfigurationException 939 { 940 Configuration parentCTypeConf = mainConfig.getChild("parent-ref", false); 941 _parentAttributeDefinition = null; 942 943 if (parentCTypeConf != null) 944 { 945 // Check this content type is a reference table 946 if (!isReferenceTable()) 947 { 948 getLogger().error("The 'parent-ref' tag for content type '{}' is defined but this feature is only enabled for reference table content types. It will be ignored.", getId()); 949 return; 950 } 951 952 String refAttributeName = parentCTypeConf.getAttribute("name"); 953 // Check valid reference of metadata 954 if (!_modelItems.containsKey(refAttributeName)) 955 { 956 getLogger().error("The 'parent-ref' tag for content type '{}' references the attribute '{}' but it does not exist. It will be ignored.", getId(), refAttributeName); 957 return; 958 } 959 960 ModelItem modelItem = _modelItems.get(refAttributeName); 961 // Check metadata of type "content" 962 if (!(modelItem instanceof ContentAttributeDefinition)) 963 { 964 getLogger().error("The 'parent-ref' tag for content type '{}' references the attribute '{}' but it is not an attribute of type CONTENT. It will be ignored.", getId(), refAttributeName); 965 return; 966 } 967 968 ContentAttributeDefinition contentAttributeDefinition = (ContentAttributeDefinition) modelItem; 969 if (contentAttributeDefinition.isMultiple()) 970 { 971 getLogger().error("The 'parent-ref' tag for content type '{}' references the attribute '{}' but it is a multiple attribute. It will be ignored.", getId(), refAttributeName); 972 return; 973 } 974 975 String parentCTypeName = contentAttributeDefinition.getContentTypeId(); 976 ContentType parentCType = _cTypeEP.getExtension(parentCTypeName); 977 978 if (parentCType == null) 979 { 980 getLogger().error("The 'parent-ref' tag for content type '{}' references the attribute '{}' with content-type '{}' but it does not exist or is not yet initialized. It will be ignored.", getId(), refAttributeName, parentCTypeName); 981 return; 982 } 983 // Check parent content type is private AND simple 984 else if (!parentCType.isPrivate()) 985 { 986 getLogger().error("The 'parent-ref' tag for content type '{}' references the attribute '{}' with content-type '{}' but it is not private. It will be ignored.", getId(), refAttributeName, parentCTypeName); 987 return; 988 } 989 else if (!parentCType.isReferenceTable()) 990 { 991 getLogger().error("The 'parent-ref' tag for content type '{}' references the attribute '{}' with content-type '{}' but it is not a reference table. It will be ignored.", getId(), refAttributeName, parentCTypeName); 992 return; 993 } 994 995 if (!_hierarchicalSimpleContentsHelper.registerRelation(parentCType, this)) 996 { 997 getLogger().error("The content type '{}' defines a parent hierarchy which is not valid. See previous logs to know more.\nThis error can lead to UI issues.", getId()); 998 return; 999 } 1000 _parentAttributeDefinition = contentAttributeDefinition; 1001 } 1002 } 1003 1004 /** 1005 * Configure the content type view configurations 1006 * @param mainConfig The content type configuration 1007 * @throws ConfigurationException if an error occurred 1008 */ 1009 protected void _configureViewConfigurations(Configuration mainConfig) throws ConfigurationException 1010 { 1011 // Get applicable views configurations 1012 1013 // 1., get the views configured in the content type extension 1014 ConfigurationAndPluginName mainConfigurationAndPluginName = new ConfigurationAndPluginName(mainConfig, _pluginName); 1015 Map<String, ViewConfigurations> viewConfigurationsFromExtensionWithLegacySyntax = _getApplicableViewConfigurations(mainConfigurationAndPluginName, VIEW_TAG_NAME_WITH_LEGACY_SYNTAX); 1016 Map<String, ViewConfigurations> viewConfigurationsFromExtensionWithClassicSyntax = _getApplicableViewConfigurations(mainConfigurationAndPluginName, VIEW_TAG_NAME); 1017 Map<String, ViewConfigurations> viewConfigurationsFromExtension = mergeViewConfigurationsWithLegacyAndClassicSyntaxes(viewConfigurationsFromExtensionWithLegacySyntax, viewConfigurationsFromExtensionWithClassicSyntax); 1018 1019 // 2. get the views configured in the content type overrides extensions 1020 List<ConfigurationAndPluginName> overrideConfigurationsFromExtensions = getOverrideConfigurationsFromExtensions(); 1021 Map<String, ViewConfigurations> viewConfigurationsFromOverrideExtensionWithLegacySyntax = _getApplicableViewConfigurations(overrideConfigurationsFromExtensions, VIEW_TAG_NAME_WITH_LEGACY_SYNTAX); 1022 Map<String, ViewConfigurations> viewConfigurationsFromOverrideExtensionWithClassicSyntax = _getApplicableViewConfigurations(overrideConfigurationsFromExtensions, VIEW_TAG_NAME); 1023 Map<String, ViewConfigurations> viewConfigurationsFromOverrideExtension = mergeViewConfigurationsWithLegacyAndClassicSyntaxes(viewConfigurationsFromOverrideExtensionWithLegacySyntax, viewConfigurationsFromOverrideExtensionWithClassicSyntax); 1024 1025 // Merge view configurations from extension and overrides extensions. 1026 Map<String, ViewConfigurations> viewConfigurationsFromAllExtensions = mergeViewConfigurationsWithOverrides(viewConfigurationsFromExtension, viewConfigurationsFromOverrideExtension); 1027 1028 // 3. get the views configured in the _override folder 1029 Optional<ConfigurationAndPluginName> optOverrideConfigurationFomFolder = getOverrideConfigurationFomFolder(); 1030 Map<String, ViewConfigurations> viewConfigurationsFromOverrideFolder = Map.of(); 1031 if (optOverrideConfigurationFomFolder.isPresent()) 1032 { 1033 ConfigurationAndPluginName overrideConfigurationFomFolder = optOverrideConfigurationFomFolder.get(); 1034 Map<String, ViewConfigurations> viewConfigurationsFromOverrideFolderWithLegacySyntax = _getApplicableViewConfigurations(overrideConfigurationFomFolder, VIEW_TAG_NAME_WITH_LEGACY_SYNTAX); 1035 Map<String, ViewConfigurations> viewConfigurationsFromOverrideFolderWithClassicSyntax = _getApplicableViewConfigurations(overrideConfigurationFomFolder, VIEW_TAG_NAME); 1036 viewConfigurationsFromOverrideFolder = mergeViewConfigurationsWithLegacyAndClassicSyntaxes(viewConfigurationsFromOverrideFolderWithLegacySyntax, viewConfigurationsFromOverrideFolderWithClassicSyntax); 1037 } 1038 1039 // Merge view configurations from extensions and override folder. 1040 _viewConfigurations = mergeViewConfigurationsWithOverrides(viewConfigurationsFromAllExtensions, viewConfigurationsFromOverrideFolder); 1041 } 1042 1043 /** 1044 * Merge view configurations with legacy and classic syntax. 1045 * Keep the main configuration from the classic syntax if there is one. Concatenate all override configurations 1046 * @param viewConfigurationsWithLegacySyntax the view configurations with legacy syntax 1047 * @param viewConfigurationsWithClassicSyntax the view configurations with legacy syntax 1048 * @return the merged view configurations 1049 */ 1050 protected Map<String, ViewConfigurations> mergeViewConfigurationsWithLegacyAndClassicSyntaxes(Map<String, ViewConfigurations> viewConfigurationsWithLegacySyntax, Map<String, ViewConfigurations> viewConfigurationsWithClassicSyntax) 1051 { 1052 Map<String, ViewConfigurations> viewConfigurations = viewConfigurationsWithLegacySyntax; 1053 for (String viewName : viewConfigurationsWithClassicSyntax.keySet()) 1054 { 1055 if (viewConfigurations.containsKey(viewName)) 1056 { 1057 ViewConfigurations currentViewConfigurationsWithClassicSyntax = viewConfigurationsWithClassicSyntax.get(viewName); 1058 1059 // Keep the main configuration from the classic syntax if there is one 1060 Optional<ConfigurationAndPluginName> mainConfiguration = currentViewConfigurationsWithClassicSyntax.mainConfiguration() 1061 .or(() -> viewConfigurations.get(viewName).mainConfiguration()); 1062 1063 // Concatenate all override configurations 1064 List<ConfigurationAndPluginName> allOverrides = viewConfigurations.get(viewName).overrides(); 1065 allOverrides.addAll(currentViewConfigurationsWithClassicSyntax.overrides()); 1066 1067 viewConfigurations.put(viewName, new ViewConfigurations(viewName, mainConfiguration, allOverrides)); 1068 } 1069 else 1070 { 1071 // Add the nonexistent view configurations 1072 viewConfigurations.put(viewName, viewConfigurationsWithClassicSyntax.get(viewName)); 1073 } 1074 } 1075 1076 return viewConfigurations; 1077 } 1078 1079 /** 1080 * Merge view configurations with overrides 1081 * If there is a main configuration in overrides, do not keep configurations (main or overrides) of the original view configurations. Otherwise, concatenate all override configurations 1082 * @param viewConfigurations the original view configurations 1083 * @param viewOverrideConfigurations the override view configurations 1084 * @return @return the merged view configurations 1085 */ 1086 protected Map<String, ViewConfigurations> mergeViewConfigurationsWithOverrides(Map<String, ViewConfigurations> viewConfigurations, Map<String, ViewConfigurations> viewOverrideConfigurations) 1087 { 1088 Map<String, ViewConfigurations> mergedViewConfigurations = viewConfigurations; 1089 for (String viewName : viewOverrideConfigurations.keySet()) 1090 { 1091 if (mergedViewConfigurations.containsKey(viewName)) 1092 { 1093 ViewConfigurations currentViewOverrideConfigurations = viewOverrideConfigurations.get(viewName); 1094 if (currentViewOverrideConfigurations.mainConfiguration().isPresent()) 1095 { 1096 // If there is a main configuration in overrides, do not keep configurations (main or overrides) of the original view configurations 1097 mergedViewConfigurations.put(viewName, viewOverrideConfigurations.get(viewName)); 1098 } 1099 else 1100 { 1101 // Otherwise, concatenate all override configurations 1102 mergedViewConfigurations.get(viewName).overrides().addAll(currentViewOverrideConfigurations.overrides()); 1103 } 1104 } 1105 else 1106 { 1107 // Add the nonexistent view configurations 1108 mergedViewConfigurations.put(viewName, viewOverrideConfigurations.get(viewName)); 1109 } 1110 } 1111 1112 return mergedViewConfigurations; 1113 } 1114 1115 /** 1116 * Compute the applicable views from their configurations. (from "_override" folder and from extensions) 1117 * @param configurationAndPluginNames The content type configuration 1118 * @param viewTagName The name of the tag containing the view 1119 * @return the applicable views, indexed by their names. For each view, indicates if the view is configured 1120 * @throws ConfigurationException if the configuration is invalid 1121 */ 1122 protected Map<String, ViewConfigurations> _getApplicableViewConfigurations(List<ConfigurationAndPluginName> configurationAndPluginNames, String viewTagName) throws ConfigurationException 1123 { 1124 Map<String, ViewConfigurations> allApplicableViewConfigurations = new LinkedHashMap<>(); 1125 1126 for (ConfigurationAndPluginName configurationAndPluginName : configurationAndPluginNames) 1127 { 1128 Map<String, ViewConfigurations> applicableViewConfigurations = _getApplicableViewConfigurations(configurationAndPluginName, viewTagName); 1129 1130 for (String viewName : applicableViewConfigurations.keySet()) 1131 { 1132 if (allApplicableViewConfigurations.containsKey(viewName)) 1133 { 1134 // Concatenate all override configurations 1135 allApplicableViewConfigurations.get(viewName).overrides().addAll(applicableViewConfigurations.get(viewName).overrides()); 1136 } 1137 else 1138 { 1139 // Add the nonexistent view configurations 1140 allApplicableViewConfigurations.put(viewName, applicableViewConfigurations.get(viewName)); 1141 } 1142 } 1143 } 1144 1145 return allApplicableViewConfigurations; 1146 } 1147 1148 /** 1149 * Compute the applicable views from their configurations. 1150 * @param configurationAndPluginName The content type configuration 1151 * @param viewTagName The name of the tag containing the view 1152 * @return the applicable views, indexed by their names. For each view, indicates if the view is configured 1153 * @throws ConfigurationException if the configuration is invalid 1154 */ 1155 protected Map<String, ViewConfigurations> _getApplicableViewConfigurations(ConfigurationAndPluginName configurationAndPluginName, String viewTagName) throws ConfigurationException 1156 { 1157 Map<String, ViewConfigurations> applicableViewConfigurations = new LinkedHashMap<>(); 1158 1159 for (Configuration viewConfiguration : configurationAndPluginName.configuration().getChildren(viewTagName)) 1160 { 1161 String viewName = viewConfiguration.getAttribute("name"); 1162 ViewConfigurations viewConfigurations = applicableViewConfigurations.computeIfAbsent(viewName, configs -> new ViewConfigurations(viewName, Optional.empty(), new ArrayList<>())); 1163 1164 if (viewConfiguration.getAttributeAsBoolean("override", false)) 1165 { 1166 viewConfigurations.overrides().add(new ConfigurationAndPluginName(viewConfiguration, configurationAndPluginName.pluginName())); 1167 } 1168 else 1169 { 1170 if (viewConfigurations.mainConfiguration().isPresent()) 1171 { 1172 // There is already a main configuration for this view in the same configuration file 1173 throw new ConfigurationException("The view named '" + viewName + "' is defined twice in the content type '" + this.getId() + "'.", configurationAndPluginName.configuration()); 1174 } 1175 else 1176 { 1177 applicableViewConfigurations.put(viewName, new ViewConfigurations(viewName, Optional.of(new ConfigurationAndPluginName(viewConfiguration, configurationAndPluginName.pluginName())), viewConfigurations.overrides())); 1178 } 1179 } 1180 } 1181 1182 return applicableViewConfigurations; 1183 } 1184 1185 /** 1186 * Configure the global validators for content type 1187 * @param config The content type configuration 1188 * @throws ConfigurationException if an error occurs 1189 */ 1190 protected void _configureGlobalValidators (Configuration config) throws ConfigurationException 1191 { 1192 _globalValidators = new ArrayList<>(); 1193 List<String> globalValidatorsToLookup = new ArrayList<>(); 1194 1195 Configuration globalValidatorsConfig = config.getChild("global-validators", true); 1196 globalValidatorsToLookup.addAll(_parseGlobalValidators(new ConfigurationAndPluginName(globalValidatorsConfig, _pluginName), config.getAttributeAsBoolean("include-from-supertype", true))); 1197 1198 List<ConfigurationAndPluginName> overrideConfigurationAndPluginNames = getOverrideConfigurations(); 1199 // Global validators into an overriden configuration are added to the original global validators 1200 for (ConfigurationAndPluginName overrideConfigurationAndPluginName : overrideConfigurationAndPluginNames) 1201 { 1202 Configuration overrideConfiguration = overrideConfigurationAndPluginName.configuration().getChild("global-validators", true); 1203 globalValidatorsToLookup.addAll(_parseGlobalValidators(new ConfigurationAndPluginName(overrideConfiguration, overrideConfigurationAndPluginName.pluginName()), false)); 1204 } 1205 1206 try 1207 { 1208 _globalValidatorsManager.initialize(); 1209 } 1210 catch (Exception e) 1211 { 1212 throw new ConfigurationException("Unable to initialize global validator manager", e); 1213 } 1214 1215 for (String validatorRole : globalValidatorsToLookup) 1216 { 1217 try 1218 { 1219 ContentValidator contentValidator = _globalValidatorsManager.lookup(validatorRole); 1220 contentValidator.setContentType(this); 1221 1222 _globalValidators.add(contentValidator); 1223 } 1224 catch (ComponentException e) 1225 { 1226 throw new ConfigurationException("Unable to lookup global validator role: '" + validatorRole + "' for content type: " + this.getId(), e); 1227 } 1228 } 1229 1230 } 1231 1232 /** 1233 * Parse the global validators 1234 * @param configurationAndPluginName the configuration 1235 * @param includeSuperTypeValidators true to include validators of super types 1236 * @return the role of global validators to be lookuped 1237 * @throws ConfigurationException if configuration is incorrect 1238 */ 1239 @SuppressWarnings("unchecked") 1240 protected List<String> _parseGlobalValidators(ConfigurationAndPluginName configurationAndPluginName, boolean includeSuperTypeValidators) throws ConfigurationException 1241 { 1242 List<String> gvRoles = new ArrayList<>(); 1243 1244 if (includeSuperTypeValidators) 1245 { 1246 for (String superTypeId : _superTypeIds) 1247 { 1248 ContentType cType = _cTypeEP.getExtension(superTypeId); 1249 _globalValidators.addAll(cType.getGlobalValidators()); 1250 } 1251 } 1252 1253 for (Configuration globalValidatorConfig : configurationAndPluginName.configuration().getChildren("global-validator")) 1254 { 1255 String globalValidatorId = __GLOBAL_VALIDATOR_ROLE_PREFIX + RandomStringUtils.randomAlphanumeric(10); 1256 String validatorClassName = globalValidatorConfig.getAttribute("class"); 1257 1258 try 1259 { 1260 Class validatorClass = Class.forName(validatorClassName); 1261 _globalValidatorsManager.addComponent(configurationAndPluginName.pluginName(), null, globalValidatorId, validatorClass, globalValidatorConfig); 1262 } 1263 catch (Exception e) 1264 { 1265 throw new ConfigurationException("Unable to instantiate global validator for class: " + validatorClassName, e); 1266 } 1267 1268 gvRoles.add(globalValidatorId); 1269 } 1270 1271 return gvRoles; 1272 } 1273 1274 /** 1275 * Parse the tags and add it to tags list 1276 * @param configuration the configuration to use 1277 * @throws ConfigurationException if the configuration is not valid. 1278 */ 1279 protected void _configureLocalTags(Configuration configuration) throws ConfigurationException 1280 { 1281 Configuration[] children = configuration.getChildren("tag"); 1282 for (Configuration tagConfig : children) 1283 { 1284 String tagValue = tagConfig.getValue(); 1285 if (tagConfig.getAttributeAsBoolean("inheritable", true)) 1286 { 1287 _inheritableTags.add(tagValue); 1288 } 1289 _tags.add(tagValue); 1290 } 1291 } 1292 1293 public void initializeAfterModelItemsInitialization() throws Exception 1294 { 1295 _checkTitleAttribute(); 1296 _checkContentAttributes(this, 0); 1297 1298 _computePropertiesReferences(this); 1299 1300 _parseViews(); 1301 } 1302 1303 @SuppressWarnings("static-access") 1304 private void _checkTitleAttribute() throws ConfigurationException 1305 { 1306 if (!isAbstract() && !isMixin()) 1307 { 1308 ModelItem titleItem = null; 1309 if (hasModelItem(Content.ATTRIBUTE_TITLE)) 1310 { 1311 titleItem = getModelItem(Content.ATTRIBUTE_TITLE); 1312 } 1313 else 1314 { 1315 // The title attribute is mandatory for non abstract content types 1316 throw new ConfigurationException("An attribute 'title' in content type '" + getId() + "' should be defined."); 1317 } 1318 1319 // The title attribute should'nt be a group item 1320 if (!(titleItem instanceof ElementDefinition)) 1321 { 1322 throw new ConfigurationException("An attribute 'title' in content type '" + getId() + "' should be defined."); 1323 } 1324 1325 // The title attribute should be a string or a multilingual string 1326 ElementDefinition titleAttribute = (ElementDefinition) titleItem; 1327 String typeId = titleAttribute.getType().getId(); 1328 if (!ModelItemTypeConstants.STRING_TYPE_ID.equals(typeId) && !ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID.equals(typeId)) 1329 { 1330 throw new ConfigurationException("The attribute 'title' in content type '" + getId() + "' is defined with the type '" + typeId + "'. Only '" + ModelItemTypeConstants.STRING_TYPE_ID + "' and '" + ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID + "' are allowed"); 1331 } 1332 1333 // The title attribute should not be multiple 1334 if (titleAttribute.isMultiple()) 1335 { 1336 throw new ConfigurationException("The attribute 'title' in content type '" + getId() + "' should not be multiple."); 1337 } 1338 1339 // The title attribute must be mandatory 1340 Validator titleValidator = titleAttribute.getValidator(); 1341 if (titleValidator == null || !(boolean) titleValidator.getConfiguration().get("mandatory")) 1342 { 1343 throw new ConfigurationException("The attribute 'title' in content type '" + getId() + "' should be mandatory."); 1344 } 1345 } 1346 } 1347 1348 /** 1349 * Check for each content attribute: the content type id, the mutual references and the default values 1350 * @param modelItemContainer the {@link ModelItemContainer} to check 1351 * @param repeaterLevel the current nested level of repeaters, 0 if the current attribute isn't in a repeater. 1352 * @throws ConfigurationException if a content attribute has an invalid configuration 1353 */ 1354 protected void _checkContentAttributes(ModelItemContainer modelItemContainer, int repeaterLevel) throws ConfigurationException 1355 { 1356 for (ModelItem modelItem : modelItemContainer.getModelItems()) 1357 { 1358 if (modelItem instanceof ContentAttributeDefinition definition) 1359 { 1360 _checkContentTypeId(definition); 1361 _checkMutualReferences(definition, repeaterLevel); 1362 definition.checkDefaultValue(); 1363 } 1364 1365 // Check sub-attributes 1366 if (modelItem instanceof ModelItemContainer) 1367 { 1368 // Increment the repeater level if the current attribute is a repeater. 1369 int newRepeaterLevel = (modelItem instanceof org.ametys.plugins.repository.model.RepeaterDefinition) ? repeaterLevel + 1 : repeaterLevel; 1370 _checkContentAttributes((ModelItemContainer) modelItem, newRepeaterLevel); 1371 } 1372 } 1373 } 1374 1375 /** 1376 * Check the content type id of the given content attribute definition 1377 * @param definition the definition to check 1378 * @throws ConfigurationException if the given content attribute references an invalid or non-existing content type. 1379 */ 1380 protected void _checkContentTypeId(ContentAttributeDefinition definition) throws ConfigurationException 1381 { 1382 String contentTypeId = definition.getContentTypeId(); 1383 1384 if (StringUtils.isNotBlank(contentTypeId) && !_cTypeEP.hasExtension(contentTypeId)) 1385 { 1386 throw new ConfigurationException("The content attribute of path " + definition.getPath() + " in content type " + getId() + " references a nonexistent content-type: '" + contentTypeId + "'"); 1387 } 1388 } 1389 1390 /** 1391 * Check the mutual reference declaration of the given content attribute definition 1392 * @param definition the definition to check 1393 * @param repeaterLevel the current nested level of repeaters, 0 if the current attribute isn't in a repeater. 1394 * @throws ConfigurationException if there is a problem with mutual reference declaration 1395 */ 1396 protected void _checkMutualReferences(ContentAttributeDefinition definition, int repeaterLevel) throws ConfigurationException 1397 { 1398 String contentTypeId = definition.getContentTypeId(); 1399 String invertRelationPath = definition.getInvertRelationPath(); 1400 1401 if (StringUtils.isNotEmpty(invertRelationPath)) 1402 { 1403 String currentAttributePath = definition.getPath(); 1404 1405 if (StringUtils.isEmpty(contentTypeId)) 1406 { 1407 throw new ConfigurationException("The attribute at path '" + currentAttributePath + "' in content type '" + getId() + "' is declared as a mutual relationship, a content type is required."); 1408 } 1409 1410 // Do not check the extension existence, this is already done in _checkContentAttribute method 1411 ContentType contentType = _cTypeEP.getExtension(contentTypeId); 1412 1413 if (repeaterLevel > 0 && definition.isMultiple()) 1414 { 1415 throw new ConfigurationException("The attribute at path '" + currentAttributePath + "' in content type '" + getId() + "' is declared as a mutual relationship, it can't be multiple AND in a repeater."); 1416 } 1417 else if (repeaterLevel >= 2) 1418 { 1419 throw new ConfigurationException("The attribute at path '" + currentAttributePath + "' in content type '" + getId() + "' is declared as a mutual relationship, it can't be in two levels of repeaters."); 1420 } 1421 1422 try 1423 { 1424 ModelItem invertRelationDefinition = contentType.getModelItem(invertRelationPath); 1425 1426 // Ensure that the referenced attribute is of type content. 1427 if (!ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(invertRelationDefinition.getType().getId())) 1428 { 1429 throw new ConfigurationException("Mutual relationship: the attribute at path '" + invertRelationPath + "' of type " + contentTypeId + " is not of type Content."); 1430 } 1431 1432 String invertCTypeId = ((ContentAttributeDefinition) invertRelationDefinition).getContentTypeId(); 1433 String invertPath = ((ContentAttributeDefinition) invertRelationDefinition).getInvertRelationPath(); 1434 1435 // Ensure that the referenced attribute's content type is compatible with the current type. 1436 if (!_cTypeEP.isSameOrDescendant(getId(), invertCTypeId)) 1437 { 1438 throw new ConfigurationException("Mutual relationship: the attribute at path " + invertRelationPath + " of type " + contentTypeId + " references an incompatible type: " + StringUtils.defaultString(invertCTypeId, "<null>")); 1439 } 1440 1441 // Ensure that the referenced attribute references this attribute. 1442 if (!currentAttributePath.equals(invertPath)) 1443 { 1444 throw new ConfigurationException("Mutual relationship: the attribute at path " + currentAttributePath + " of type " + getId() + " references the attribute '" + invertRelationPath + "' of type " + contentTypeId + " but the latter does not reference it back."); 1445 } 1446 } 1447 catch (UndefinedItemPathException e) 1448 { 1449 // Ensure the referenced attribute presence. 1450 throw new ConfigurationException("Mutual relationship: the attribute at path '" + invertRelationPath + "' doesn't exist for type " + contentTypeId); 1451 } 1452 } 1453 } 1454 1455 /** 1456 * Parses the content type views 1457 * @throws ConfigurationException if an error occurred 1458 */ 1459 protected void _parseViews() throws ConfigurationException 1460 { 1461 Map<String, ViewConfigurationsByType> allViewConfigurations = _mergeViewConfigurationsFromCurrentTypeAndSuperTypes(); 1462 for (String viewName : allViewConfigurations.keySet()) 1463 { 1464 ViewConfigurationsByType viewConfigurationsByType = allViewConfigurations.get(viewName); 1465 View view = _contentTypesParserHelper.parseView(this, viewConfigurationsByType); 1466 _views.put(viewName, view); 1467 } 1468 1469 // Check that mandatory views aren't missing 1470 if (!_abstract && !hasTag(TAG_MIXIN)) 1471 { 1472 if (!_views.containsKey("details")) 1473 { 1474 throw new ConfigurationException("Mandatory view named 'details' is missing for content type " + _id); 1475 } 1476 if (!_views.containsKey("main")) 1477 { 1478 throw new ConfigurationException("Mandatory view named 'main' is missing for content type " + _id); 1479 } 1480 } 1481 } 1482 1483 /** 1484 * Retrieves the merge of the view configurations between the current content type and its super types 1485 * @return the merge of the view configurations 1486 * @throws ConfigurationException if an error occurs 1487 */ 1488 protected Map<String, ViewConfigurationsByType> _mergeViewConfigurationsFromCurrentTypeAndSuperTypes() throws ConfigurationException 1489 { 1490 Map<String, ViewConfigurationsByType> applicableViewConfigurationsByType = new HashMap<>(); 1491 1492 for (String viewName : _viewConfigurations.keySet()) 1493 { 1494 ViewConfigurations currentTypeViewConfigurations = _viewConfigurations.get(viewName); 1495 Map<ContentType, ConfigurationAndPluginName> mainConfiguration = currentTypeViewConfigurations.mainConfiguration().isPresent() 1496 ? Map.of(this, currentTypeViewConfigurations.mainConfiguration().get()) 1497 : Map.of(); 1498 Map<ContentType, List<ConfigurationAndPluginName>> overrides = !currentTypeViewConfigurations.overrides().isEmpty() 1499 ? Map.of(this, currentTypeViewConfigurations.overrides()) 1500 : Map.of(); 1501 ViewConfigurationsByType currentTypeViewConfigurationsByType = new ViewConfigurationsByType(viewName, mainConfiguration, overrides); 1502 applicableViewConfigurationsByType.put(viewName, currentTypeViewConfigurationsByType); 1503 } 1504 1505 Map<String, ViewConfigurationsByType> superTypesApplicableViewConfigurations = _contentTypesHelper.getViewConfigurations(_superTypeIds, new String[0]); 1506 for (String viewName : superTypesApplicableViewConfigurations.keySet()) 1507 { 1508 if (!_viewConfigurations.containsKey(viewName) || _viewConfigurations.get(viewName).mainConfiguration().isEmpty()) 1509 { 1510 ViewConfigurationsByType superTypeViewConfigurations = superTypesApplicableViewConfigurations.get(viewName); 1511 if (_viewConfigurations.containsKey(viewName)) 1512 { 1513 superTypeViewConfigurations.overrides() 1514 .put(this, _viewConfigurations.get(viewName).overrides()); 1515 } 1516 1517 applicableViewConfigurationsByType.put(viewName, superTypeViewConfigurations); 1518 } 1519 } 1520 1521 return applicableViewConfigurationsByType; 1522 } 1523 1524 @Override 1525 public void initializeAfterViewsInitialization() throws Exception 1526 { 1527 _resolveViewReferences(); 1528 } 1529 1530 /** 1531 * Resolve the temporary view references 1532 * @throws ConfigurationException if a view reference has a configuration error (the content attribute nesting the reference does not specify any content type, the view does not exist, ...) 1533 */ 1534 protected void _resolveViewReferences() throws ConfigurationException 1535 { 1536 for (String viewName : getViewNames()) 1537 { 1538 View view = getView(viewName); 1539 _resolveViewReferences(view, viewName); 1540 } 1541 } 1542 1543 /** 1544 * Resolve the temporary view references in the given {@link ViewItemAccessor} 1545 * @param viewItemAccessor the {@link ViewItemAccessor} 1546 * @param currentViewName the name of the current view (to avoid views referencing themselves) 1547 * @throws ConfigurationException if a view reference has a configuration error (the content attribute nesting the reference does not specify any content type, the view does not exist, ...) 1548 */ 1549 protected void _resolveViewReferences(ViewItemAccessor viewItemAccessor, String currentViewName) throws ConfigurationException 1550 { 1551 boolean hasResolvedReferences = false; 1552 List<ViewItem> viewItemsWithResolvedReferences = new ArrayList<>(); 1553 for (ViewItem viewItem : viewItemAccessor.getViewItems()) 1554 { 1555 if (viewItem instanceof TemporaryViewReference) 1556 { 1557 assert viewItemAccessor instanceof ViewElement; 1558 1559 ElementDefinition definition = ((ViewElement) viewItemAccessor).getDefinition(); 1560 assert definition instanceof ContentAttributeDefinition; 1561 1562 String contentTypeId = ((ContentAttributeDefinition) definition).getContentTypeId(); 1563 if (contentTypeId != null) 1564 { 1565 Set<String> ancestors = _contentTypesHelper.getAncestors(this.getId()); 1566 if (!(ancestors.contains(contentTypeId) && currentViewName.equals(viewItem.getName()))) 1567 { 1568 ContentType contentType = _cTypeEP.getExtension(contentTypeId); 1569 View view = contentType.getView(viewItem.getName()); 1570 if (view != null) 1571 { 1572 hasResolvedReferences = true; 1573 viewItemsWithResolvedReferences.addAll(view.getViewItems()); 1574 } 1575 else 1576 { 1577 throw new ConfigurationException("The view '" + viewItem.getName() + "' does not exist in content type '" + contentTypeId + "' referenced by the attribute named '" + definition.getName() + "'."); 1578 } 1579 } 1580 else 1581 { 1582 throw new ConfigurationException("The view '" + viewItem.getName() + "' cannot make a reference to itself."); 1583 } 1584 } 1585 else 1586 { 1587 throw new ConfigurationException("The attribute '" + definition.getName() + "' doesn't reference any content type. It is not possible to add the items of the view '" + viewItem.getName() + "'."); 1588 } 1589 } 1590 else 1591 { 1592 if (viewItem instanceof ViewItemAccessor) 1593 { 1594 _resolveViewReferences((ViewItemAccessor) viewItem, currentViewName); 1595 } 1596 viewItemsWithResolvedReferences.add(viewItem); 1597 } 1598 } 1599 1600 if (hasResolvedReferences) 1601 { 1602 viewItemAccessor.clear(); 1603 viewItemAccessor.addViewItems(viewItemsWithResolvedReferences); 1604 } 1605 } 1606 1607 /** 1608 * Browses the model items of the given {@link ModelItemContainer} and computes properties references. 1609 * @param modelItemContainer the model item container 1610 * @throws ConfigurationException if a property references a model item that does not exist 1611 */ 1612 protected void _computePropertiesReferences(ModelItemContainer modelItemContainer) throws ConfigurationException 1613 { 1614 for (ModelItem modelItem : modelItemContainer.getModelItems()) 1615 { 1616 if (modelItem instanceof ElementRefProperty elementRefProperty) 1617 { 1618 String itemPath = elementRefProperty.getElementPath(); 1619 1620 if (!hasModelItem(itemPath) || !(getModelItem(itemPath) instanceof ElementDefinition)) 1621 { 1622 throw new ConfigurationException("Property for path '" + itemPath + "' does not correspond to an attribute of this content type"); 1623 } 1624 1625 List<ModelItem> referencedModelItems = _contentTypesHelper.getModelItemPath(itemPath, this); 1626 1627 List<String> joinPaths = new ArrayList<>(); 1628 boolean localContentType = true; 1629 StringBuilder currentContentPath = new StringBuilder(); 1630 for (ModelItem referencedModelItem : referencedModelItems) 1631 { 1632 if (currentContentPath.length() > 0) 1633 { 1634 currentContentPath.append(ModelItem.ITEM_PATH_SEPARATOR); 1635 } 1636 currentContentPath.append(referencedModelItem.getName()); 1637 1638 if (ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(referencedModelItem.getType().getId())) 1639 { 1640 if (!localContentType) 1641 { 1642 joinPaths.add(currentContentPath.toString()); 1643 currentContentPath.setLength(0); 1644 1645 Map<String, List<String>> cTypeRefs = _propertiesReferences.computeIfAbsent(referencedModelItem.getModel().getId(), __ -> new LinkedHashMap<>()); 1646 cTypeRefs.put(elementRefProperty.getPath(), new ArrayList<>(joinPaths)); 1647 } 1648 1649 localContentType = false; 1650 } 1651 } 1652 } 1653 else if (modelItem instanceof ModelItemGroup group) 1654 { 1655 _computePropertiesReferences(group); 1656 } 1657 } 1658 } 1659 1660 @Override 1661 public List<ContentValidator> getGlobalValidators() 1662 { 1663 return Collections.unmodifiableList(_globalValidators); 1664 } 1665 1666 @Override 1667 public RichTextUpdater getRichTextUpdater() 1668 { 1669 return _richTextUpdater; 1670 } 1671 1672 @Override 1673 public Set<String> getTags() 1674 { 1675 return Collections.unmodifiableSet(_tags); 1676 } 1677 1678 @Override 1679 public Set<String> getInheritableTags() 1680 { 1681 return Collections.unmodifiableSet(_inheritableTags); 1682 } 1683 1684 @Override 1685 public boolean hasTag(String tagName) 1686 { 1687 return _tags.contains(tagName); 1688 } 1689 1690 @Override 1691 public boolean isPrivate() 1692 { 1693 return hasTag(TAG_PRIVATE); 1694 } 1695 1696 @Override 1697 public boolean isAbstract() 1698 { 1699 return _abstract; 1700 } 1701 1702 @Override 1703 public boolean isSimple() 1704 { 1705 return _isSimple; 1706 } 1707 1708 @Override 1709 public boolean isReferenceTable() 1710 { 1711 return hasTag(TAG_REFERENCE_TABLE) || hasTag(TAG_RENDERABLE_FERENCE_TABLE); 1712 } 1713 1714 @Override 1715 public boolean isMultilingual() 1716 { 1717 return _isMultilingual; 1718 } 1719 1720 @Override 1721 public boolean isMixin() 1722 { 1723 return hasTag(TAG_MIXIN); 1724 } 1725 1726 @Override 1727 public Set<String> getConfiguredDefaultWorkflowNames() 1728 { 1729 return _configuredWorkflowNames; 1730 } 1731 1732 @Override 1733 public Optional<String> getDefaultWorkflowName() 1734 { 1735 return _defaultWorkflowName; 1736 } 1737 1738 @Override 1739 public String getRight() 1740 { 1741 return _right; 1742 } 1743 1744 @Override 1745 public void saxContentTypeAdditionalData(org.xml.sax.ContentHandler contentHandler, Content content) throws AmetysRepositoryException, SAXException 1746 { 1747 // Nothing 1748 } 1749 1750 @Override 1751 public Map<String, Object> getAdditionalData(Content content) 1752 { 1753 return new HashMap<>(); 1754 } 1755 1756 @Override 1757 public String toString() 1758 { 1759 return "'" + getId() + "'"; 1760 } 1761 1762 /** 1763 * Restricted definition. 1764 * @deprecated use {@link RestrictedModelItem} instead 1765 */ 1766 @Deprecated 1767 protected interface RestrictedDefinition 1768 { 1769 /** 1770 * Provides the restrictions. 1771 * @return the restrictions. 1772 */ 1773 Restrictions getRestrictions(); 1774 } 1775 1776 /** 1777 * Definition with semantic annotations 1778 */ 1779 protected interface AnnotableDefinition 1780 { 1781 /** 1782 * Provides the semantic annotations 1783 * @return the semantic annotations 1784 */ 1785 List<SemanticAnnotation> getSemanticAnnotations(); 1786 1787 /** 1788 * Set the semantic annotations 1789 * @param annotations the semantic annotations to set 1790 */ 1791 void setSemanticAnnotations(List<SemanticAnnotation> annotations); 1792 } 1793 1794 public Optional<ContentAttributeDefinition> getParentAttributeDefinition() 1795 { 1796 return Optional.ofNullable(_parentAttributeDefinition); 1797 } 1798 1799 public Collection<ModelItem> getModelItems() 1800 { 1801 return Collections.unmodifiableCollection(_modelItems.values()); 1802 } 1803 1804 public Map<String, Map<String, List<String>>> getPropertiesReferences() 1805 { 1806 return _propertiesReferences; 1807 } 1808 1809 public Set<String> getViewNames(boolean includeInternals) 1810 { 1811 if (includeInternals) 1812 { 1813 return Collections.unmodifiableSet(_views.keySet()); 1814 } 1815 else 1816 { 1817 return _views.entrySet() 1818 .stream() 1819 .filter(entry -> !entry.getValue().isInternal()) 1820 .map(Map.Entry::getKey) 1821 .collect(Collectors.toSet()); 1822 } 1823 } 1824 1825 public View getView(String viewName) 1826 { 1827 return _views.get(viewName); 1828 } 1829 1830 public Map<String, ViewConfigurations> getViewConfigurations() 1831 { 1832 return _viewConfigurations; 1833 } 1834 1835 /** 1836 * Get the overridden views list 1837 * @return the overridden views list 1838 */ 1839 public List<String> getOverriddenViews() 1840 { 1841 return this._overriddenViews; 1842 } 1843 1844 public Optional<ViewConfigurations> getViewConfigurations(String viewName) 1845 { 1846 return Optional.ofNullable(_viewConfigurations.get(viewName)); 1847 } 1848 1849 public String getFamilyId() 1850 { 1851 return ContentTypeExtensionPoint.ROLE; 1852 } 1853}