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