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.DefaultConfiguration; 040import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 041import org.apache.avalon.framework.context.Context; 042import org.apache.avalon.framework.context.ContextException; 043import org.apache.avalon.framework.context.Contextualizable; 044import org.apache.avalon.framework.service.ServiceException; 045import org.apache.avalon.framework.service.ServiceManager; 046import org.apache.avalon.framework.thread.ThreadSafe; 047import org.apache.cocoon.Constants; 048import org.apache.commons.lang3.RandomStringUtils; 049import org.apache.commons.lang3.StringUtils; 050import org.apache.excalibur.source.Source; 051import org.xml.sax.SAXException; 052 053import org.ametys.cms.content.references.RichTextOutgoingReferencesExtractor; 054import org.ametys.cms.content.referencetable.HierarchicalReferenceTablesHelper; 055import org.ametys.cms.contenttype.indexing.CustomIndexingField; 056import org.ametys.cms.contenttype.indexing.CustomMetadataIndexingField; 057import org.ametys.cms.contenttype.indexing.DefaultMetadataIndexingField; 058import org.ametys.cms.contenttype.indexing.IndexingField; 059import org.ametys.cms.contenttype.indexing.IndexingModel; 060import org.ametys.cms.contenttype.indexing.MetadataIndexingField; 061import org.ametys.cms.contenttype.indexing.SemanticAnnotationIndexingField; 062import org.ametys.cms.data.type.ModelItemTypeConstants; 063import org.ametys.cms.model.ContentRestrictedCompositeDefinition; 064import org.ametys.cms.model.ContentRestrictedRepeaterDefinition; 065import org.ametys.cms.model.parsing.ContentRestrictedCompositeDefinitionParser; 066import org.ametys.cms.model.parsing.ContentRestrictedRepeaterDefinitionParser; 067import org.ametys.cms.model.restrictions.ContentRestrictedModelItemHelper; 068import org.ametys.cms.model.restrictions.ContentRestrictedModelItemHelper.FirstRestrictionsChecksState; 069import org.ametys.cms.model.restrictions.RestrictedModelItem; 070import org.ametys.cms.model.restrictions.Restrictions; 071import org.ametys.cms.repository.Content; 072import org.ametys.cms.repository.ContentAttributeTypeExtensionPoint; 073import org.ametys.cms.transformation.RichTextTransformer; 074import org.ametys.cms.transformation.docbook.DocbookOutgoingReferencesExtractor; 075import org.ametys.cms.transformation.docbook.DocbookTransformer; 076import org.ametys.plugins.repository.AmetysRepositoryException; 077import org.ametys.runtime.i18n.I18nizableText; 078import org.ametys.runtime.model.ElementDefinition; 079import org.ametys.runtime.model.Enumerator; 080import org.ametys.runtime.model.ModelItem; 081import org.ametys.runtime.model.ModelItemContainer; 082import org.ametys.runtime.model.ModelItemGroup; 083import org.ametys.runtime.model.TemporaryViewReference; 084import org.ametys.runtime.model.View; 085import org.ametys.runtime.model.ViewElement; 086import org.ametys.runtime.model.ViewItem; 087import org.ametys.runtime.model.ViewItemAccessor; 088import org.ametys.runtime.model.exception.UndefinedItemPathException; 089import org.ametys.runtime.model.type.ElementType; 090import org.ametys.runtime.parameter.AbstractParameterParser; 091import org.ametys.runtime.parameter.StaticEnumerator; 092import org.ametys.runtime.parameter.Validator; 093import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 094 095import com.google.common.collect.HashMultimap; 096import com.google.common.collect.Multimap; 097 098/** 099 * Type of content which is retrieved from a XML configuration. 100 * TODO document xml configuration 101 * ... 102 * Provides access based on rights and current workflow steps.<p> 103 * It used a configuration file with the following format: 104 * <code><br> 105 * <restrict-to><br> 106 * [<right type="read|write" id="RIGHT_ID"/>]* 107 * <!-- logical OR between several right id of the same type --><br> 108 * [<workflow type="read|write" step="3"/>]* 109 * <!-- logical OR between several workflow step of the same type --><br> 110 * [<cannot type="read|write"/>]*<br> 111 * </restrict-to><br> 112 * </code> 113 */ 114public class DefaultContentType extends AbstractContentTypeDescriptor implements ContentType, Contextualizable, ThreadSafe, Disposable 115{ 116 /** Suffix for global validator role. */ 117 protected static final String __GLOBAL_VALIDATOR_ROLE_PREFIX = "_globalvalidator"; 118 119 static Pattern __annotationNamePattern; 120 121 /** Metadata definitions. */ 122 @Deprecated 123 protected Map<String, MetadataDefinition> _metadata = new LinkedHashMap<>(); 124 /** Model items */ 125 protected Map<String, ModelItem> _modelItems = new LinkedHashMap<>(); 126 /** The right needed to create a content of this type, or null if no right is needed. */ 127 protected String _right; 128 /** The abstract property */ 129 protected boolean _abstract; 130 /** Configured workflow names of the content type or its supertypes, can be empty */ 131 protected Set<String> _configuredWorkflowNames; 132 /** Default workflow name. */ 133 protected Optional<String> _defaultWorkflowName; 134 /** The tags */ 135 protected Set<String> _tags; 136 /** The inheritable tags */ 137 protected Set<String> _inheritableTags; 138 /** The parent attribute definition */ 139 protected ContentAttributeDefinition _parentAttributeDefinition; 140 /** Service manager. */ 141 protected ServiceManager _manager; 142 /** Avalon Context. */ 143 protected Context _context; 144 /** Cocoon Context */ 145 protected org.apache.cocoon.environment.Context _cocoonContext; 146 /** The restrictions helper */ 147 protected ContentRestrictedModelItemHelper _restrictedModelItemHelper; 148 /** Default rich text transformer. */ 149 protected RichTextTransformer _richTextTransformer; 150 /** Docbook (rich text) outgoing references extractor. */ 151 protected RichTextOutgoingReferencesExtractor _richTextOutgoingReferencesExtractor; 152 /** Potential global validators. */ 153 protected List<ContentValidator> _globalValidators; 154 /** Potentiel richtext updater */ 155 protected RichTextUpdater _richTextUpdater; 156 /** Indexing model */ 157 protected IndexingModel _indexingModel; 158 /** The helper component for hierarchical simple contents */ 159 protected HierarchicalReferenceTablesHelper _hierarchicalSimpleContentsHelper; 160 /** List of overridden attributes */ 161 protected List<String> _overriddenAttributes = new ArrayList<>(); 162 /** List of overridden views */ 163 protected List<String> _overriddenViews = new ArrayList<>(); 164 /** All content type's views */ 165 protected Map<String, View> _views = new LinkedHashMap<>(); 166 /** All configurations of content type's views */ 167 protected Map<String, Configuration> _viewConfigurations; 168 169 /** The parser for content attribute's definitions */ 170 protected ContentAttributeDefinitionParser _attributeDefinitionParser; 171 /** The parser for content compisite's definitions */ 172 protected ContentRestrictedCompositeDefinitionParser _compositeDefinitionParser; 173 /** The parser for content repeater's definitions */ 174 protected ContentRestrictedRepeaterDefinitionParser _repeaterDefinitionParser; 175 /** The parser for dublin core attribute's definitions */ 176 protected DublinCoreAttributeDefinitionParser _dublinCoreAttributeDefinitionParser; 177 178 /** 179 * ComponentManager pour les Validator 180 * @deprecated use {@link #_validatorManager} instead 181 */ 182 @Deprecated 183 private ThreadSafeComponentManager<Validator> _oldValidatorManager; 184 185 // ComponentManager for validators 186 private ThreadSafeComponentManager<Validator> _validatorManager; 187 188 // ComponentManager pour les Global Validators 189 private ThreadSafeComponentManager<ContentValidator> _globalValidatorsManager; 190 191 private ContentTypeReservedAttributeNameExtensionPoint _contentTypeReservedAttributeNameExtensionPoint; 192 193 /** 194 * ComponentManager pour les Enumerator 195 * @deprecated use {@link #_enumeratorManager} instead 196 */ 197 @Deprecated 198 private ThreadSafeComponentManager<org.ametys.runtime.parameter.Enumerator> _oldEnumeratorManager; 199 200 // ComponentManager for enumerators 201 private ThreadSafeComponentManager<Enumerator> _enumeratorManager; 202 203 // ComponentManager pour les CustomIndexingField 204 private ThreadSafeComponentManager<CustomIndexingField> _customFieldManager; 205 206 // ComponentManager pour les CustomMetadataIndexingField 207 private ThreadSafeComponentManager<CustomMetadataIndexingField> _customMetadataIndexingFieldManager; 208 209 // Content attribute types extesion point 210 private ContentAttributeTypeExtensionPoint _contentAttributeTypeExtensionPoint; 211 212 private boolean _isSimple; 213 214 private boolean _isMultilingual; 215 216 @Override 217 public void service(ServiceManager manager) throws ServiceException 218 { 219 super.service(manager); 220 _manager = manager; 221 _richTextTransformer = (RichTextTransformer) manager.lookup(DocbookTransformer.ROLE); 222 _richTextOutgoingReferencesExtractor = (RichTextOutgoingReferencesExtractor) manager.lookup(DocbookOutgoingReferencesExtractor.ROLE); 223 _richTextUpdater = (RichTextUpdater) manager.lookup(DocbookRichTextUpdater.ROLE); 224 _hierarchicalSimpleContentsHelper = (HierarchicalReferenceTablesHelper) manager.lookup(HierarchicalReferenceTablesHelper.ROLE); 225 _restrictedModelItemHelper = (ContentRestrictedModelItemHelper) manager.lookup(ContentRestrictedModelItemHelper.ROLE); 226 _contentAttributeTypeExtensionPoint = (ContentAttributeTypeExtensionPoint) manager.lookup(ContentAttributeTypeExtensionPoint.ROLE); 227 } 228 229 /** 230 * Get the ContentTypeReservedAttributeNameExtensionPoint instance 231 * @return the instance 232 */ 233 protected ContentTypeReservedAttributeNameExtensionPoint _getContentTypeReservedAttributeNameExtensionPoint() 234 { 235 if (_contentTypeReservedAttributeNameExtensionPoint == null) 236 { 237 try 238 { 239 _contentTypeReservedAttributeNameExtensionPoint = (ContentTypeReservedAttributeNameExtensionPoint) _manager.lookup(ContentTypeReservedAttributeNameExtensionPoint.ROLE); 240 } 241 catch (ServiceException e) 242 { 243 throw new RuntimeException(e); 244 } 245 } 246 return _contentTypeReservedAttributeNameExtensionPoint; 247 } 248 249 @Override 250 public void contextualize(Context context) throws ContextException 251 { 252 _context = context; 253 _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 254 } 255 256 @Override 257 public void dispose() 258 { 259 _oldValidatorManager.dispose(); 260 _oldValidatorManager = null; 261 262 _validatorManager.dispose(); 263 _validatorManager = null; 264 265 _globalValidatorsManager.dispose(); 266 _globalValidatorsManager = null; 267 268 _oldEnumeratorManager.dispose(); 269 _oldEnumeratorManager = null; 270 271 _enumeratorManager.dispose(); 272 _enumeratorManager = null; 273 274 _customFieldManager.dispose(); 275 _customFieldManager = null; 276 277 _customMetadataIndexingFieldManager.dispose(); 278 _customMetadataIndexingFieldManager = null; 279 } 280 281 @Override 282 protected Configuration getRootConfiguration(Configuration configuration) 283 { 284 return configuration.getChild("content-type"); 285 } 286 287 @Override 288 protected Configuration getOverridenConfiguration() throws ConfigurationException 289 { 290 Configuration overridenConf = null; 291 File ctFile = new File(_cocoonContext.getRealPath("/WEB-INF/param/content-types/_override/" + _id + ".xml")); 292 293 if (ctFile.exists()) 294 { 295 try (InputStream is = new FileInputStream(ctFile)) 296 { 297 298 overridenConf = new DefaultConfigurationBuilder(true).build(is); 299 } 300 catch (Exception ex) 301 { 302 throw new ConfigurationException("Unable to parse overriden configuration for content type '" + _id + "' at WEB-INF/param/content-types/_override/" + _id + ".xml", overridenConf, ex); 303 } 304 } 305 306 return overridenConf; 307 } 308 309 @Override 310 public void configure(Configuration configuration) throws ConfigurationException 311 { 312 _oldValidatorManager = new ThreadSafeComponentManager<>(); 313 _oldValidatorManager.setLogger(getLogger()); 314 _oldValidatorManager.contextualize(_context); 315 _oldValidatorManager.service(_manager); 316 317 _validatorManager = new ThreadSafeComponentManager<>(); 318 _validatorManager.setLogger(getLogger()); 319 _validatorManager.contextualize(_context); 320 _validatorManager.service(_manager); 321 322 _globalValidatorsManager = new ThreadSafeComponentManager<>(); 323 _globalValidatorsManager.setLogger(getLogger()); 324 _globalValidatorsManager.contextualize(_context); 325 _globalValidatorsManager.service(_manager); 326 327 _oldEnumeratorManager = new ThreadSafeComponentManager<>(); 328 _oldEnumeratorManager.setLogger(getLogger()); 329 _oldEnumeratorManager.contextualize(_context); 330 _oldEnumeratorManager.service(_manager); 331 332 _enumeratorManager = new ThreadSafeComponentManager<>(); 333 _enumeratorManager.setLogger(getLogger()); 334 _enumeratorManager.contextualize(_context); 335 _enumeratorManager.service(_manager); 336 337 _customFieldManager = new ThreadSafeComponentManager<>(); 338 _customFieldManager.setLogger(getLogger()); 339 _customFieldManager.contextualize(_context); 340 _customFieldManager.service(_manager); 341 342 _customMetadataIndexingFieldManager = new ThreadSafeComponentManager<>(); 343 _customMetadataIndexingFieldManager.setLogger(getLogger()); 344 _customMetadataIndexingFieldManager.contextualize(_context); 345 _customMetadataIndexingFieldManager.service(_manager); 346 347 Configuration rootConfiguration = getRootConfiguration(configuration); 348 349 _abstract = rootConfiguration.getAttributeAsBoolean("abstract", false); 350 351 _configureSuperTypes(rootConfiguration); 352 353 _configureLabels(rootConfiguration); 354 _configureIcons(rootConfiguration); 355 356 _configureCSSFiles(rootConfiguration); 357 358 // Tags 359 _tags = new HashSet<>(); 360 _inheritableTags = new HashSet<>(); 361 362 if (rootConfiguration.getChild("tags", false) != null) 363 { 364 if (rootConfiguration.getChild("tags").getAttributeAsBoolean("inherited", false)) 365 { 366 // Get tags from super types 367 for (String superTypeId : _superTypeIds) 368 { 369 ContentType superType = _cTypeEP.getExtension(superTypeId); 370 _tags.addAll(superType.getInheritableTags()); 371 _inheritableTags.addAll(superType.getInheritableTags()); 372 } 373 } 374 375 _configureLocalTags(rootConfiguration.getChild("tags")); 376 } 377 378 // Rights 379 _right = rootConfiguration.getChild("right").getValue(null); 380 381 _isSimple = true; 382 for (String superTypeId : _superTypeIds) 383 { 384 ContentType superType = _cTypeEP.getExtension(superTypeId); 385 if (superType == null) 386 { 387 throw new ConfigurationException("The content type '" + this.getId() + "' cannot extends the unexisting type '" + superTypeId + "'"); 388 } 389 if (!superType.isSimple()) 390 { 391 _isSimple = false; 392 break; 393 } 394 } 395 396 _configureDefaultWorkflowName(rootConfiguration); 397 398 _isMultilingual = false; 399 for (String superTypeId : _superTypeIds) 400 { 401 ContentType superType = _cTypeEP.getExtension(superTypeId); 402 if (superType.isMultilingual()) 403 { 404 _isMultilingual = true; 405 break; 406 } 407 } 408 409 // Attribute definitions 410 _configureAttributeDefinitions (rootConfiguration); 411 412 // Parent content type 413 _configureParentContentType(rootConfiguration); 414 415 // Views 416 _configureViews(rootConfiguration); 417 _configureMetadataSets (rootConfiguration); 418 419 _checkForReservedAttributeName(); 420 421 if (!_abstract && !hasTag(TAG_MIXIN)) 422 { 423 if (!_views.containsKey("details")) 424 { 425 throw new ConfigurationException("Mandatory view named 'details' is missing for content type " + _id, configuration); 426 } 427 if (!_views.containsKey("main")) 428 { 429 throw new ConfigurationException("Mandatory view named 'main' is missing for content type " + _id, configuration); 430 } 431 } 432 433 // Global validators 434 _configureGlobalValidators (rootConfiguration); 435 436 // Indexing model 437 _configureIndexingModel (rootConfiguration); 438 } 439 440 private void _checkForReservedAttributeName() throws ConfigurationException 441 { 442 Set<String> reservedNames = _getContentTypeReservedAttributeNameExtensionPoint().getExtensionsIds(); 443 444 List<String> usedReservedKeywords = new ArrayList<>(reservedNames); 445 usedReservedKeywords.retainAll(_modelItems.keySet()); 446 447 if (!usedReservedKeywords.isEmpty()) 448 { 449 throw new ConfigurationException("In content type '" + _id + "', one or more attributes are named with a reserved keyword: " + StringUtils.join(usedReservedKeywords, ", ") 450 + ". The reserved keywords are: {" + StringUtils.join(reservedNames, ", ") + "}"); 451 } 452 } 453 454 /** 455 * Configure attribute definitions 456 * @param mainConfig The content type configuration 457 * @throws ConfigurationException if an error occurred 458 */ 459 protected void _configureAttributeDefinitions (Configuration mainConfig) throws ConfigurationException 460 { 461 _attributeDefinitionParser = new ContentAttributeDefinitionParser(_contentAttributeTypeExtensionPoint, _enumeratorManager, _validatorManager); 462 _compositeDefinitionParser = new ContentRestrictedCompositeDefinitionParser(_contentAttributeTypeExtensionPoint); 463 _repeaterDefinitionParser = new ContentRestrictedRepeaterDefinitionParser(_contentAttributeTypeExtensionPoint); 464 _dublinCoreAttributeDefinitionParser = new DublinCoreAttributeDefinitionParser(_contentAttributeTypeExtensionPoint, _enumeratorManager, _validatorManager, _dcProvider); 465 MetadataAndRepeaterDefinitionParser defParser = new MetadataAndRepeaterDefinitionParser(_oldEnumeratorManager, _oldValidatorManager); 466 467 // First, get metadata from super type if applicable. 468 _modelItems.putAll(_contentTypesHelper.getModelItems(_superTypeIds)); 469 _metadata.putAll(_getMetadataDefinitions(_superTypeIds)); 470 471 Map<String, Configuration> attributeConfiguration = new LinkedHashMap<>(); 472 _getApplicableAttributes(mainConfig, attributeConfiguration, false); 473 474 Configuration overriddenConfig = getOverridenConfiguration(); 475 if (overriddenConfig != null) 476 { 477 _getApplicableAttributes(overriddenConfig, attributeConfiguration, true); 478 } 479 480 // Then, parse own attributes 481 _parseAllMetadatas(attributeConfiguration, defParser); 482 _parseAllAttributes(attributeConfiguration); 483 484 try 485 { 486 _attributeDefinitionParser.lookupComponents(); 487// _dublinCoreAttributeDefinitionParser.lookupComponents(); 488 defParser.lookupComponents(); 489 } 490 catch (Exception e) 491 { 492 throw new ConfigurationException("Unable to lookup parameter local components", overriddenConfig, e); 493 } 494 495 } 496 497 @Deprecated 498 private Map<String, MetadataDefinition> _getMetadataDefinitions(String[] cTypes) throws ConfigurationException 499 { 500 Map<String, MetadataDefinition> metadata = new LinkedHashMap<>(); 501 502 for (String id : cTypes) 503 { 504 ContentType cType = _cTypeEP.getExtension(id); 505 506 for (String name : cType.getMetadataNames()) 507 { 508 MetadataDefinition definition = cType.getMetadataDefinition(name); 509 510 if (metadata.containsKey(name)) 511 { 512 if (!definition.getReferenceContentType().equals(metadata.get(name).getReferenceContentType())) 513 { 514 // The definition does not provide from a common ancestor 515 throw new ConfigurationException("The metadata '" + name + "' defined in content-type '" + id + "' is already defined in another co-super-type '" 516 + metadata.get(name).getReferenceContentType() + "'"); 517 } 518 continue; 519 } 520 521 metadata.put(name, definition); 522 } 523 } 524 525 return metadata; 526 } 527 528 /** 529 * Fill a map of the applicable attribute configurations. 530 * @param config the content type configuration. 531 * @param attributeConfigurations the Map of attributes {@link Configuration}, indexed by name. 532 * @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. 533 * @throws ConfigurationException if an error occurs. 534 */ 535 protected void _getApplicableAttributes(Configuration config, Map<String, Configuration> attributeConfigurations, boolean allowOverride) throws ConfigurationException 536 { 537 for (Configuration childConfiguration : config.getChildren()) 538 { 539 String childName = childConfiguration.getName(); 540 541 if (childName.equals("metadata") || childName.equals("repeater")) 542 { 543 String attributeName = childConfiguration.getAttribute("name", ""); 544 545 if (!allowOverride && attributeConfigurations.containsKey(attributeName)) 546 { 547 throw new ConfigurationException("Attribute with name '" + attributeName + "' is already defined", childConfiguration); 548 } 549 else if (allowOverride && attributeConfigurations.containsKey(attributeName)) 550 { 551 _checkAttributeTypes(attributeConfigurations.get(attributeName), childConfiguration); 552 } 553 554 if (allowOverride) 555 { 556 this._overriddenAttributes.add(attributeName); 557 } 558 559 attributeConfigurations.put(attributeName, childConfiguration); 560 } 561 else if (childName.equals("dublin-core")) 562 { 563 attributeConfigurations.put("dc", childConfiguration); 564 } 565 } 566 } 567 /** 568 * Get the overridden attributes list 569 * @return the overridden attributes list 570 */ 571 public List<String> getOverriddenAttributes() 572 { 573 return this._overriddenAttributes; 574 } 575 576 /** 577 * Check if all metadata's types defined in first configuration are equals to those defined in second configuration 578 * @param metadataConf1 The first configuration to compare 579 * @param metadataConf2 The second configuration to compare 580 * @throws ConfigurationException if the types are not equals 581 */ 582 protected void _checkAttributeTypes (Configuration metadataConf1, Configuration metadataConf2) throws ConfigurationException 583 { 584 String type = metadataConf1.getAttribute("type", ""); 585 String overridenType = metadataConf2.getAttribute("type", ""); 586 if (!overridenType.equals(type)) 587 { 588 throw new ConfigurationException("The type of metadata '" + metadataConf1.getAttribute("name") + " (" + type.toUpperCase() + ")" + "' can not be overriden to '" + metadataConf2.getAttribute("name") + " (" + overridenType.toUpperCase() + ")'"); 589 } 590 591 if ("composite".equals(type) || metadataConf1.getName().equals("repeater")) 592 { 593 for (Configuration childConfig1 : metadataConf1.getChildren()) 594 { 595 String childName = childConfig1.getName(); 596 if (childName.equals("metadata") || childName.equals("repeater")) 597 { 598 Configuration childConfig2 = null; 599 for (Configuration conf : metadataConf2.getChildren(childName)) 600 { 601 if (childConfig1.getAttribute("name").equals(conf.getAttribute("name"))) 602 { 603 childConfig2 = conf; 604 break; 605 } 606 } 607 608 if (childConfig2 != null) 609 { 610 _checkAttributeTypes (childConfig1, childConfig2); 611 } 612 } 613 } 614 } 615 } 616 617 /** 618 * Parse all attribute configurations. 619 * @param attributeConfigurations the attribute configurations. 620 * @throws ConfigurationException if the configuration is invalid. 621 */ 622 protected void _parseAllAttributes(Map<String, Configuration> attributeConfigurations) throws ConfigurationException 623 { 624 for (Configuration childConfiguration : attributeConfigurations.values()) 625 { 626 String childConfigName = childConfiguration.getName(); 627 628 if (childConfigName.equals("metadata") || childConfigName.equals("repeater")) 629 { 630 ModelItem child = _parseModelItem(childConfiguration, null); 631 if (child != null) 632 { 633 final String childName = child.getName(); 634 if (_modelItems.containsKey(childName)) 635 { 636 _checkAttributeTypes(_modelItems.get(childName), child); 637 } 638 639 _checkContentTypeSimplicity(child); 640 _modelItems.put(childName, child); 641 } 642 } 643 else if (childConfigName.equals("dublin-core")) 644 { 645 _parseDublinCoreAttributes(); 646 } 647 } 648 } 649 650 /** 651 * Parses a model item 652 * @param itemConfiguration configuration of the model item to parse 653 * @param parent the parent of the model item to parse. Can be <code>null</code> if the item has no parent. 654 * @return the parsed model item 655 * @throws ConfigurationException if an error occurs while the model item is parsed 656 */ 657 @SuppressWarnings("static-access") 658 protected ModelItem _parseModelItem(Configuration itemConfiguration, ModelItemGroup parent) throws ConfigurationException 659 { 660 ModelItem modelItem = null; 661 final String itemConfigName = itemConfiguration.getName(); 662 if (itemConfigName.equals("metadata")) 663 { 664 String typeId = itemConfiguration.getAttribute("type"); 665 if (ModelItemTypeConstants.COMPOSITE_TYPE_ID.equals(typeId)) 666 { 667 modelItem = _compositeDefinitionParser.parse(_manager, _pluginName, itemConfiguration, this, parent); 668 } 669 else 670 { 671 modelItem = _attributeDefinitionParser.parse(_manager, _pluginName, itemConfiguration, this, parent); 672 } 673 } 674 else if (ModelItemTypeConstants.REPEATER_TYPE_ID.equals(itemConfigName)) 675 { 676 modelItem = _repeaterDefinitionParser.parse(_manager, _pluginName, itemConfiguration, this, parent); 677 } 678 679 if (modelItem != null && modelItem instanceof ModelItemGroup) 680 { 681 for (Configuration childConfiguration : itemConfiguration.getChildren()) 682 { 683 _parseModelItem(childConfiguration, (ModelItemGroup) modelItem); 684 } 685 } 686 687 return modelItem; 688 } 689 690 /** 691 * Check if all attribute types defined in first model item are equals to those defined in second model item 692 * @param item1 The first item to compare 693 * @param item2 The second item to compare 694 * @throws ConfigurationException if the types are not equals 695 */ 696 protected void _checkAttributeTypes (ModelItem item1, ModelItem item2) throws ConfigurationException 697 { 698 if (item1 instanceof AttributeDefinition) 699 { 700 AttributeDefinition attributeDef1 = (AttributeDefinition) item1; 701 final String attibute1TypeId = attributeDef1.getType().getId(); 702 if (!(item2 instanceof AttributeDefinition) || !attibute1TypeId.equals(((AttributeDefinition) item2).getType().getId())) 703 { 704 throw new ConfigurationException("The type of attribute '" + attributeDef1 + "' defined in content type '" + attributeDef1.getModel() + "', can not be overriden to '" + item2 + "' in content type '" + ((AttributeDefinition) item2).getModel() + "'"); 705 } 706 } 707 else 708 { 709 if (!(item2 instanceof ModelItemGroup)) 710 { 711 throw new ConfigurationException("The item group '" + item1 + "' can not be overriden by the non item group '" + item2 + "' in content type '" + ((AttributeDefinition) item2).getModel() + "'"); 712 } 713 714 ModelItemGroup group1 = (ModelItemGroup) item1; 715 ModelItemGroup group2 = (ModelItemGroup) item2; 716 717 for (ModelItem subItemfromGroup1 : group1.getChildren()) 718 { 719 ModelItem subItemFromGroup2 = group2.getChild(subItemfromGroup1.getName()); 720 if (subItemFromGroup2 != null) 721 { 722 _checkAttributeTypes(subItemfromGroup1, subItemFromGroup2); 723 _checkContentTypeSimplicity(subItemfromGroup1); 724 } 725 } 726 } 727 } 728 729 /** 730 * Checks the given model item to determine if this content type is multilingual and/or simple 731 * All items of a simple content-type have to be a simple type (string, long, date, ..) 732 * A multilingual content type should contain at least an attribute of type MULTILINGUAL-STRING 733 * @param modelItem The model item to check 734 */ 735 protected void _checkContentTypeSimplicity(ModelItem modelItem) 736 { 737 if (modelItem instanceof ModelItemGroup) 738 { 739 // If the content type contains groups, it is not simple 740 _isSimple = false; 741 } 742 else if (modelItem instanceof AttributeDefinition) 743 { 744 ElementType type = ((AttributeDefinition) modelItem).getType(); 745 if (!type.isSimple()) 746 { 747 // If there is a no simple attribute, the content type is not simple 748 _isSimple = false; 749 } 750 751 if (ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID.equals(type.getId())) 752 { 753 // If there is a multilingual-string attribute, the content type is multilingual 754 _isMultilingual = true; 755 } 756 } 757 } 758 759 /** 760 * Parse DublinCore attributes 761 * @throws ConfigurationException if the configuration is invalid 762 */ 763 @SuppressWarnings("static-access") 764 protected void _parseDublinCoreAttributes() throws ConfigurationException 765 { 766 Source src = null; 767 768 try 769 { 770 src = _srcResolver.resolveURI("resource://org/ametys/cms/dublincore/dublincore.xml"); 771 772 if (src.exists()) 773 { 774 Configuration configuration = null; 775 try (InputStream is = src.getInputStream()) 776 { 777 configuration = new DefaultConfigurationBuilder(true).build(is); 778 } 779 780 ContentRestrictedCompositeDefinition definition = new ContentRestrictedCompositeDefinition(); 781 definition.setModel(this); 782 definition.setName("dc"); 783 definition.setLabel(new I18nizableText("plugin.cms", "PLUGINS_CMS_DUBLINCORE_LABEL")); 784 definition.setDescription(new I18nizableText("plugin.cms", "PLUGINS_CMS_DUBLINCORE_DESC")); 785 definition.setType(_contentAttributeTypeExtensionPoint.getExtension(ModelItemTypeConstants.COMPOSITE_TYPE_ID)); 786 787 for (Configuration childConfiguration : configuration.getChildren()) 788 { 789 String childName = childConfiguration.getName(); 790 791 if (childName.equals("metadata")) 792 { 793 _dublinCoreAttributeDefinitionParser.parse(_manager, _pluginName, childConfiguration, this, definition); 794 } 795 } 796 797 _modelItems.put("dc", definition); 798 } 799 } 800 catch (IOException | SAXException e) 801 { 802 throw new ConfigurationException("Unable to parse Dublin Core metadata", e); 803 } 804 finally 805 { 806 if (src != null) 807 { 808 _srcResolver.release(src); 809 } 810 } 811 } 812 813 /** 814 * Parse all metadata configurations. 815 * @param metadataConfigurations the metadata configurations. 816 * @param defParser the metadata definition parser. 817 * @throws ConfigurationException if the configuration is invalid. 818 * @deprecated use {@link #_parseAllAttributes(Map)} instead 819 */ 820 @Deprecated 821 protected void _parseAllMetadatas(Map<String, Configuration> metadataConfigurations, MetadataAndRepeaterDefinitionParser defParser) throws ConfigurationException 822 { 823 for (Configuration childConfiguration : metadataConfigurations.values()) 824 { 825 String childName = childConfiguration.getName(); 826 827 if (childName.equals("metadata") || childName.equals("repeater")) 828 { 829 _parseMetadata(childConfiguration, defParser); 830 } 831 else if (childName.equals("dublin-core")) 832 { 833 _parseDublinCoreMetadata(defParser); 834 } 835 } 836 } 837 838 /** 839 * Parse a metadata configuration. 840 * @param metadataConfiguration the metadata configuration. 841 * @param defParser the metadata definition parser. 842 * @return the created MetadataDefinition. 843 * @throws ConfigurationException if the configuration is invalid 844 * @deprecated use {@link #_parseModelItem(Configuration, ModelItemGroup)} instead 845 */ 846 @Deprecated 847 protected MetadataDefinition _parseMetadata(Configuration metadataConfiguration, MetadataAndRepeaterDefinitionParser defParser) throws ConfigurationException 848 { 849 MetadataDefinition metadataDefinition = defParser.parseParameter(_manager, _pluginName, metadataConfiguration); 850 metadataDefinition.setReferenceContentType(_id); 851 852 String metadataName = metadataDefinition.getName(); 853 854 if (_metadata.containsKey(metadataName)) 855 { 856 _checkMetadataTypes (_metadata.get(metadataName), metadataDefinition); 857 } 858 859 // Update simple and multilingual properties 860 _checkMetadataDefinition(metadataDefinition); 861 862 _metadata.put(metadataName, metadataDefinition); 863 864 return metadataDefinition; 865 } 866 867 /** 868 * Check if all metadata's types defined in first metadata definition are equals to those defined in second metadata definition 869 * @param metaDef1 The first metadata definition to compare 870 * @param metaDef2 The second metadata definition to compare 871 * @throws ConfigurationException if the types are not equals 872 * @deprecated Use {@link #_checkAttributeTypes(ModelItem, ModelItem)} instead 873 */ 874 @Deprecated 875 protected void _checkMetadataTypes (MetadataDefinition metaDef1, MetadataDefinition metaDef2) throws ConfigurationException 876 { 877 if (!metaDef1.getType().equals(metaDef2.getType())) 878 { 879 throw new ConfigurationException("The type of metadata '" + metaDef1.toString() + "' defined in content type '" + metaDef1.getReferenceContentType() + "', can not be overriden to '" + metaDef2.toString() + "' in content type '" + metaDef2.getReferenceContentType() + "'"); 880 } 881 882 if (metaDef1.getType().equals(MetadataType.COMPOSITE)) 883 { 884 for (String subMetadataName : metaDef1.getMetadataNames()) 885 { 886 if (metaDef2.getMetadataDefinition(subMetadataName) != null) 887 { 888 _checkMetadataTypes (metaDef1.getMetadataDefinition(subMetadataName), metaDef2.getMetadataDefinition(subMetadataName)); 889 890 // Update simple and multilingual properties 891 _checkMetadataDefinition(metaDef1.getMetadataDefinition(subMetadataName)); 892 } 893 } 894 } 895 } 896 897 /** 898 * Check the medatata definition to determines if this content type is multilingual and/or simple 899 * All medatata of a simple content-type have to be a simple type (string, long, date, ..) 900 * A multilingual content type should contain at least a metadata of type MULTILINGUAL_STRING 901 * @param metadataDefinition The metadata definition 902 * @return false if the medatata definition is not a valid medatata definition for a simple content-type 903 * @deprecated Use {@link #_checkContentTypeSimplicity(ModelItem)} instead 904 */ 905 @Deprecated 906 protected boolean _checkMetadataDefinition (MetadataDefinition metadataDefinition) 907 { 908 MetadataType type = metadataDefinition.getType(); 909 910 switch (type) 911 { 912 case MULTILINGUAL_STRING: 913 // The content type is a multilingual content type 914 _isMultilingual = true; 915 break; 916 917 case COMPOSITE: 918 case FILE: 919 case REFERENCE: 920 case RICH_TEXT: 921 // The content type can not be simple (complex metadata are not allowed) 922 _isSimple = false; 923 break; 924 default: 925 break; 926 } 927 928 return true; 929 } 930 931 /** 932 * Parse DublinCore metadata 933 * @param defParser The parser definition 934 * @throws ConfigurationException if the configuration is invalid 935 * @deprecated Use {@link #_parseDublinCoreAttributes()} instead 936 */ 937 @Deprecated 938 protected void _parseDublinCoreMetadata (MetadataAndRepeaterDefinitionParser defParser) throws ConfigurationException 939 { 940 Source src = null; 941 942 try 943 { 944 src = _srcResolver.resolveURI("resource://org/ametys/cms/dublincore/dublincore.xml"); 945 946 if (src.exists()) 947 { 948 Configuration configuration = null; 949 try (InputStream is = src.getInputStream()) 950 { 951 configuration = new DefaultConfigurationBuilder(true).build(is); 952 } 953 954 MetadataDefinition metadataDefinition = new MetadataDefinition(); 955 metadataDefinition.setReferenceContentType(_id); 956 metadataDefinition.setId("/dc"); // FIXME ? 957 metadataDefinition.setLabel(new I18nizableText("plugin.cms", "PLUGINS_CMS_DUBLINCORE_LABEL")); 958 metadataDefinition.setDescription(new I18nizableText("plugin.cms", "PLUGINS_CMS_DUBLINCORE_DESC")); 959 metadataDefinition.setName("dc"); 960 metadataDefinition.setType(MetadataType.COMPOSITE); 961 962 for (Configuration childConfiguration : configuration.getChildren()) 963 { 964 String childName = childConfiguration.getName(); 965 966 if (childName.equals("metadata")) 967 { 968 MetadataDefinition metaDef = defParser.parseParameter(_manager, _pluginName, childConfiguration); 969 metaDef.setReferenceContentType(_id); 970 String metadataName = metaDef.getName(); 971 metaDef.setId("/dc/" + metadataName); // FIXME ? 972 973 if (metaDef.getEnumerator() == null && _dcProvider.isEnumerated(metadataName)) 974 { 975 StaticEnumerator enumerator = new StaticEnumerator(); 976 977 Map<String, I18nizableText> entries = _dcProvider.getEntries(metadataName); 978 if (entries != null) 979 { 980 for (String value : entries.keySet()) 981 { 982 enumerator.add(entries.get(value), value); 983 } 984 985 } 986 metaDef.setEnumerator(enumerator); 987 } 988 989 metadataDefinition.addMetadata(metaDef); 990 } 991 } 992 993 _metadata.put("dc", metadataDefinition); 994 } 995 } 996 catch (IOException e) 997 { 998 throw new ConfigurationException("Unable to parse Dublin Core metadata", e); 999 } 1000 catch (SAXException e) 1001 { 1002 throw new ConfigurationException("Unable to parse Dublin Core metadata", e); 1003 } 1004 finally 1005 { 1006 if (src != null) 1007 { 1008 _srcResolver.release(src); 1009 } 1010 } 1011 } 1012 1013 /** 1014 * Configure the default workflow name from the XML configuration. 1015 * - From the overriden configuration 1016 * - If not, from the current configuration 1017 * - If not, from the supertypes 1018 * - If it cannot be determined and the content type is a reference table, then "reference-table" 1019 * - Otherwise "content" 1020 * @param mainConfig The configuration 1021 * @throws ConfigurationException if an exception occurs 1022 */ 1023 protected void _configureDefaultWorkflowName(Configuration mainConfig) throws ConfigurationException 1024 { 1025 _configuredWorkflowNames = Optional.ofNullable(getOverridenConfiguration()) 1026 // Override mode 1027 .map(oc -> oc.getChild("default-workflow").getValue(null)) 1028 // Normal mode 1029 .or(() -> Optional.ofNullable(mainConfig.getChild("default-workflow").getValue(null))) 1030 // Transform it in a singleton set 1031 .map(Set::of) 1032 // Inherited mode 1033 .orElseGet(this::_getDefaultWorkflowNamesFromSupertypes); 1034 1035 _defaultWorkflowName = _configuredWorkflowNames.size() > 1 1036 // Several workflow names returns undefined 1037 ? Optional.empty() 1038 : _configuredWorkflowNames.stream() 1039 // One workflow name is directly returned 1040 .findFirst() 1041 // Otherwise default workflow : reference-table or content 1042 .or(() -> Optional.of(isReferenceTable() ? "reference-table" : "content")); 1043 } 1044 1045 private Set<String> _getDefaultWorkflowNamesFromSupertypes() 1046 { 1047 Set<String> defaultWorkflowNames = new HashSet<>(); 1048 1049 // Get tags from super types 1050 for (String superTypeId : _superTypeIds) 1051 { 1052 ContentType superType = _cTypeEP.getExtension(superTypeId); 1053 defaultWorkflowNames.addAll(superType.getConfiguredDefaultWorkflowNames()); 1054 } 1055 1056 if (defaultWorkflowNames.size() > 1) 1057 { 1058 getLogger().warn("Several default workflows are defined for content type '{}' : {}.", _id, StringUtils.join(defaultWorkflowNames)); 1059 } 1060 1061 return defaultWorkflowNames; 1062 } 1063 1064 /** 1065 * Configures the "parent" content type. 1066 * This must not be confounded with the super types. (See {@link AbstractContentTypeDescriptor#_configureSuperTypes(Configuration)}) 1067 * @param mainConfig The main configuration 1068 * @throws ConfigurationException if an error occurred 1069 */ 1070 protected void _configureParentContentType(Configuration mainConfig) throws ConfigurationException 1071 { 1072 Configuration parentCTypeConf = mainConfig.getChild("parent-ref", false); 1073 _parentAttributeDefinition = null; 1074 1075 if (parentCTypeConf != null) 1076 { 1077 // Check this content type is a reference table 1078 if (!isReferenceTable()) 1079 { 1080 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()); 1081 return; 1082 } 1083 1084 String refAttributeName = parentCTypeConf.getAttribute("name"); 1085 // Check valid reference of metadata 1086 if (!_modelItems.containsKey(refAttributeName)) 1087 { 1088 getLogger().error("The 'parent-ref' tag for content type '{}' references the attribute '{}' but it does not exist. It will be ignored.", getId(), refAttributeName); 1089 return; 1090 } 1091 1092 ModelItem modelItem = _modelItems.get(refAttributeName); 1093 // Check metadata of type "content" 1094 if (!(modelItem instanceof ContentAttributeDefinition)) 1095 { 1096 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); 1097 return; 1098 } 1099 1100 ContentAttributeDefinition contentAttributeDefinition = (ContentAttributeDefinition) modelItem; 1101 if (contentAttributeDefinition.isMultiple()) 1102 { 1103 getLogger().error("The 'parent-ref' tag for content type '{}' references the attribute '{}' but it is a multiple attribute. It will be ignored.", getId(), refAttributeName); 1104 return; 1105 } 1106 1107 String parentCTypeName = contentAttributeDefinition.getContentTypeId(); 1108 ContentType parentCType = _cTypeEP.getExtension(parentCTypeName); 1109 1110 if (parentCType == null) 1111 { 1112 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); 1113 return; 1114 } 1115 // Check parent content type is private AND simple 1116 else if (!parentCType.isPrivate()) 1117 { 1118 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); 1119 return; 1120 } 1121 else if (!parentCType.isReferenceTable()) 1122 { 1123 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); 1124 return; 1125 } 1126 1127 if (!_hierarchicalSimpleContentsHelper.registerRelation(parentCType, this)) 1128 { 1129 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()); 1130 return; 1131 } 1132 _parentAttributeDefinition = contentAttributeDefinition; 1133 } 1134 } 1135 1136 /** 1137 * Configure the content type views 1138 * @param mainConfig The content type configuration 1139 * @throws ConfigurationException if an error occurred 1140 */ 1141 protected void _configureViews(Configuration mainConfig) throws ConfigurationException 1142 { 1143 // Get applicable views configurations 1144 // At first, get the views configured with the old syntax to replace them by the ones configured with the new syntax 1145 _viewConfigurations = _getApplicableViews(mainConfig, VIEW_TAG_NAME_WITH_LEGACY_SYNTAX, false); 1146 _viewConfigurations.putAll(_getApplicableViews(mainConfig, VIEW_TAG_NAME, false)); 1147 1148 Configuration overriddenConfig = getOverridenConfiguration(); 1149 if (overriddenConfig != null) 1150 { 1151 _viewConfigurations.putAll(_getApplicableViews(overriddenConfig, VIEW_TAG_NAME_WITH_LEGACY_SYNTAX, true)); 1152 _viewConfigurations.putAll(_getApplicableViews(overriddenConfig, VIEW_TAG_NAME, true)); 1153 } 1154 1155 Map<String, Map<ContentType, Configuration>> allApplicableViews = _contentTypesHelper.getViewConfigurations(_superTypeIds, new String[0]); 1156 for (Map.Entry<String, Configuration> viewConfigurationByViewName : _viewConfigurations.entrySet()) 1157 { 1158 String viewName = viewConfigurationByViewName.getKey(); 1159 Configuration viewConfiguration = viewConfigurationByViewName.getValue(); 1160 allApplicableViews.put(viewName, Map.of(this, viewConfiguration)); 1161 } 1162 1163 // Parse content type's views 1164 for (String viewName : allApplicableViews.keySet()) 1165 { 1166 View view = _contentTypesHelper.parseView(this, allApplicableViews.get(viewName)); 1167 _views.put(viewName, view); 1168 } 1169 } 1170 1171 /** 1172 * Get the overridden views list 1173 * @return the overridden views list 1174 */ 1175 public List<String> getOverriddenViews() 1176 { 1177 return this._overriddenViews; 1178 } 1179 1180 /** 1181 * Compute the applicable views from their configurations. 1182 * @param configuration The content type configuration 1183 * @param viewTagName The name of the tag containing the view 1184 * @param allowOverride if <code>true</code>, encountering a view which has already been declared (based on its name) will replace it. Otherwise, an exception will be thrown. 1185 * @return the applicable views, indexed by their names. For each view, indicates if the view is configured 1186 * @throws ConfigurationException if the configuration is invalid 1187 */ 1188 protected Map<String, Configuration> _getApplicableViews(Configuration configuration, String viewTagName, boolean allowOverride) throws ConfigurationException 1189 { 1190 Map<String, Configuration> applicableViews = new LinkedHashMap<>(); 1191 1192 for (Configuration viewConfig : configuration.getChildren(viewTagName)) 1193 { 1194 String name = viewConfig.getAttribute("name"); 1195 if (allowOverride) 1196 { 1197 this._overriddenViews.add(name); 1198 } 1199 1200 if (!allowOverride && applicableViews.containsKey(name)) 1201 { 1202 // TODO NEWATTRIBUTEAPI_CONTENT: Review the unicity of views due to edition / view differences : 1203 // In old API, we made the difference between view and edition metadata set. 1204 // We decided remove this notion. But all existing content types have for example 2 "main" metadata-set, one for view and the other for edition 1205 // So we can't check here that there is onl one view called "main" 1206 // Really remove the edition / view notion? or allow the creation of a view without type and put it in edition and view mode? 1207// throw new ConfigurationException("The view named '" + name + "' is already defined.", viewConfig); 1208 getLogger().info("The view named '" + name + "' is already defined.", viewConfig); 1209 } 1210 else 1211 { 1212 applicableViews.put(name, viewConfig); 1213 } 1214 } 1215 1216 return applicableViews; 1217 } 1218 1219 /** 1220 * Configure the global validators for content type 1221 * @param config The content type configuration 1222 * @throws ConfigurationException if an error occurs 1223 */ 1224 protected void _configureGlobalValidators (Configuration config) throws ConfigurationException 1225 { 1226 _globalValidators = new ArrayList<>(); 1227 List<String> globalValidatorsToLookup = new ArrayList<>(); 1228 1229 Configuration globalValidatorsConfig = config.getChild("global-validators", true); 1230 globalValidatorsToLookup.addAll(_parseGlobalValidators(globalValidatorsConfig, config.getAttributeAsBoolean("include-from-supertype", true))); 1231 1232 Configuration overriddenConfig = getOverridenConfiguration(); 1233 if (overriddenConfig != null) 1234 { 1235 // Global validators into an overriden configuration are added to the original global validators 1236 globalValidatorsToLookup.addAll(_parseGlobalValidators(overriddenConfig.getChild("global-validators", true), false)); 1237 } 1238 1239 try 1240 { 1241 _globalValidatorsManager.initialize(); 1242 } 1243 catch (Exception e) 1244 { 1245 throw new ConfigurationException("Unable to initialize global validator manager", e); 1246 } 1247 1248 for (String validatorRole : globalValidatorsToLookup) 1249 { 1250 try 1251 { 1252 ContentValidator contentValidator = _globalValidatorsManager.lookup(validatorRole); 1253 contentValidator.setContentType(this); 1254 1255 _globalValidators.add(contentValidator); 1256 } 1257 catch (ComponentException e) 1258 { 1259 throw new ConfigurationException("Unable to lookup global validator role: '" + validatorRole + "' for content type: " + this.getId(), e); 1260 } 1261 } 1262 1263 } 1264 1265 /** 1266 * Parse the global validators 1267 * @param config the configuration 1268 * @param includeSuperTypeValidators true to include validators of super types 1269 * @return the role of global validators to be lookuped 1270 * @throws ConfigurationException if configuration is incorrect 1271 */ 1272 @SuppressWarnings("unchecked") 1273 protected List<String> _parseGlobalValidators(Configuration config, boolean includeSuperTypeValidators) throws ConfigurationException 1274 { 1275 List<String> gvRoles = new ArrayList<>(); 1276 1277 if (includeSuperTypeValidators) 1278 { 1279 for (String superTypeId : _superTypeIds) 1280 { 1281 ContentType cType = _cTypeEP.getExtension(superTypeId); 1282 _globalValidators.addAll(cType.getGlobalValidators()); 1283 } 1284 } 1285 1286 for (Configuration globalValidatorConfig : config.getChildren("global-validator")) 1287 { 1288 String globalValidatorId = __GLOBAL_VALIDATOR_ROLE_PREFIX + RandomStringUtils.randomAlphanumeric(10); 1289 String validatorClassName = globalValidatorConfig.getAttribute("class"); 1290 1291 try 1292 { 1293 Class validatorClass = Class.forName(validatorClassName); 1294 _globalValidatorsManager.addComponent(_pluginName, null, globalValidatorId, validatorClass, globalValidatorConfig); 1295 } 1296 catch (Exception e) 1297 { 1298 throw new ConfigurationException("Unable to instantiate global validator for class: " + validatorClassName, e); 1299 } 1300 1301 gvRoles.add(globalValidatorId); 1302 } 1303 1304 return gvRoles; 1305 } 1306 1307 /** 1308 * Configure the indexing model 1309 * @param config The main configuration 1310 * @throws ConfigurationException if an error occurred 1311 */ 1312 protected void _configureIndexingModel (Configuration config) throws ConfigurationException 1313 { 1314 Configuration indexConf = config.getChild("indexing-model", true); 1315 1316 Configuration overriddenConfig = getOverridenConfiguration(); 1317 if (overriddenConfig != null && overriddenConfig.getChild("indexing-model", false) != null) 1318 { 1319 // Indexing model is overriden (original configuration is ignored) 1320 indexConf = overriddenConfig.getChild("indexing-model", true); 1321 } 1322 1323 _indexingModel = new IndexingModel(); 1324 1325 boolean includeFromSuperType = indexConf.getAttributeAsBoolean("include-from-supertype", true); 1326 if (includeFromSuperType) 1327 { 1328 _indexingModel = _contentTypesHelper.getIndexingModel(_superTypeIds, new String[0]); 1329 } 1330 1331 boolean includeAll = indexConf.getAttributeAsBoolean("include-all", true); 1332 if (includeAll) 1333 { 1334 for (String metadataName : _metadata.keySet()) 1335 { 1336 MetadataDefinition definition = _metadata.get(metadataName); 1337 _indexingModel.addIndexingField(new DefaultMetadataIndexingField(metadataName, definition, metadataName)); 1338 } 1339 } 1340 1341 // Optionally add the semantic annotations to the indexing model. 1342 boolean includeSemanticAnnotations = indexConf.getAttributeAsBoolean("include-semantic-annotations", true); 1343 if (includeSemanticAnnotations) 1344 { 1345 _addSemanticAnnotations(indexConf, this); 1346 } 1347 1348 // Metadata fields. 1349 _configureMetadataIndexingFields(indexConf); 1350 1351 // Custom fields. 1352 _configureCustomIndexingFields(indexConf); 1353 1354 // Custom metadata fields. 1355 _configureCustomMetadataIndexingFields(indexConf); 1356 } 1357 1358 /** 1359 * Add semantic annotations as indexing fields to the indexing model. 1360 * @param indexConf the indexing model configuration. 1361 * @param holder the metadata holder (ContentType or MetadataDefinition) to scan for annotable metadata. 1362 */ 1363 protected void _addSemanticAnnotations(Configuration indexConf, MetadataDefinitionHolder holder) 1364 { 1365 // Get the semantic annotations in a multimap. 1366 Multimap<SemanticAnnotation, String> metadatas = HashMultimap.create(); 1367 _getSemanticAnnotations(holder, metadatas, ""); 1368 1369 // Add a custom indexing field for each annotation to the indexing model. 1370 for (SemanticAnnotation annotation : metadatas.keySet()) 1371 { 1372 Collection<String> metaPaths = metadatas.get(annotation); 1373 _indexingModel.addIndexingField(new SemanticAnnotationIndexingField(annotation, metaPaths, this)); 1374 } 1375 } 1376 1377 /** 1378 * Get the semantic annotations and their paths from the given metadata definition holder's sub-tree. 1379 * @param holder The current metadata definition holder. 1380 * @param annotations The map of semantic annotations and the corresponding metadata paths. 1381 * @param prefix The current prefix in the metadata tree (with a trailing slash, if applicable). 1382 */ 1383 protected void _getSemanticAnnotations(MetadataDefinitionHolder holder, Multimap<SemanticAnnotation, String> annotations, String prefix) 1384 { 1385 for (String metadataName : holder.getMetadataNames()) 1386 { 1387 String metaPath = prefix + metadataName; 1388 MetadataDefinition metaDef = holder.getMetadataDefinition(metadataName); 1389 if (metaDef instanceof AnnotableDefinition) 1390 { 1391 List<SemanticAnnotation> metaAnnotations = ((AnnotableDefinition) metaDef).getSemanticAnnotations(); 1392 for (SemanticAnnotation annotation : metaAnnotations) 1393 { 1394 annotations.put(annotation, metaPath); 1395 } 1396 } 1397 1398 _getSemanticAnnotations(metaDef, annotations, metaPath + ContentConstants.METADATA_PATH_SEPARATOR); 1399 } 1400 } 1401 1402 /** 1403 * Configure the metadata indexing fields. 1404 * @param indexConf the indexing model configuration. 1405 * @throws ConfigurationException if an error occurs. 1406 */ 1407 protected void _configureMetadataIndexingFields(Configuration indexConf) throws ConfigurationException 1408 { 1409 Configuration[] fieldsConf = indexConf.getChildren("metadata-field"); 1410 for (Configuration fieldConf : fieldsConf) 1411 { 1412 String metadataPath = fieldConf.getAttribute("path"); 1413 1414 MetadataDefinition metadataDef = _contentTypesHelper.getMetadataDefinition(metadataPath, this); 1415 if (metadataDef != null) 1416 { 1417 String fieldName = fieldConf.getAttribute("name", null); 1418 if (fieldName == null) 1419 { 1420 fieldName = StringUtils.substringBeforeLast(metadataPath, ContentConstants.METADATA_PATH_SEPARATOR); 1421 } 1422 1423 // TODO check if metadataDef.getType() is a primitive type (string, long, double, ..) ?? 1424 /*if (MetadataType.COMPOSITE == metadataDef.getType()) 1425 { 1426 throw new ConfigurationException("Indexing field of path '" + metadataPath + "' defined in content type '" + this.getId() + "' is not a valid path."); 1427 }*/ 1428 _indexingModel.addIndexingField(new DefaultMetadataIndexingField(fieldName, metadataDef, metadataPath)); 1429 } 1430 else 1431 { 1432 throw new ConfigurationException("Indexing field of path '" + metadataPath + "' defined in content type '" + this.getId() + "' is not a valid path.", fieldConf); 1433 } 1434 } 1435 } 1436 1437 /** 1438 * Configure the custom indexing fields. 1439 * @param indexConf the indexing model configuration. 1440 * @throws ConfigurationException if an error occurs. 1441 */ 1442 @SuppressWarnings("unchecked") 1443 protected void _configureCustomIndexingFields(Configuration indexConf) throws ConfigurationException 1444 { 1445 List<String> customFieldRoles = new ArrayList<>(); 1446 1447 Configuration[] customsConf = indexConf.getChildren("custom-field"); 1448 for (Configuration customConf : customsConf) 1449 { 1450 String className = customConf.getAttribute("class", null); 1451 if (className == null) 1452 { 1453 throw new ConfigurationException("A custom index field defined in content type '" + this.getId() + "' does not specifiy a class.", customConf); 1454 } 1455 1456 try 1457 { 1458 Class<CustomIndexingField> customFieldClass = (Class<CustomIndexingField>) Class.forName(className); 1459 String fieldRole = getId() + "-" + className; 1460 _customFieldManager.addComponent("cms", null, getId() + "-" + className, customFieldClass, customConf); 1461 1462 customFieldRoles.add(fieldRole); 1463 } 1464 catch (Exception e) 1465 { 1466 throw new ConfigurationException("Unable to instanciate custom indexing field for class: " + className, customConf, e); 1467 } 1468 } 1469 1470 try 1471 { 1472 _customFieldManager.initialize(); 1473 } 1474 catch (Exception e) 1475 { 1476 throw new ConfigurationException("Unable to initialize custom indexing field manager", e); 1477 } 1478 1479 for (String customFieldRole : customFieldRoles) 1480 { 1481 try 1482 { 1483 CustomIndexingField field = _customFieldManager.lookup(customFieldRole); 1484 _indexingModel.addIndexingField(field); 1485 } 1486 catch (ComponentException e) 1487 { 1488 throw new ConfigurationException("Unable to lookup custom indexing field with role: '" + customFieldRole + "' for content type: " + this.getId(), e); 1489 } 1490 } 1491 } 1492 1493 /** 1494 * Configure the custom metadata indexing fields. 1495 * @param indexConf the indexing model configuration. 1496 * @throws ConfigurationException if an error occurs. 1497 */ 1498 @SuppressWarnings("unchecked") 1499 protected void _configureCustomMetadataIndexingFields(Configuration indexConf) throws ConfigurationException 1500 { 1501 List<String> customMetadataFieldRoles = new ArrayList<>(); 1502 1503 int index = 0; 1504 Configuration[] customMetaConfs = indexConf.getChildren("custom-metadata-field"); 1505 for (Configuration customMetaConf : customMetaConfs) 1506 { 1507 String className = customMetaConf.getAttribute("class", null); 1508 if (className == null) 1509 { 1510 throw new ConfigurationException("A custom indexing field defined in content type '" + this.getId() + "' does not specifiy a class.", customMetaConf); 1511 } 1512 1513 try 1514 { 1515 DefaultConfiguration localConf = new DefaultConfiguration(customMetaConf, true); 1516 DefaultConfiguration cTypeConf = new DefaultConfiguration("contentType"); 1517 cTypeConf.setAttribute("id", this.getId()); 1518 localConf.addChild(cTypeConf); 1519 1520 // Use an index in the role to be able to use several times the same class in a content-type. 1521 String fieldRole = getId() + "-" + className + "-" + index; 1522 Class<CustomMetadataIndexingField> customMetaFieldClass = (Class<CustomMetadataIndexingField>) Class.forName(className); 1523 _customMetadataIndexingFieldManager.addComponent("cms", null, fieldRole, customMetaFieldClass, localConf); 1524 1525 customMetadataFieldRoles.add(fieldRole); 1526 1527 index++; 1528 } 1529 catch (Exception e) 1530 { 1531 throw new ConfigurationException("Unable to instanciate custom metadata indexing field for class: " + className, customMetaConf, e); 1532 } 1533 } 1534 1535 try 1536 { 1537 _customMetadataIndexingFieldManager.initialize(); 1538 } 1539 catch (Exception e) 1540 { 1541 throw new ConfigurationException("Unable to initialize custom metadata indexing field manager", e); 1542 } 1543 1544 for (String customMetaFieldRole : customMetadataFieldRoles) 1545 { 1546 try 1547 { 1548 CustomMetadataIndexingField field = _customMetadataIndexingFieldManager.lookup(customMetaFieldRole); 1549 _indexingModel.addIndexingField(field); 1550 } 1551 catch (ComponentException e) 1552 { 1553 throw new ConfigurationException("Unable to lookup custom indexing field with role: '" + customMetaFieldRole + "' for content type: " + this.getId(), e); 1554 } 1555 } 1556 } 1557 1558 /** 1559 * Parse the tags and add it to tags list 1560 * @param configuration the configuration to use 1561 * @throws ConfigurationException if the configuration is not valid. 1562 */ 1563 protected void _configureLocalTags(Configuration configuration) throws ConfigurationException 1564 { 1565 Configuration[] children = configuration.getChildren("tag"); 1566 for (Configuration tagConfig : children) 1567 { 1568 String tagValue = tagConfig.getValue(); 1569 if (tagConfig.getAttributeAsBoolean("inheritable", true)) 1570 { 1571 _inheritableTags.add(tagValue); 1572 } 1573 _tags.add(tagValue); 1574 } 1575 } 1576 1577 @Override 1578 public void postInitialize() throws Exception 1579 { 1580 _checkTitleAttribute(); 1581 1582 _checkContentAttributes(this, 0); 1583 1584 _resolveViewReferences(); 1585 1586 _computeIndexingModelReferences(); 1587 } 1588 1589 @SuppressWarnings("static-access") 1590 private void _checkTitleAttribute() throws ConfigurationException 1591 { 1592 if (!isAbstract() && !isMixin()) 1593 { 1594 ModelItem titleItem = null; 1595 if (hasModelItem(Content.ATTRIBUTE_TITLE)) 1596 { 1597 titleItem = getModelItem(Content.ATTRIBUTE_TITLE); 1598 } 1599 else 1600 { 1601 // The title attribute is mandatory for non abstract content types 1602 throw new ConfigurationException("An attribute 'title' in content type '" + getId() + "' should be defined."); 1603 } 1604 1605 // The title attribute should'nt be a group item 1606 if (!(titleItem instanceof ElementDefinition)) 1607 { 1608 throw new ConfigurationException("An attribute 'title' in content type '" + getId() + "' should be defined."); 1609 } 1610 1611 // The title attribute should be a string or a multilingual string 1612 ElementDefinition titleAttribute = (ElementDefinition) titleItem; 1613 String typeId = titleAttribute.getType().getId(); 1614 if (!ModelItemTypeConstants.STRING_TYPE_ID.equals(typeId) && !ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID.equals(typeId)) 1615 { 1616 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"); 1617 } 1618 1619 // The title attribute should not be multiple 1620 if (titleAttribute.isMultiple()) 1621 { 1622 throw new ConfigurationException("The attribute 'title' in content type '" + getId() + "' should not be multiple."); 1623 } 1624 1625 // The title attribute must be mandatory 1626 Validator titleValidator = titleAttribute.getValidator(); 1627 if (titleValidator == null || !(boolean) titleValidator.getConfiguration().get("mandatory")) 1628 { 1629 throw new ConfigurationException("The attribute 'title' in content type '" + getId() + "' should be mandatory."); 1630 } 1631 } 1632 } 1633 1634 /** 1635 * Check for each content attribute: the content type id, the mutual references and the default values 1636 * @param modelItemContainer the {@link ModelItemContainer} to check 1637 * @param repeaterLevel the current nested level of repeaters, 0 if the current attribute isn't in a repeater. 1638 * @throws ConfigurationException if a content attribute has an invalid configuration 1639 */ 1640 protected void _checkContentAttributes(ModelItemContainer modelItemContainer, int repeaterLevel) throws ConfigurationException 1641 { 1642 for (ModelItem modelItem : modelItemContainer.getModelItems()) 1643 { 1644 if (ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(modelItem.getType().getId())) 1645 { 1646 ContentAttributeDefinition definition = (ContentAttributeDefinition) modelItem; 1647 _checkContentTypeId(definition); 1648 _checkMutualReferences(definition, repeaterLevel); 1649 definition.checkDefaultValue(); 1650 } 1651 1652 // Check sub-attributes 1653 if (modelItem instanceof ModelItemContainer) 1654 { 1655 // Increment the repeater level if the current attribute is a repeater. 1656 int newRepeaterLevel = (modelItem instanceof org.ametys.plugins.repository.model.RepeaterDefinition) ? repeaterLevel + 1 : repeaterLevel; 1657 _checkContentAttributes((ModelItemContainer) modelItem, newRepeaterLevel); 1658 } 1659 } 1660 } 1661 1662 /** 1663 * Check the content type id of the given content attribute definition 1664 * @param definition the definition to check 1665 * @throws ConfigurationException if the given content attribute references an invalid or non-existing content type. 1666 */ 1667 protected void _checkContentTypeId(ContentAttributeDefinition definition) throws ConfigurationException 1668 { 1669 String contentTypeId = definition.getContentTypeId(); 1670 1671 if (StringUtils.isNotBlank(contentTypeId) && !_cTypeEP.hasExtension(contentTypeId)) 1672 { 1673 throw new ConfigurationException("The content attribute of path " + definition.getPath() + " in content type " + getId() + " references a nonexistent content-type: '" + contentTypeId + "'"); 1674 } 1675 } 1676 1677 /** 1678 * Check the mutual reference declaration of the given content attribute definition 1679 * @param definition the definition to check 1680 * @param repeaterLevel the current nested level of repeaters, 0 if the current attribute isn't in a repeater. 1681 * @throws ConfigurationException if there is a problem with mutual reference declaration 1682 */ 1683 protected void _checkMutualReferences(ContentAttributeDefinition definition, int repeaterLevel) throws ConfigurationException 1684 { 1685 String contentTypeId = definition.getContentTypeId(); 1686 String invertRelationPath = definition.getInvertRelationPath(); 1687 1688 if (StringUtils.isNotEmpty(invertRelationPath)) 1689 { 1690 String currentAttributePath = definition.getPath(); 1691 1692 if (StringUtils.isEmpty(contentTypeId)) 1693 { 1694 throw new ConfigurationException("The attribute at path '" + currentAttributePath + "' in content type '" + getId() + "' is declared as a mutual relationship, a content type is required."); 1695 } 1696 1697 // Do not check the extension existence, this is already done in _checkContentAttribute method 1698 ContentType contentType = _cTypeEP.getExtension(contentTypeId); 1699 1700 if (repeaterLevel > 0 && definition.isMultiple()) 1701 { 1702 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."); 1703 } 1704 else if (repeaterLevel >= 2) 1705 { 1706 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."); 1707 } 1708 1709 try 1710 { 1711 ModelItem invertRelationDefinition = contentType.getModelItem(invertRelationPath); 1712 1713 // Ensure that the referenced attribute is of type content. 1714 if (!ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(invertRelationDefinition.getType().getId())) 1715 { 1716 throw new ConfigurationException("Mutual relationship: the attribute at path '" + invertRelationPath + "' of type " + contentTypeId + " is not of type Content."); 1717 } 1718 1719 String invertCTypeId = ((ContentAttributeDefinition) invertRelationDefinition).getContentTypeId(); 1720 String invertPath = ((ContentAttributeDefinition) invertRelationDefinition).getInvertRelationPath(); 1721 1722 // Ensure that the referenced attribute's content type is compatible with the current type. 1723 if (!_cTypeEP.isSameOrDescendant(getId(), invertCTypeId)) 1724 { 1725 throw new ConfigurationException("Mutual relationship: the attribute at path " + invertRelationPath + " of type " + contentTypeId + " references an incompatible type: " + StringUtils.defaultString(invertCTypeId, "<null>")); 1726 } 1727 1728 // Ensure that the referenced attribute references this attribute. 1729 if (!currentAttributePath.equals(invertPath)) 1730 { 1731 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."); 1732 } 1733 } 1734 catch (UndefinedItemPathException e) 1735 { 1736 // Ensure the referenced attribute presence. 1737 throw new ConfigurationException("Mutual relationship: the attribute at path '" + invertRelationPath + "' doesn't exist for type " + contentTypeId); 1738 } 1739 } 1740 } 1741 1742 /** 1743 * Resolve the temporary view references 1744 * @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, ...) 1745 */ 1746 protected void _resolveViewReferences() throws ConfigurationException 1747 { 1748 for (String viewName : getViewNames()) 1749 { 1750 View view = getView(viewName); 1751 _resolveViewReferences(view, viewName); 1752 } 1753 } 1754 1755 /** 1756 * Resolve the temporary view references in the given {@link ViewItemAccessor} 1757 * @param viewItemAccessor the {@link ViewItemAccessor} 1758 * @param currentViewName the name of the current view (to avoid views referencing themselves) 1759 * @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, ...) 1760 */ 1761 protected void _resolveViewReferences(ViewItemAccessor viewItemAccessor, String currentViewName) throws ConfigurationException 1762 { 1763 boolean hasResolvedReferences = false; 1764 List<ViewItem> viewItemsWithResolvedReferences = new ArrayList<>(); 1765 for (ViewItem viewItem : viewItemAccessor.getViewItems()) 1766 { 1767 if (viewItem instanceof TemporaryViewReference) 1768 { 1769 assert viewItemAccessor instanceof ViewElement; 1770 1771 ElementDefinition definition = ((ViewElement) viewItemAccessor).getDefinition(); 1772 assert definition instanceof ContentAttributeDefinition; 1773 1774 String contentTypeId = ((ContentAttributeDefinition) definition).getContentTypeId(); 1775 if (contentTypeId != null) 1776 { 1777 Set<String> ancestors = _contentTypesHelper.getAncestors(this.getId()); 1778 if (!(ancestors.contains(contentTypeId) && currentViewName.equals(viewItem.getName()))) 1779 { 1780 ContentType contentType = _cTypeEP.getExtension(contentTypeId); 1781 View view = contentType.getView(viewItem.getName()); 1782 if (view != null) 1783 { 1784 hasResolvedReferences = true; 1785 viewItemsWithResolvedReferences.addAll(view.getViewItems()); 1786 } 1787 else 1788 { 1789 throw new ConfigurationException("The view '" + viewItem.getName() + "' does not exist in content type '" + contentTypeId + "' referenced by the attribute named '" + definition.getName() + "'."); 1790 } 1791 } 1792 else 1793 { 1794 throw new ConfigurationException("The view '" + viewItem.getName() + "' cannot make a reference to itself."); 1795 } 1796 } 1797 else 1798 { 1799 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() + "'."); 1800 } 1801 } 1802 else 1803 { 1804 if (viewItem instanceof ViewItemAccessor) 1805 { 1806 _resolveViewReferences((ViewItemAccessor) viewItem, currentViewName); 1807 } 1808 viewItemsWithResolvedReferences.add(viewItem); 1809 } 1810 } 1811 1812 if (hasResolvedReferences) 1813 { 1814 viewItemAccessor.clear(); 1815 viewItemAccessor.addViewItems(viewItemsWithResolvedReferences); 1816 } 1817 } 1818 1819 /** 1820 * Browse the indexing model and compute indexing field references. 1821 */ 1822 protected void _computeIndexingModelReferences() 1823 { 1824 // Impacted ContentType -> local IndexingField name -> path to impacted content. 1825 Map<String, Map<String, List<String>>> references = new HashMap<>(); 1826 1827 for (IndexingField field : _indexingModel.getFields()) 1828 { 1829 if (field instanceof MetadataIndexingField) 1830 { 1831 String metadataPath = ((MetadataIndexingField) field).getMetadataPath(); 1832 1833 List<MetadataDefinition> definitions = getIndexingFieldDefinitions(this, metadataPath); 1834 1835 List<String> joinPaths = new ArrayList<>(); 1836 boolean localContentType = true; 1837 StringBuilder currentContentPath = new StringBuilder(); 1838 for (MetadataDefinition definition : definitions) 1839 { 1840 if (currentContentPath.length() > 0) 1841 { 1842 currentContentPath.append(ContentConstants.METADATA_PATH_SEPARATOR); 1843 } 1844 currentContentPath.append(definition.getName()); 1845 1846 if (definition.getType() == MetadataType.CONTENT || definition.getType() == MetadataType.SUB_CONTENT) 1847 { 1848 if (!localContentType) 1849 { 1850 joinPaths.add(currentContentPath.toString()); 1851 currentContentPath.setLength(0); 1852 1853 String cTypeId = definition.getContentType(); 1854 1855 Map<String, List<String>> cTypeRefs; 1856 if (references.containsKey(cTypeId)) 1857 { 1858 cTypeRefs = references.get(cTypeId); 1859 } 1860 else 1861 { 1862 cTypeRefs = new LinkedHashMap<>(); 1863 references.put(cTypeId, cTypeRefs); 1864 } 1865 1866 cTypeRefs.put(field.getName(), new ArrayList<>(joinPaths)); 1867 } 1868 1869 localContentType = false; 1870 } 1871 } 1872 } 1873 } 1874 1875 _indexingModel.setReferences(references); 1876 } 1877 1878 /** 1879 * Get the list of metadata definitions "traversed" from the initial content type to the given metadata. 1880 * @param initialContentType the initial content type. 1881 * @param metadataPath the compound metadata path. 1882 * @return the list of metadata definitions. 1883 */ 1884 protected List<MetadataDefinition> getIndexingFieldDefinitions(ContentType initialContentType, String metadataPath) 1885 { 1886 List<MetadataDefinition> definitions = new ArrayList<>(); 1887 1888 String[] pathSegments = StringUtils.split(metadataPath, ContentConstants.METADATA_PATH_SEPARATOR); 1889 1890 if (pathSegments.length > 0) 1891 { 1892 IndexingModel indexingModel = _contentTypesHelper.getIndexingModel(new String[] {initialContentType.getId()}, new String[0]); 1893 1894 IndexingField refField = indexingModel.getField(pathSegments[0]); 1895 1896 MetadataDefinition metadataDef = null; 1897 if (refField != null && refField instanceof CustomMetadataIndexingField) 1898 { 1899 metadataDef = ((CustomMetadataIndexingField) refField).getMetadataDefinition(); 1900 } 1901 else 1902 { 1903 metadataDef = initialContentType.getMetadataDefinition(pathSegments[0]); 1904 } 1905 1906 if (metadataDef != null) 1907 { 1908 definitions.add(metadataDef); 1909 } 1910 1911 for (int i = 1; i < pathSegments.length && metadataDef != null; i++) 1912 { 1913 if (metadataDef.getType() == MetadataType.CONTENT || metadataDef.getType() == MetadataType.SUB_CONTENT) 1914 { 1915 String refCTypeId = metadataDef.getContentType(); 1916 if (refCTypeId != null && _cTypeEP.hasExtension(refCTypeId)) 1917 { 1918 ContentType refCType = _cTypeEP.getExtension(refCTypeId); 1919 1920 List<MetadataDefinition> followingDefs = getIndexingFieldDefinitions(refCType, StringUtils.join(pathSegments, '/', i, pathSegments.length)); 1921 definitions.addAll(followingDefs); 1922 1923 return definitions; 1924 } 1925 } 1926 else 1927 { 1928 refField = indexingModel.getField(pathSegments[i]); 1929 if (refField != null && refField instanceof CustomMetadataIndexingField) 1930 { 1931 metadataDef = ((CustomMetadataIndexingField) refField).getMetadataDefinition(); 1932 } 1933 else 1934 { 1935 metadataDef = metadataDef.getMetadataDefinition(pathSegments[i]); 1936 } 1937 definitions.add(metadataDef); 1938 } 1939 } 1940 } 1941 1942 return definitions; 1943 } 1944 1945 @Override 1946 public List<ContentValidator> getGlobalValidators() 1947 { 1948 return Collections.unmodifiableList(_globalValidators); 1949 } 1950 1951 @Override 1952 public RichTextUpdater getRichTextUpdater() 1953 { 1954 return _richTextUpdater; 1955 } 1956 1957 @Override 1958 public Set<String> getMetadataNames() 1959 { 1960 return Collections.unmodifiableSet(_metadata.keySet()); 1961 } 1962 1963 @Override 1964 public MetadataDefinition getMetadataDefinition(String metadataName) 1965 { 1966 return _metadata.get(metadataName); 1967 } 1968 1969 @Override 1970 public boolean hasMetadataDefinition(String metadataName) 1971 { 1972 return _metadata.containsKey(metadataName); 1973 } 1974 1975 @Override 1976 public MetadataDefinition getMetadataDefinitionByPath(String metadataPath) 1977 { 1978 String[] pathSegments = StringUtils.split(metadataPath, ContentConstants.METADATA_PATH_SEPARATOR); 1979 1980 if (pathSegments.length == 0) 1981 { 1982 return null; 1983 } 1984 1985 MetadataDefinition metadataDef = _metadata.get(pathSegments[0]); 1986 1987 for (int i = 1; i < pathSegments.length && metadataDef != null; i++) 1988 { 1989 metadataDef = metadataDef.getMetadataDefinition(pathSegments[i]); 1990 } 1991 1992 return metadataDef; 1993 } 1994 1995 @Override 1996 public IndexingModel getIndexingModel() 1997 { 1998 return _indexingModel; 1999 } 2000 2001 @Override 2002 public boolean canRead(Content content, MetadataDefinition metadataDef) throws AmetysRepositoryException 2003 { 2004 Restrictions restrictions = _getRestrictionsForPath(metadataDef); 2005 2006 FirstRestrictionsChecksState state = _restrictedModelItemHelper._doFirstRestrictionsChecks(content, restrictions, true); 2007 if (!FirstRestrictionsChecksState.UNKNOWN.equals(state)) 2008 { 2009 return FirstRestrictionsChecksState.TRUE.equals(state); 2010 } 2011 2012 String[] pathSegments = StringUtils.split(metadataDef.getId(), ContentConstants.METADATA_PATH_SEPARATOR); 2013 if (pathSegments.length > 1) 2014 { 2015 // Check read access on parent metadata definition 2016 String parentMetadataPath = metadataDef.getId().substring(0, metadataDef.getId().lastIndexOf(ContentConstants.METADATA_PATH_SEPARATOR)); 2017 MetadataDefinition parentMetadataDef = _contentTypesHelper.getMetadataDefinition(parentMetadataPath, content); 2018 return canRead(content, parentMetadataDef); 2019 } 2020 2021 return true; 2022 } 2023 2024 @Override 2025 public boolean canWrite(Content content, MetadataDefinition metadataDef) throws AmetysRepositoryException 2026 { 2027 Restrictions restrictions = _getRestrictionsForPath(metadataDef); 2028 2029 FirstRestrictionsChecksState state = _restrictedModelItemHelper._doFirstRestrictionsChecks(content, restrictions, false); 2030 if (!FirstRestrictionsChecksState.UNKNOWN.equals(state)) 2031 { 2032 return FirstRestrictionsChecksState.TRUE.equals(state); 2033 } 2034 2035 String[] pathSegments = StringUtils.split(metadataDef.getId(), ContentConstants.METADATA_PATH_SEPARATOR); 2036 if (pathSegments.length > 1) 2037 { 2038 // Check write access on parent metadata definition 2039 String parentMetadataPath = metadataDef.getId().substring(0, metadataDef.getId().lastIndexOf(ContentConstants.METADATA_PATH_SEPARATOR)); 2040 MetadataDefinition parentMetadataDef = _contentTypesHelper.getMetadataDefinition(parentMetadataPath, content); 2041 return canWrite(content, parentMetadataDef); 2042 } 2043 2044 return canRead(content, metadataDef); 2045 } 2046 2047 @Override 2048 public Set<String> getTags() 2049 { 2050 return Collections.unmodifiableSet(_tags); 2051 } 2052 2053 @Override 2054 public Set<String> getInheritableTags() 2055 { 2056 return Collections.unmodifiableSet(_inheritableTags); 2057 } 2058 2059 @Override 2060 public boolean hasTag(String tagName) 2061 { 2062 return _tags.contains(tagName); 2063 } 2064 2065 @Override 2066 public boolean isPrivate() 2067 { 2068 return hasTag(TAG_PRIVATE); 2069 } 2070 2071 @Override 2072 public boolean isAbstract() 2073 { 2074 return _abstract; 2075 } 2076 2077 @Override 2078 public boolean isSimple() 2079 { 2080 return _isSimple; 2081 } 2082 2083 @Override 2084 public boolean isReferenceTable() 2085 { 2086 return hasTag(TAG_REFERENCE_TABLE) || hasTag(TAG_RENDERABLE_FERENCE_TABLE); 2087 } 2088 2089 @Override 2090 public boolean isMultilingual() 2091 { 2092 return _isMultilingual; 2093 } 2094 2095 @Override 2096 public boolean isMixin() 2097 { 2098 return hasTag(TAG_MIXIN); 2099 } 2100 2101 @Override 2102 public Set<String> getConfiguredDefaultWorkflowNames() 2103 { 2104 return _configuredWorkflowNames; 2105 } 2106 2107 @Override 2108 public Optional<String> getDefaultWorkflowName() 2109 { 2110 return _defaultWorkflowName; 2111 } 2112 2113 @Override 2114 public String getRight() 2115 { 2116 return _right; 2117 } 2118 2119 @Override 2120 public void saxContentTypeAdditionalData(org.xml.sax.ContentHandler contentHandler, Content content) throws AmetysRepositoryException, SAXException 2121 { 2122 // Nothing 2123 } 2124 2125 @Override 2126 public Map<String, Object> getAdditionalData(Content content) 2127 { 2128 return new HashMap<>(); 2129 } 2130 2131 /** 2132 * Retrieves the restrictions for a given path. 2133 * @param metadataDef the metadata definition. 2134 * @return the restrictions or <code>null</code> if not found. 2135 */ 2136 protected Restrictions _getRestrictionsForPath(MetadataDefinition metadataDef) 2137 { 2138 if (metadataDef != null && metadataDef instanceof RestrictedDefinition) 2139 { 2140 return ((RestrictedDefinition) metadataDef).getRestrictions(); 2141 } 2142 2143 // Not found 2144 return null; 2145 } 2146 2147 @Override 2148 public String toString() 2149 { 2150 return "'" + getId() + "'"; 2151 } 2152 2153 /** 2154 * Restricted definition. 2155 * @deprecated use {@link RestrictedModelItem} instead 2156 */ 2157 @Deprecated 2158 protected interface RestrictedDefinition 2159 { 2160 /** 2161 * Provides the restrictions. 2162 * @return the restrictions. 2163 */ 2164 Restrictions getRestrictions(); 2165 } 2166 2167 /** 2168 * Definition with semantic annotations 2169 */ 2170 protected interface AnnotableDefinition 2171 { 2172 /** 2173 * Provides the semantic annotations 2174 * @return the semantic annotations 2175 */ 2176 List<SemanticAnnotation> getSemanticAnnotations(); 2177 2178 /** 2179 * Set the semantic annotations 2180 * @param annotations the semantic annotations to set 2181 */ 2182 void setSemanticAnnotations(List<SemanticAnnotation> annotations); 2183 } 2184 2185 /** 2186 * Internal {@link MetadataDefinition} storage contains instances of this class. 2187 * @deprecated Use {@link AttributeDefinition} instead 2188 */ 2189 @Deprecated 2190 protected static class RestrictedMetadataDefinition extends MetadataDefinition implements RestrictedDefinition 2191 { 2192 /** Restrictions. */ 2193 protected Restrictions _restrictions = new Restrictions(); 2194 2195 @Override 2196 public Restrictions getRestrictions() 2197 { 2198 return _restrictions; 2199 } 2200 } 2201 2202 /** 2203 * Internal {@link MetadataDefinition} storage contains instances of this class. 2204 * @deprecated Use {@link RichTextAttributeDefinition} instead 2205 */ 2206 @Deprecated 2207 protected static class RestrictedRichTextDefinition extends RichTextMetadataDefinition implements RestrictedDefinition 2208 { 2209 /** Restrictions. */ 2210 protected Restrictions _restrictions = new Restrictions(); 2211 2212 @Override 2213 public Restrictions getRestrictions() 2214 { 2215 return _restrictions; 2216 } 2217 } 2218 2219 /** 2220 * Internal {@link org.ametys.cms.contenttype.RepeaterDefinition} storage contains instances of this class. 2221 * @deprecated Use {@link ContentRestrictedRepeaterDefinition} instead 2222 */ 2223 @Deprecated 2224 protected static class RestrictedRepeaterDefinition extends org.ametys.cms.contenttype.RepeaterDefinition implements RestrictedDefinition 2225 { 2226 /** Restrictions. */ 2227 protected Restrictions _restrictions = new Restrictions(); 2228 2229 @Override 2230 public Restrictions getRestrictions() 2231 { 2232 return _restrictions; 2233 } 2234 } 2235 2236 /** 2237 * {@link RestrictedMetadataDefinition} and {@link RestrictedRepeaterDefinition} parser. 2238 * @deprecated Use {@link ContentAttributeDefinitionParser} instead 2239 */ 2240 @Deprecated 2241 protected class MetadataAndRepeaterDefinitionParser extends AbstractParameterParser<MetadataDefinition, MetadataType> 2242 { 2243 /** Parent prefix. */ 2244 protected String _parentPrefix = ""; 2245 2246 /** 2247 * Creates an {@link MetadataAndRepeaterDefinitionParser}. 2248 * @param enumeratorManager the enumerator component manager. 2249 * @param validatorManager the validator component manager. 2250 */ 2251 public MetadataAndRepeaterDefinitionParser(ThreadSafeComponentManager<org.ametys.runtime.parameter.Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager) 2252 { 2253 super(enumeratorManager, validatorManager); 2254 } 2255 2256 @Override 2257 protected MetadataDefinition _createParameter(Configuration metadataConfiguration) throws ConfigurationException 2258 { 2259 String defName = metadataConfiguration.getName(); 2260 2261 if (defName.equals("metadata")) 2262 { 2263 String defType = metadataConfiguration.getAttribute("type"); 2264 if (defType.equals("rich-text") || defType.equals("html-rich-text")) 2265 { 2266 return new RestrictedRichTextDefinition(); 2267 } 2268 else 2269 { 2270 return new RestrictedMetadataDefinition(); 2271 } 2272 2273 } 2274 else if (defName.equals("repeater")) 2275 { 2276 return new RestrictedRepeaterDefinition(); 2277 } 2278 2279 throw new ConfigurationException("Unsupported metadata or repeater configuration", metadataConfiguration); 2280 } 2281 2282 @Override 2283 protected String _parseId(Configuration metadataConfiguration) throws ConfigurationException 2284 { 2285 String metadataName = metadataConfiguration.getAttribute("name"); 2286 2287 if (!metadataName.matches("^[a-zA-Z]((?!__)[a-zA-Z0-9_-])*$")) 2288 { 2289 throw new ConfigurationException("Invalid metadata name: " + metadataName + ". The metadata name must start with a letter and must contain only letters, digits, underscore or dash characters.", metadataConfiguration); 2290 } 2291 2292 return _parentPrefix + (_parentPrefix.length() > 0 ? ContentConstants.METADATA_PATH_SEPARATOR : "") + metadataName; 2293 } 2294 2295 @Override 2296 protected MetadataType _parseType(Configuration metadataConfiguration) throws ConfigurationException 2297 { 2298 if (metadataConfiguration.getName().equals("repeater")) 2299 { 2300 // A repeater is a composite 2301 return MetadataType.COMPOSITE; 2302 } 2303 else if (metadataConfiguration.getAttribute("type").equals("html-rich-text")) 2304 { 2305 return MetadataType.RICH_TEXT; 2306 } 2307 2308 try 2309 { 2310 return MetadataType.valueOf(metadataConfiguration.getAttribute("type").toUpperCase().replaceAll("-", "_")); 2311 } 2312 catch (IllegalArgumentException e) 2313 { 2314 throw new ConfigurationException("Invalid type", metadataConfiguration, e); 2315 } 2316 } 2317 2318 @Override 2319 protected I18nizableText _parseI18nizableText(Configuration config, String pluginName, String name) throws ConfigurationException 2320 { 2321 // Override i18n parsing to use the default catalog (which can be application for automatic content types) 2322 return I18nizableText.parseI18nizableText(config.getChild(name), getDefaultCatalog()); 2323 } 2324 2325 @Override 2326 protected Object _parseDefaultValue(Configuration metadataConfiguration, MetadataDefinition metadataDef) 2327 { 2328 String value; 2329 2330 Configuration childNode = metadataConfiguration.getChild("default-value", false); 2331 if (childNode == null) 2332 { 2333 value = null; 2334 } 2335 else 2336 { 2337 value = childNode.getValue(""); 2338 } 2339 2340 return value; 2341 } 2342 2343 @Override 2344 protected void _additionalParsing(ServiceManager manager, String pluginName, Configuration metadataConfiguration, String metadataId, MetadataDefinition metadataDefinition) throws ConfigurationException 2345 { 2346 super._additionalParsing(manager, pluginName, metadataConfiguration, metadataId, metadataDefinition); 2347 String metadataName = metadataConfiguration.getAttribute("name"); 2348 2349 metadataDefinition.setReferenceContentType(_id); 2350 metadataDefinition.setName(metadataName); 2351 metadataDefinition.setMultiple(metadataConfiguration.getAttributeAsBoolean("multiple", false)); 2352 // Use default transformer (docbook) 2353 metadataDefinition.setRichTextTransformer(_richTextTransformer); 2354 // Use docbook outgoing consistency extractor 2355 metadataDefinition.setRichTextOutgoingReferencesExtractor(_richTextOutgoingReferencesExtractor); 2356 2357 if (metadataDefinition instanceof org.ametys.cms.contenttype.RepeaterDefinition) 2358 { 2359 org.ametys.cms.contenttype.RepeaterDefinition repeaterDefinition = (org.ametys.cms.contenttype.RepeaterDefinition) metadataDefinition; 2360 2361 _parseRepeaterDefinition(manager, pluginName, metadataConfiguration, repeaterDefinition); 2362 } 2363 2364 if (metadataDefinition instanceof AnnotableDefinition) 2365 { 2366 AnnotableDefinition definitionWithSemAnnotations = (AnnotableDefinition) metadataDefinition; 2367 2368 _parseDefinitionWithAnnotations(manager, pluginName, metadataConfiguration, definitionWithSemAnnotations); 2369 } 2370 2371 _restrictedModelItemHelper.populateRestrictions(metadataConfiguration, ((RestrictedDefinition) metadataDefinition).getRestrictions()); 2372 2373 if (metadataDefinition.getType() == MetadataType.CONTENT || metadataDefinition.getType() == MetadataType.SUB_CONTENT) 2374 { 2375 // Content metadata: parse and set the content type restriction. 2376 String contentType = metadataConfiguration.getAttribute("contentType", null); 2377 if (StringUtils.isNotEmpty(contentType)) 2378 { 2379 metadataDefinition.setContentType(contentType); 2380 } 2381 2382 if (metadataDefinition.getType() == MetadataType.CONTENT) 2383 { 2384 _parseContentRelations(metadataConfiguration, metadataDefinition, contentType); 2385 } 2386 } 2387 else if (metadataDefinition.getType() == MetadataType.COMPOSITE) 2388 { 2389 for (Configuration childConfig : metadataConfiguration.getChildren()) 2390 { 2391 String childName = childConfig.getName(); 2392 2393 if (childName.equals("metadata") || childName.equals("repeater")) 2394 { 2395 String oldParentPrefix = _parentPrefix; 2396 // Stack new parent prefix 2397 _parentPrefix = _parentPrefix + (_parentPrefix.length() > 0 ? ContentConstants.METADATA_PATH_SEPARATOR : "") + metadataName; 2398 MetadataDefinition subMetaDef = parseParameter(manager, pluginName, childConfig); 2399 // Restore parent prefix 2400 _parentPrefix = oldParentPrefix; 2401 2402 if (!metadataDefinition.addMetadata(subMetaDef)) 2403 { 2404 throw new ConfigurationException("Metadata with name " + subMetaDef.getName() + " is already defined", childConfig); 2405 } 2406 } 2407 } 2408 } 2409 } 2410 2411 /** 2412 * Parse content mutual relations. 2413 * @param metadataConfiguration the metadata configuration. 2414 * @param metadataDefinition the metadata definition to fill. 2415 * @param contentType the content type. 2416 */ 2417 protected void _parseContentRelations(Configuration metadataConfiguration, MetadataDefinition metadataDefinition, String contentType) 2418 { 2419 String invert = metadataConfiguration.getAttribute("invert", null); 2420 if (StringUtils.isNotEmpty(invert)) 2421 { 2422 metadataDefinition.setInvertRelationPath(invert); 2423 2424 boolean forceInvert = metadataConfiguration.getAttributeAsBoolean("forceInvert", false); 2425 metadataDefinition.setForceInvert(forceInvert); 2426 } 2427 } 2428 2429 /** 2430 * Parses the repeater definition. 2431 * @param manager the service manager. 2432 * @param pluginName the plugin name declaring this parameter. 2433 * @param metadataConfiguration the metadata configuration to use. 2434 * @param repeaterDefinition the repeater definition. 2435 * @throws ConfigurationException if the configuration is not valid. 2436 */ 2437 protected void _parseRepeaterDefinition(ServiceManager manager, String pluginName, Configuration metadataConfiguration, org.ametys.cms.contenttype.RepeaterDefinition repeaterDefinition) throws ConfigurationException 2438 { 2439 repeaterDefinition.setAddLabel(_parseI18nizableText(metadataConfiguration, pluginName, "add-label")); 2440 repeaterDefinition.setDeleteLabel(_parseI18nizableText(metadataConfiguration, pluginName, "del-label")); 2441 repeaterDefinition.setHeaderLabel(metadataConfiguration.getChild("header-label").getValue(null)); 2442 repeaterDefinition.setInitialSize(metadataConfiguration.getAttributeAsInteger("initial-size", 0)); 2443 repeaterDefinition.setMinSize(metadataConfiguration.getAttributeAsInteger("min-size", 0)); 2444 repeaterDefinition.setMaxSize(metadataConfiguration.getAttributeAsInteger("max-size", -1)); 2445 } 2446 2447 /** 2448 * Parses the definition with semantic annotations. 2449 * @param manager the service manager. 2450 * @param pluginName the plugin name declaring this parameter. 2451 * @param metadataConfiguration the metadata configuration to use. 2452 * @param annotableDefinition the metadata definition 2453 * @throws ConfigurationException if the configuration is not valid. 2454 */ 2455 protected void _parseDefinitionWithAnnotations(ServiceManager manager, String pluginName, Configuration metadataConfiguration, AnnotableDefinition annotableDefinition) throws ConfigurationException 2456 { 2457 Configuration annotationsConfiguration = metadataConfiguration.getChild("annotations"); 2458 List<SemanticAnnotation> semAnnotations = _parseSemAnnotations(pluginName, annotationsConfiguration); 2459 annotableDefinition.setSemanticAnnotations(semAnnotations); 2460 } 2461 2462 /** 2463 * Extract the list of the declared annotations 2464 * @param pluginName the plugin name declaring this parameter. 2465 * @param annotationsConfiguration the annotations configuration to use. 2466 * @return the list of the declared annotations 2467 * @throws ConfigurationException if the configuration is not valid. 2468 */ 2469 protected List<SemanticAnnotation> _parseSemAnnotations(String pluginName, Configuration annotationsConfiguration) throws ConfigurationException 2470 { 2471 List<SemanticAnnotation> annotations = new ArrayList<>(); 2472 2473 for (Configuration annotationConfig : annotationsConfiguration.getChildren("annotation")) 2474 { 2475 String id = annotationConfig.getAttribute("name"); 2476 2477 if (!_getAnnotationNamePattern().matcher(id).matches()) 2478 { 2479 throw new ConfigurationException("Invalid annonation name '" + id + "'. This value is not permitted: only [a-zA-Z][a-zA-Z0-9-_]* are allowed."); 2480 } 2481 2482 I18nizableText label = _parseI18nizableText(annotationConfig, pluginName, "label"); 2483 I18nizableText description = _parseI18nizableText(annotationConfig, pluginName, "description"); 2484 annotations.add(new SemanticAnnotation(id, label, description)); 2485 } 2486 2487 return annotations; 2488 } 2489 2490 /** 2491 * Get the annotation name pattern to test validity. 2492 * @return The annotation name pattern. 2493 */ 2494 protected Pattern _getAnnotationNamePattern() 2495 { 2496 if (__annotationNamePattern == null) 2497 { 2498 // [a-zA-Z][a-zA-Z0-9_]* 2499 __annotationNamePattern = Pattern.compile("[a-z][a-z0-9-_]*", Pattern.CASE_INSENSITIVE); 2500 } 2501 2502 return __annotationNamePattern; 2503 } 2504 } 2505 2506 public Optional<ContentAttributeDefinition> getParentAttributeDefinition() 2507 { 2508 return Optional.ofNullable(_parentAttributeDefinition); 2509 } 2510 2511 public Collection<ModelItem> getModelItems() 2512 { 2513 return Collections.unmodifiableCollection(_modelItems.values()); 2514 } 2515 2516 public Set<String> getViewNames(boolean includeInternals) 2517 { 2518 if (includeInternals) 2519 { 2520 return Collections.unmodifiableSet(_views.keySet()); 2521 } 2522 else 2523 { 2524 return _views.entrySet() 2525 .stream() 2526 .filter(entry -> !entry.getValue().isInternal()) 2527 .map(Map.Entry::getKey) 2528 .collect(Collectors.toSet()); 2529 } 2530 } 2531 2532 public View getView(String viewName) 2533 { 2534 return _views.get(viewName); 2535 } 2536 2537 public Optional<Configuration> getViewConfiguration(String viewName) 2538 { 2539 return Optional.ofNullable(_viewConfigurations.get(viewName)); 2540 } 2541 2542 public String getFamilyId() 2543 { 2544 return ContentTypeExtensionPoint.ROLE; 2545 } 2546}