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.repository.Content; 060import org.ametys.cms.repository.WorkflowAwareContent; 061import org.ametys.cms.transformation.RichTextTransformer; 062import org.ametys.cms.transformation.docbook.DocbookOutgoingReferencesExtractor; 063import org.ametys.cms.transformation.docbook.DocbookTransformer; 064import org.ametys.core.right.RightManager; 065import org.ametys.core.right.RightManager.RightResult; 066import org.ametys.core.user.CurrentUserProvider; 067import org.ametys.core.user.UserIdentity; 068import org.ametys.plugins.repository.AmetysRepositoryException; 069import org.ametys.plugins.workflow.support.WorkflowProvider; 070import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 071import org.ametys.runtime.i18n.I18nizableText; 072import org.ametys.runtime.parameter.AbstractParameterParser; 073import org.ametys.runtime.parameter.Enumerator; 074import org.ametys.runtime.parameter.StaticEnumerator; 075import org.ametys.runtime.parameter.Validator; 076import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 077 078import com.google.common.collect.HashMultimap; 079import com.google.common.collect.Multimap; 080import com.opensymphony.workflow.spi.Step; 081 082/** 083 * Type of content which is retrieved from a XML configuration. 084 * TODO document xml configuration 085 * ... 086 * Provides access based on rights and current workflow steps.<p> 087 * It used a configuration file with the following format: 088 * <code><br> 089 * <restrict-to><br> 090 * [<right type="read|write" id="RIGHT_ID"/>]* 091 * <!-- logical OR between several right id of the same type --><br> 092 * [<workflow type="read|write" step="3"/>]* 093 * <!-- logical OR between several workflow step of the same type --><br> 094 * [<cannot type="read|write"/>]*<br> 095 * </restrict-to><br> 096 * </code> 097 */ 098public class DefaultContentType extends AbstractContentTypeDescriptor implements ContentType, Contextualizable, ThreadSafe, Disposable 099{ 100 /** Suffix for global validator role. */ 101 protected static final String __GLOBAL_VALIDATOR_ROLE_PREFIX = "_globalvalidator"; 102 103 static Pattern __annotationNamePattern; 104 105 /** Metadata definitions. */ 106 protected Map<String, MetadataDefinition> _metadata = new LinkedHashMap<>(); 107 /** The right needed to create a content of this type, or null if no right is needed. */ 108 protected String _right; 109 /** The abstract property */ 110 protected boolean _abstract; 111 /** The tags */ 112 protected Set<String> _tags; 113 /** The parent metadata name */ 114 protected String _parentMetadataName; 115 /** Service manager. */ 116 protected ServiceManager _manager; 117 /** Avalon Context. */ 118 protected Context _context; 119 /** Cocoon Context */ 120 protected org.apache.cocoon.environment.Context _cocoonContext; 121 /** The workflow provider */ 122 protected WorkflowProvider _workflowProvider; 123 /** The rights manager. */ 124 protected RightManager _rightManager; 125 /** Current user provider. */ 126 protected CurrentUserProvider _currentUserProvider; 127 /** Default rich text transformer. */ 128 protected RichTextTransformer _richTextTransformer; 129 /** Docbook (rich text) outgoing references extractor. */ 130 protected RichTextOutgoingReferencesExtractor _richTextOutgoingReferencesExtractor; 131 /** Potential global validators. */ 132 protected List<ContentValidator> _globalValidators; 133 /** Potentiel richtext updater */ 134 protected RichTextUpdater _richTextUpdater; 135 /** Indexing model */ 136 protected IndexingModel _indexingModel; 137 /** The helper component for hierarchical simple contents */ 138 protected HierarchicalReferenceTablesHelper _hierarchicalSimpleContentsHelper; 139 140 // ComponentManager pour les Validator 141 private ThreadSafeComponentManager<Validator> _validatorManager; 142 143 // ComponentManager pour les Global Validators 144 private ThreadSafeComponentManager<ContentValidator> _globalValidatorsManager; 145 146 //ComponentManager pour les Enumerator 147 private ThreadSafeComponentManager<Enumerator> _enumeratorManager; 148 149 // ComponentManager pour les CustomIndexingField 150 private ThreadSafeComponentManager<CustomIndexingField> _customFieldManager; 151 152 // ComponentManager pour les CustomMetadataIndexingField 153 private ThreadSafeComponentManager<CustomMetadataIndexingField> _customMetadataIndexingFieldManager; 154 155 private boolean _isSimple; 156 157 private boolean _isMultilingual; 158 159 @Override 160 public void service(ServiceManager manager) throws ServiceException 161 { 162 super.service(manager); 163 _manager = manager; 164 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 165 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 166 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 167 _richTextTransformer = (RichTextTransformer) manager.lookup(DocbookTransformer.ROLE); 168 _richTextOutgoingReferencesExtractor = (RichTextOutgoingReferencesExtractor) manager.lookup(DocbookOutgoingReferencesExtractor.ROLE); 169 _richTextUpdater = (RichTextUpdater) manager.lookup(DocbookRichTextUpdater.ROLE); 170 _hierarchicalSimpleContentsHelper = (HierarchicalReferenceTablesHelper) manager.lookup(HierarchicalReferenceTablesHelper.ROLE); 171 } 172 173 @Override 174 public void contextualize(Context context) throws ContextException 175 { 176 _context = context; 177 _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 178 } 179 180 @Override 181 public void dispose() 182 { 183 _validatorManager.dispose(); 184 _validatorManager = null; 185 186 _globalValidatorsManager.dispose(); 187 _globalValidatorsManager = null; 188 189 _enumeratorManager.dispose(); 190 _enumeratorManager = null; 191 192 _customFieldManager.dispose(); 193 _customFieldManager = null; 194 195 _customMetadataIndexingFieldManager.dispose(); 196 _customMetadataIndexingFieldManager = null; 197 } 198 199 @Override 200 protected Configuration getRootConfiguration(Configuration configuration) 201 { 202 return configuration.getChild("content-type"); 203 } 204 205 @Override 206 protected Configuration getOverridenConfiguration() throws ConfigurationException 207 { 208 Configuration overridenConf = null; 209 File ctFile = new File(_cocoonContext.getRealPath("/WEB-INF/param/content-types/_override/" + _id + ".xml")); 210 211 if (ctFile.exists()) 212 { 213 try (InputStream is = new FileInputStream(ctFile)) 214 { 215 216 overridenConf = new DefaultConfigurationBuilder(true).build(is); 217 } 218 catch (Exception ex) 219 { 220 throw new ConfigurationException("Unable to parse overriden configuration for content type '" + _id + "' at WEB-INF/param/content-types/_override/" + _id + ".xml", overridenConf, ex); 221 } 222 } 223 224 return overridenConf; 225 } 226 227 @Override 228 public void configure(Configuration configuration) throws ConfigurationException 229 { 230 _validatorManager = new ThreadSafeComponentManager<>(); 231 _validatorManager.setLogger(getLogger()); 232 _validatorManager.contextualize(_context); 233 _validatorManager.service(_manager); 234 235 _globalValidatorsManager = new ThreadSafeComponentManager<>(); 236 _globalValidatorsManager.setLogger(getLogger()); 237 _globalValidatorsManager.contextualize(_context); 238 _globalValidatorsManager.service(_manager); 239 240 _enumeratorManager = new ThreadSafeComponentManager<>(); 241 _enumeratorManager.setLogger(getLogger()); 242 _enumeratorManager.contextualize(_context); 243 _enumeratorManager.service(_manager); 244 245 _customFieldManager = new ThreadSafeComponentManager<>(); 246 _customFieldManager.setLogger(getLogger()); 247 _customFieldManager.contextualize(_context); 248 _customFieldManager.service(_manager); 249 250 _customMetadataIndexingFieldManager = new ThreadSafeComponentManager<>(); 251 _customMetadataIndexingFieldManager.setLogger(getLogger()); 252 _customMetadataIndexingFieldManager.contextualize(_context); 253 _customMetadataIndexingFieldManager.service(_manager); 254 255 Configuration rootConfiguration = getRootConfiguration(configuration); 256 257 _abstract = rootConfiguration.getAttributeAsBoolean("abstract", false); 258 259 _configureSuperTypes(rootConfiguration); 260 261 _configureLabels(rootConfiguration); 262 _configureIcons(rootConfiguration); 263 264 _configureCSSFiles(rootConfiguration); 265 266 // Tags 267 _tags = new HashSet<>(); 268 269 if (rootConfiguration.getChild("tags", false) != null) 270 { 271 if (rootConfiguration.getChild("tags").getAttributeAsBoolean("inherited", false)) 272 { 273 // Get tags from super types 274 for (String superTypeId : _superTypeIds) 275 { 276 ContentType superType = _cTypeEP.getExtension(superTypeId); 277 _tags.addAll(superType.getTags()); 278 } 279 } 280 _tags.addAll(_parseTags (rootConfiguration.getChild("tags"))); 281 } 282 283 // Rights 284 _right = rootConfiguration.getChild("right").getValue(null); 285 286 _isSimple = true; 287 for (String superTypeId : _superTypeIds) 288 { 289 ContentType superType = _cTypeEP.getExtension(superTypeId); 290 if (!superType.isSimple()) 291 { 292 _isSimple = false; 293 break; 294 } 295 } 296 297 _isMultilingual = false; 298 for (String superTypeId : _superTypeIds) 299 { 300 ContentType superType = _cTypeEP.getExtension(superTypeId); 301 if (superType.isMultilingual()) 302 { 303 _isMultilingual = true; 304 break; 305 } 306 } 307 308 // Metadata definitions 309 _configureMetadataDefinitions (rootConfiguration); 310 311 // Parent content type 312 _configureParentContentType(rootConfiguration); 313 314 // Metadata sets 315 _configureMetadataSets (rootConfiguration); 316 317 if (!_abstract && !hasTag(TAG_MIXIN)) 318 { 319 if (!_allMetadataSetsForView.containsKey("details")) 320 { 321 throw new ConfigurationException("Mandatory metadata-set for view named 'details' is missing for content type " + _id); 322 } 323 if (!_allMetadataSetsForView.containsKey("main")) 324 { 325 throw new ConfigurationException("Mandatory metadata-set for view named 'main' is missing for content type " + _id); 326 } 327 if (!_allMetadataSetsForEdition.containsKey("main")) 328 { 329 throw new ConfigurationException("Mandatory metadata-set for edition named 'main' is missing for content type " + _id); 330 } 331 } 332 333 // Global validators 334 _configureGlobalValidators (rootConfiguration); 335 336 // Indexing model 337 _configureIndexingModel (rootConfiguration); 338 } 339 340 /** 341 * Configure metadata definitions 342 * @param mainConfig The content type configuration 343 * @throws ConfigurationException if an error occurred 344 */ 345 protected void _configureMetadataDefinitions (Configuration mainConfig) throws ConfigurationException 346 { 347 MetadataAndRepeaterDefinitionParser defParser = new MetadataAndRepeaterDefinitionParser(_enumeratorManager, _validatorManager); 348 349 // First, get metadata from super type if applicable. 350 _metadata.putAll(_contentTypesHelper.getMetadataDefinitions(_superTypeIds)); 351 352 Map<String, Configuration> metadataConfiguration = new LinkedHashMap<>(); 353 354 _getApplicableMetadata(mainConfig, metadataConfiguration, false); 355 356 Configuration overriddenConfig = getOverridenConfiguration(); 357 if (overriddenConfig != null) 358 { 359 _getApplicableMetadata(overriddenConfig, metadataConfiguration, true); 360 } 361 362 // Then, parse own metadata 363 _parseAllMetadatas(metadataConfiguration, defParser); 364 365 try 366 { 367 defParser.lookupComponents(); 368 } 369 catch (Exception e) 370 { 371 throw new ConfigurationException("Unable to lookup parameter local components", overriddenConfig, e); 372 } 373 374 } 375 376 /** 377 * Configures the "parent" content type. 378 * This must not be confounded with the super types. (See {@link AbstractContentTypeDescriptor#_configureSuperTypes(Configuration)}) 379 * @param mainConfig The main configuration 380 * @throws ConfigurationException if an error occured 381 */ 382 protected void _configureParentContentType(Configuration mainConfig) throws ConfigurationException 383 { 384 Configuration parentCTypeConf = mainConfig.getChild("parent-ref", false); 385 if (parentCTypeConf != null) 386 { 387 // Check this content type is a reference table 388 if (!isReferenceTable()) 389 { 390 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()); 391 return; 392 } 393 394 String refMetadataName = parentCTypeConf.getAttribute("name"); 395 // Check valid reference of metadata 396 if (!_metadata.containsKey(refMetadataName)) 397 { 398 getLogger().error("The 'parent-ref' tag for content type '{}' references the metadata '{}' but it does not exist. It will be ignored.", getId(), refMetadataName); 399 return; 400 } 401 402 MetadataDefinition metadataDef = _metadata.get(refMetadataName); 403 // Check metadata of type "content" 404 if (!MetadataType.CONTENT.equals(metadataDef.getType())) 405 { 406 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); 407 return; 408 } 409 else if (metadataDef.isMultiple()) 410 { 411 getLogger().error("The 'parent-ref' tag for content type '{}' references the metadata '{}' but it is a multiple metadata. It will be ignored.", getId(), refMetadataName); 412 return; 413 } 414 415 String parentCTypeName = metadataDef.getContentType(); 416 ContentType parentCType = _cTypeEP.getExtension(parentCTypeName); 417 418 if (parentCType == null) 419 { 420 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); 421 return; 422 } 423 // Check parent content type is private AND simple 424 else if (!parentCType.isPrivate()) 425 { 426 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); 427 return; 428 } 429 else if (!parentCType.isReferenceTable()) 430 { 431 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); 432 return; 433 } 434 435 if (!_hierarchicalSimpleContentsHelper.registerRelation(parentCType, this)) 436 { 437 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()); 438 return; 439 } 440 _parentMetadataName = refMetadataName; 441 } 442 } 443 444 /** 445 * Configure the global validators for content type 446 * @param config The content type configuration 447 * @throws ConfigurationException if an error occured 448 */ 449 @SuppressWarnings("unchecked") 450 protected void _configureGlobalValidators (Configuration config) throws ConfigurationException 451 { 452 _globalValidators = new ArrayList<>(); 453 List<String> globalValidatorsToLookup = new ArrayList<>(); 454 455 Configuration globalValidatorsConfig = config.getChild("global-validators", true); 456 457 boolean includeSuperTypeValidators = globalValidatorsConfig.getAttributeAsBoolean("include-from-supertype", true); 458 if (includeSuperTypeValidators) 459 { 460 for (String superTypeId : _superTypeIds) 461 { 462 ContentType cType = _cTypeEP.getExtension(superTypeId); 463 _globalValidators.addAll(cType.getGlobalValidators()); 464 } 465 } 466 467 int count = 1; 468 for (Configuration globalValidatorConfig : globalValidatorsConfig.getChildren("global-validator")) 469 { 470 String globalValidatorId = __GLOBAL_VALIDATOR_ROLE_PREFIX + count++; 471 String validatorClassName = globalValidatorConfig.getAttribute("class"); 472 473 try 474 { 475 Class validatorClass = Class.forName(validatorClassName); 476 _globalValidatorsManager.addComponent(_pluginName, null, globalValidatorId, validatorClass, globalValidatorConfig); 477 } 478 catch (Exception e) 479 { 480 throw new ConfigurationException("Unable to instantiate global validator for class: " + validatorClassName, e); 481 } 482 483 globalValidatorsToLookup.add(globalValidatorId); 484 } 485 486 try 487 { 488 _globalValidatorsManager.initialize(); 489 } 490 catch (Exception e) 491 { 492 throw new ConfigurationException("Unable to initialize global validator manager", e); 493 } 494 495 for (String validatorRole : globalValidatorsToLookup) 496 { 497 try 498 { 499 ContentValidator contentValidator = _globalValidatorsManager.lookup(validatorRole); 500 contentValidator.setContentType(this); 501 502 _globalValidators.add(contentValidator); 503 } 504 catch (ComponentException e) 505 { 506 throw new ConfigurationException("Unable to lookup global validator role: '" + validatorRole + "' for content type: " + this.getId(), e); 507 } 508 } 509 510 } 511 512 /** 513 * Fill a map of the applicable metadata configurations. 514 * @param config the content type configuration. 515 * @param metadataConfigurations the Map of metadata {@link Configuration}, indexed by name. 516 * @param allowOverride if true, encountering a metadata which has already been declared (based on its name) will replace it. Otherwise, an exception will be thrown. 517 * @throws ConfigurationException if an error occurs. 518 */ 519 protected void _getApplicableMetadata(Configuration config, Map<String, Configuration> metadataConfigurations, boolean allowOverride) throws ConfigurationException 520 { 521 for (Configuration childConfiguration : config.getChildren()) 522 { 523 String childName = childConfiguration.getName(); 524 525 if (childName.equals("metadata") || childName.equals("repeater")) 526 { 527 String metadataName = childConfiguration.getAttribute("name", ""); 528 529 if (!allowOverride && metadataConfigurations.containsKey(metadataName)) 530 { 531 throw new ConfigurationException("Metadata with name '" + metadataName + "' is already defined", childConfiguration); 532 } 533 else if (allowOverride && metadataConfigurations.containsKey(metadataName)) 534 { 535 _checkMetadataTypes (metadataConfigurations.get(metadataName), childConfiguration); 536 } 537 538 metadataConfigurations.put(metadataName, childConfiguration); 539 } 540 else if (childName.equals("dublin-core")) 541 { 542 metadataConfigurations.put("dc", childConfiguration); 543 } 544 } 545 } 546 547 /** 548 * Parse all metadata configurations. 549 * @param metadataConfigurations the metadata configurations. 550 * @param defParser the metadata definition parser. 551 * @throws ConfigurationException if the configuration is invalid. 552 */ 553 protected void _parseAllMetadatas(Map<String, Configuration> metadataConfigurations, MetadataAndRepeaterDefinitionParser defParser) throws ConfigurationException 554 { 555 for (Configuration childConfiguration : metadataConfigurations.values()) 556 { 557 String childName = childConfiguration.getName(); 558 559 if (childName.equals("metadata") || childName.equals("repeater")) 560 { 561 _parseMetadata(childConfiguration, defParser); 562 } 563 else if (childName.equals("dublin-core")) 564 { 565 _parseDublinCoreMetadata(defParser); 566 } 567 } 568 } 569 570 /** 571 * Parse a metadata configuration. 572 * @param metadataConfiguration the metadata configuration. 573 * @param defParser the metadata definition parser. 574 * @return the created MetadataDefinition. 575 * @throws ConfigurationException if the configuration is invalid 576 */ 577 protected MetadataDefinition _parseMetadata(Configuration metadataConfiguration, MetadataAndRepeaterDefinitionParser defParser) throws ConfigurationException 578 { 579 MetadataDefinition metadataDefinition = defParser.parseParameter(_manager, _pluginName, metadataConfiguration); 580 metadataDefinition.setReferenceContentType(_id); 581 582 String metadataName = metadataDefinition.getName(); 583 584 if (_metadata.containsKey(metadataName)) 585 { 586 _checkMetadataTypes (_metadata.get(metadataName), metadataDefinition); 587 } 588 589 // Update simple and multilingual properties 590 _checkMetadataDefinition(metadataDefinition); 591 592 _metadata.put(metadataName, metadataDefinition); 593 594 return metadataDefinition; 595 } 596 597 /** 598 * Check if all metadata's types defined in first metadata definition are equals to those defined in second metadata definition 599 * @param metaDef1 The first metadata definition to compare 600 * @param metaDef2 The second metadata definition to compare 601 * @throws ConfigurationException if the types are not equals 602 */ 603 protected void _checkMetadataTypes (MetadataDefinition metaDef1, MetadataDefinition metaDef2) throws ConfigurationException 604 { 605 if (!metaDef1.getType().equals(metaDef2.getType())) 606 { 607 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() + "'"); 608 } 609 610 if (metaDef1.getType().equals(MetadataType.COMPOSITE)) 611 { 612 for (String subMetadataName : metaDef1.getMetadataNames()) 613 { 614 if (metaDef2.getMetadataDefinition(subMetadataName) != null) 615 { 616 _checkMetadataTypes (metaDef1.getMetadataDefinition(subMetadataName), metaDef2.getMetadataDefinition(subMetadataName)); 617 618 // Update simple and multilingual properties 619 _checkMetadataDefinition(metaDef1.getMetadataDefinition(subMetadataName)); 620 } 621 } 622 } 623 } 624 625 /** 626 * Check if all metadata's types defined in first configuration are equals to those defined in second configuration 627 * @param metadataConf1 The first configuration to compare 628 * @param metadataConf2 The second configuration to compare 629 * @throws ConfigurationException if the types are not equals 630 */ 631 protected void _checkMetadataTypes (Configuration metadataConf1, Configuration metadataConf2) throws ConfigurationException 632 { 633 String type = metadataConf1.getAttribute("type", ""); 634 String overridenType = metadataConf2.getAttribute("type", ""); 635 if (!overridenType.equals(type)) 636 { 637 throw new ConfigurationException("The type of metadata '" + metadataConf1.getAttribute("name") + " (" + type.toUpperCase() + ")" + "' can not be overriden to '" + metadataConf2.getAttribute("name") + " (" + overridenType.toUpperCase() + ")'"); 638 } 639 640 if ("composite".equals(type) || metadataConf1.getName().equals("repeater")) 641 { 642 for (Configuration childConfig1 : metadataConf1.getChildren()) 643 { 644 String childName = childConfig1.getName(); 645 if (childName.equals("metadata") || childName.equals("repeater")) 646 { 647 Configuration childConfig2 = null; 648 for (Configuration conf : metadataConf2.getChildren(childName)) 649 { 650 if (childConfig1.getAttribute("name").equals(conf.getAttribute("name"))) 651 { 652 childConfig2 = conf; 653 break; 654 } 655 } 656 657 if (childConfig2 != null) 658 { 659 _checkMetadataTypes (childConfig1, childConfig2); 660 } 661 } 662 } 663 } 664 } 665 666 /** 667 * Check the medatata definition to determines if this content type is multilingual and/or simple 668 * All medatata of a simple content-type have to be a simple type (string, long, date, ..) 669 * A multilingual content type should contain at least a metadata of type MULTILINGUAL_STRING 670 * @param metadataDefinition The metadata definition 671 * @return false if the medatata definition is not a valid medatata definition for a simple content-type 672 */ 673 protected boolean _checkMetadataDefinition (MetadataDefinition metadataDefinition) 674 { 675 MetadataType type = metadataDefinition.getType(); 676 677 switch (type) 678 { 679 case MULTILINGUAL_STRING: 680 // The content type is a multilingual content type 681 _isMultilingual = true; 682 break; 683 684 case COMPOSITE: 685 case FILE: 686 case GEOCODE: 687 case REFERENCE: 688 case RICH_TEXT: 689 // The content type can not be simple (complex metadata are not allowed) 690 _isSimple = false; 691 break; 692 default: 693 break; 694 } 695 696 return true; 697 } 698 699 /** 700 * Parse DublinCore metadata 701 * @param defParser The parser definition 702 * @throws ConfigurationException if the configuration is invalid 703 */ 704 protected void _parseDublinCoreMetadata (MetadataAndRepeaterDefinitionParser defParser) throws ConfigurationException 705 { 706 Source src = null; 707 708 try 709 { 710 src = _srcResolver.resolveURI("resource://org/ametys/cms/dublincore/dublincore.xml"); 711 712 if (src.exists()) 713 { 714 Configuration configuration = null; 715 try (InputStream is = src.getInputStream()) 716 { 717 configuration = new DefaultConfigurationBuilder(true).build(is); 718 } 719 720 MetadataDefinition metadataDefinition = new MetadataDefinition(); 721 metadataDefinition.setReferenceContentType(_id); 722 metadataDefinition.setId("/dc"); // FIXME ? 723 metadataDefinition.setLabel(new I18nizableText("plugin.cms", "PLUGINS_CMS_DUBLINCORE_LABEL")); 724 metadataDefinition.setDescription(new I18nizableText("plugin.cms", "PLUGINS_CMS_DUBLINCORE_DESC")); 725 metadataDefinition.setName("dc"); 726 metadataDefinition.setType(MetadataType.COMPOSITE); 727 728 for (Configuration childConfiguration : configuration.getChildren()) 729 { 730 String childName = childConfiguration.getName(); 731 732 if (childName.equals("metadata")) 733 { 734 MetadataDefinition metaDef = defParser.parseParameter(_manager, _pluginName, childConfiguration); 735 metaDef.setReferenceContentType(_id); 736 String metadataName = metaDef.getName(); 737 metaDef.setId("/dc/" + metadataName); // FIXME ? 738 739 if (metaDef.getEnumerator() == null && _dcProvider.isEnumerated(metadataName)) 740 { 741 StaticEnumerator enumerator = new StaticEnumerator(); 742 743 Map<String, I18nizableText> entries = _dcProvider.getEntries(metadataName); 744 if (entries != null) 745 { 746 for (String value : entries.keySet()) 747 { 748 enumerator.add(entries.get(value), value); 749 } 750 751 } 752 metaDef.setEnumerator(enumerator); 753 } 754 755 metadataDefinition.addMetadata(metaDef); 756 } 757 } 758 759 _metadata.put("dc", metadataDefinition); 760 } 761 } 762 catch (IOException e) 763 { 764 throw new ConfigurationException("Unable to parse Dublin Core metadata", e); 765 } 766 catch (SAXException e) 767 { 768 throw new ConfigurationException("Unable to parse Dublin Core metadata", e); 769 } 770 finally 771 { 772 if (src != null) 773 { 774 _srcResolver.release(src); 775 } 776 } 777 } 778 779 /** 780 * Configure the indexing model 781 * @param config The main configuration 782 * @throws ConfigurationException if an error occurred 783 */ 784 protected void _configureIndexingModel (Configuration config) throws ConfigurationException 785 { 786 Configuration indexConf = config.getChild("indexing-model", true); 787 788 _indexingModel = new IndexingModel(); 789 790 boolean includeFromSuperType = indexConf.getAttributeAsBoolean("include-from-supertype", true); 791 if (includeFromSuperType) 792 { 793 _indexingModel = _contentTypesHelper.getIndexingModel(_superTypeIds, new String[0]); 794 } 795 796 boolean includeAll = indexConf.getAttributeAsBoolean("include-all", true); 797 if (includeAll) 798 { 799 for (String metadataName : _metadata.keySet()) 800 { 801 MetadataDefinition definition = _metadata.get(metadataName); 802 _indexingModel.addIndexingField(new DefaultMetadataIndexingField(metadataName, definition, metadataName)); 803 } 804 } 805 806 // Optionally add the semantic annotations to the indexing model. 807 boolean includeSemanticAnnotations = indexConf.getAttributeAsBoolean("include-semantic-annotations", true); 808 if (includeSemanticAnnotations) 809 { 810 _addSemanticAnnotations(indexConf, this); 811 } 812 813 // Metadata fields. 814 _configureMetadataIndexingFields(indexConf); 815 816 // Custom fields. 817 _configureCustomIndexingFields(indexConf); 818 819 // Custom metadata fields. 820 _configureCustomMetadataIndexingFields(indexConf); 821 } 822 823 /** 824 * Add semantic annotations as indexing fields to the indexing model. 825 * @param indexConf the indexing model configuration. 826 * @param holder the metadata holder (ContentType or MetadataDefinition) to scan for annotable metadata. 827 */ 828 protected void _addSemanticAnnotations(Configuration indexConf, MetadataDefinitionHolder holder) 829 { 830 // Get the semantic annotations in a multimap. 831 Multimap<SemanticAnnotation, String> metadatas = HashMultimap.create(); 832 _getSemanticAnnotations(holder, metadatas, ""); 833 834 // Add a custom indexing field for each annotation to the indexing model. 835 for (SemanticAnnotation annotation : metadatas.keySet()) 836 { 837 Collection<String> metaPaths = metadatas.get(annotation); 838 _indexingModel.addIndexingField(new SemanticAnnotationIndexingField(annotation, metaPaths, this)); 839 } 840 } 841 842 /** 843 * Get the semantic annotations and their paths from the given metadata definition holder's sub-tree. 844 * @param holder The current metadata definition holder. 845 * @param annotations The map of semantic annotations and the corresponding metadata paths. 846 * @param prefix The current prefix in the metadata tree (with a trailing slash, if applicable). 847 */ 848 protected void _getSemanticAnnotations(MetadataDefinitionHolder holder, Multimap<SemanticAnnotation, String> annotations, String prefix) 849 { 850 for (String metadataName : holder.getMetadataNames()) 851 { 852 String metaPath = prefix + metadataName; 853 MetadataDefinition metaDef = holder.getMetadataDefinition(metadataName); 854 if (metaDef instanceof AnnotableDefinition) 855 { 856 List<SemanticAnnotation> metaAnnotations = ((AnnotableDefinition) metaDef).getSemanticAnnotations(); 857 for (SemanticAnnotation annotation : metaAnnotations) 858 { 859 annotations.put(annotation, metaPath); 860 } 861 } 862 863 _getSemanticAnnotations(metaDef, annotations, metaPath + ContentConstants.METADATA_PATH_SEPARATOR); 864 } 865 } 866 867 /** 868 * Configure the metadata indexing fields. 869 * @param indexConf the indexing model configuration. 870 * @throws ConfigurationException if an error occurs. 871 */ 872 protected void _configureMetadataIndexingFields(Configuration indexConf) throws ConfigurationException 873 { 874 Configuration[] fieldsConf = indexConf.getChildren("metadata-field"); 875 for (Configuration fieldConf : fieldsConf) 876 { 877 String metadataPath = fieldConf.getAttribute("path"); 878 879 MetadataDefinition metadataDef = _contentTypesHelper.getMetadataDefinition(metadataPath, this); 880 if (metadataDef != null) 881 { 882 String fieldName = fieldConf.getAttribute("name", null); 883 if (fieldName == null) 884 { 885 fieldName = StringUtils.substringBeforeLast(metadataPath, ContentConstants.METADATA_PATH_SEPARATOR); 886 } 887 888 // TODO check if metadataDef.getType() is a primitive type (string, long, double, ..) ?? 889 /*if (MetadataType.COMPOSITE == metadataDef.getType()) 890 { 891 throw new ConfigurationException("Indexing field of path '" + metadataPath + "' defined in content type '" + this.getId() + "' is not a valid path."); 892 }*/ 893 _indexingModel.addIndexingField(new DefaultMetadataIndexingField(fieldName, metadataDef, metadataPath)); 894 } 895 else 896 { 897 throw new ConfigurationException("Indexing field of path '" + metadataPath + "' defined in content type '" + this.getId() + "' is not a valid path.", fieldConf); 898 } 899 } 900 } 901 902 /** 903 * Configure the custom indexing fields. 904 * @param indexConf the indexing model configuration. 905 * @throws ConfigurationException if an error occurs. 906 */ 907 @SuppressWarnings("unchecked") 908 protected void _configureCustomIndexingFields(Configuration indexConf) throws ConfigurationException 909 { 910 List<String> customFieldRoles = new ArrayList<>(); 911 912 Configuration[] customsConf = indexConf.getChildren("custom-field"); 913 for (Configuration customConf : customsConf) 914 { 915 String className = customConf.getAttribute("class", null); 916 if (className == null) 917 { 918 throw new ConfigurationException("A custom index field defined in content type '" + this.getId() + "' does not specifiy a class.", customConf); 919 } 920 921 try 922 { 923 Class<CustomIndexingField> customFieldClass = (Class<CustomIndexingField>) Class.forName(className); 924 String fieldRole = getId() + "-" + className; 925 _customFieldManager.addComponent("cms", null, getId() + "-" + className, customFieldClass, customConf); 926 927 customFieldRoles.add(fieldRole); 928 } 929 catch (Exception e) 930 { 931 throw new ConfigurationException("Unable to instanciate custom indexing field for class: " + className, customConf, e); 932 } 933 } 934 935 try 936 { 937 _customFieldManager.initialize(); 938 } 939 catch (Exception e) 940 { 941 throw new ConfigurationException("Unable to initialize custom indexing field manager", e); 942 } 943 944 for (String customFieldRole : customFieldRoles) 945 { 946 try 947 { 948 CustomIndexingField field = _customFieldManager.lookup(customFieldRole); 949 _indexingModel.addIndexingField(field); 950 } 951 catch (ComponentException e) 952 { 953 throw new ConfigurationException("Unable to lookup custom indexing field with role: '" + customFieldRole + "' for content type: " + this.getId(), e); 954 } 955 } 956 } 957 958 /** 959 * Configure the custom metadata indexing fields. 960 * @param indexConf the indexing model configuration. 961 * @throws ConfigurationException if an error occurs. 962 */ 963 @SuppressWarnings("unchecked") 964 protected void _configureCustomMetadataIndexingFields(Configuration indexConf) throws ConfigurationException 965 { 966 List<String> customMetadataFieldRoles = new ArrayList<>(); 967 968 int index = 0; 969 Configuration[] customMetaConfs = indexConf.getChildren("custom-metadata-field"); 970 for (Configuration customMetaConf : customMetaConfs) 971 { 972 String className = customMetaConf.getAttribute("class", null); 973 if (className == null) 974 { 975 throw new ConfigurationException("A custom indexing field defined in content type '" + this.getId() + "' does not specifiy a class.", customMetaConf); 976 } 977 978 try 979 { 980 DefaultConfiguration localConf = new DefaultConfiguration(customMetaConf, true); 981 DefaultConfiguration cTypeConf = new DefaultConfiguration("contentType"); 982 cTypeConf.setAttribute("id", this.getId()); 983 localConf.addChild(cTypeConf); 984 985 // Use an index in the role to be able to use several times the same class in a content-type. 986 String fieldRole = getId() + "-" + className + "-" + index; 987 Class<CustomMetadataIndexingField> customMetaFieldClass = (Class<CustomMetadataIndexingField>) Class.forName(className); 988 _customMetadataIndexingFieldManager.addComponent("cms", null, fieldRole, customMetaFieldClass, localConf); 989 990 customMetadataFieldRoles.add(fieldRole); 991 992 index++; 993 } 994 catch (Exception e) 995 { 996 throw new ConfigurationException("Unable to instanciate custom metadata indexing field for class: " + className, customMetaConf, e); 997 } 998 } 999 1000 try 1001 { 1002 _customMetadataIndexingFieldManager.initialize(); 1003 } 1004 catch (Exception e) 1005 { 1006 throw new ConfigurationException("Unable to initialize custom metadata indexing field manager", e); 1007 } 1008 1009 for (String customMetaFieldRole : customMetadataFieldRoles) 1010 { 1011 try 1012 { 1013 CustomMetadataIndexingField field = _customMetadataIndexingFieldManager.lookup(customMetaFieldRole); 1014 _indexingModel.addIndexingField(field); 1015 } 1016 catch (ComponentException e) 1017 { 1018 throw new ConfigurationException("Unable to lookup custom indexing field with role: '" + customMetaFieldRole + "' for content type: " + this.getId(), e); 1019 } 1020 } 1021 } 1022 1023 /** 1024 * Parse the tags 1025 * @param configuration the configuration to use 1026 * @return the tags 1027 * @throws ConfigurationException if the configuration is not valid. 1028 */ 1029 protected Set<String> _parseTags (Configuration configuration) throws ConfigurationException 1030 { 1031 Set<String> tags = new HashSet<>(); 1032 1033 Configuration[] children = configuration.getChildren("tag"); 1034 for (Configuration tagConfig : children) 1035 { 1036 tags.add(tagConfig.getValue()); 1037 } 1038 1039 return tags; 1040 } 1041 1042 @Override 1043 public void postInitialize() throws Exception 1044 { 1045 _checkContentMetadatas(); 1046 _checkContentMutualReferences(); 1047 1048 _computeIndexingModelReferences(); 1049 } 1050 1051 /** 1052 * Check that content and sub-content metadatas reference a valid content-type. 1053 * @throws ConfigurationException if a content (or sub-content) metadata references an invalid or non-existing content type. 1054 */ 1055 protected void _checkContentMetadatas() throws ConfigurationException 1056 { 1057 for (MetadataDefinition metadataDef : _metadata.values()) 1058 { 1059 _checkContentMetadata(metadataDef, metadataDef.getName()); 1060 } 1061 } 1062 1063 /** 1064 * Recursively check that content and sub-content metadatas reference a valid content-type. 1065 * @param metadataDef the metadata definition. 1066 * @param metadataPath the metadata path. 1067 * @throws ConfigurationException if a content (or sub-content) metadata references an invalid or non-existing content type. 1068 */ 1069 protected void _checkContentMetadata(MetadataDefinition metadataDef, String metadataPath) throws ConfigurationException 1070 { 1071 if (metadataDef.getType() == MetadataType.CONTENT || metadataDef.getType() == MetadataType.SUB_CONTENT) 1072 { 1073 String cTypeId = metadataDef.getContentType(); 1074 1075 if (StringUtils.isNotBlank(cTypeId) && !_cTypeEP.hasExtension(cTypeId)) 1076 { 1077 throw new ConfigurationException("The content metadata of path " + metadataPath + " in content type " + getId() + " references a non-existing content-type: '" + cTypeId + "'"); 1078 } 1079 } 1080 1081 // Check sub-metadatas. 1082 for (String subMetaName : metadataDef.getMetadataNames()) 1083 { 1084 MetadataDefinition subMetaDef = metadataDef.getMetadataDefinition(subMetaName); 1085 String subMetaPath = metadataPath + "/" + subMetaName; 1086 1087 _checkContentMetadata(subMetaDef, subMetaPath); 1088 } 1089 } 1090 1091 /** 1092 * Check content type mutual reference declarations. 1093 * @throws ConfigurationException if there is a problem with mutual reference declarations. 1094 */ 1095 protected void _checkContentMutualReferences() throws ConfigurationException 1096 { 1097 for (MetadataDefinition metadataDef : _metadata.values()) 1098 { 1099 _checkContentMutualReferences(metadataDef, metadataDef.getName(), 0); 1100 } 1101 } 1102 1103 /** 1104 * Recursively check a content type mutual reference declarations. 1105 * @param metadataDef the metadata definition. 1106 * @param metadataPath the metadata path. 1107 * @param repeaterLevel the current nested level of repeaters, 0 if the current metadata isn't in a repeater. 1108 * @throws ConfigurationException if there is a problem with mutual reference declarations. 1109 */ 1110 protected void _checkContentMutualReferences(MetadataDefinition metadataDef, String metadataPath, int repeaterLevel) throws ConfigurationException 1111 { 1112 if (metadataDef.getType() == MetadataType.CONTENT) 1113 { 1114 String cTypeId = metadataDef.getContentType(); 1115 String invertRelationPath = metadataDef.getInvertRelationPath(); 1116 1117 if (StringUtils.isNotEmpty(invertRelationPath)) 1118 { 1119 ContentType cType = _cTypeEP.getExtension(cTypeId); 1120 MetadataDefinition invertMetadataDef = cType.getMetadataDefinitionByPath(invertRelationPath); 1121 1122 if (repeaterLevel > 0 && metadataDef.isMultiple()) 1123 { 1124 throw new ConfigurationException("The metadata at path '" + metadataPath + "' in content type " + getId() + " is declared as a mutual relationship, it can't be multiple AND in a repeater."); 1125 } 1126 else if (repeaterLevel >= 2) 1127 { 1128 throw new ConfigurationException("The metadata at path '" + metadataPath + "' in content type " + getId() + " is declared as a mutual relationship, it can't be in two levels of repeaters."); 1129 } 1130 1131 // Ensure the metadata presence. 1132 if (invertMetadataDef == null) 1133 { 1134 throw new ConfigurationException("Mutual relationship: the metadata at path '" + invertRelationPath + "' doesn't exist for type " + cTypeId); 1135 } 1136 1137 // Ensure that the referenced metadata is a Content. 1138 if (invertMetadataDef.getType() != MetadataType.CONTENT) 1139 { 1140 throw new ConfigurationException("Mutual relationship: the metadata at path '" + invertRelationPath + "' of type " + cTypeId + " is not of type Content."); 1141 } 1142 1143 String invertCTypeId = invertMetadataDef.getContentType(); 1144 String invertPath = invertMetadataDef.getInvertRelationPath(); 1145 1146 // Ensure that the referenced metadata's content type is compatible with the current type. 1147 if (!_cTypeEP.isSameOrDescendant(getId(), invertCTypeId)) 1148 { 1149 throw new ConfigurationException("Mutual relationship: the metadata at path " + invertRelationPath + " of type " + cTypeId + " references an incompatible type: " + StringUtils.defaultString(invertCTypeId, "<null>")); 1150 } 1151 1152 // Ensure that the referenced metadata references this metadata. 1153 if (!metadataPath.equals(invertPath)) 1154 { 1155 throw new ConfigurationException("Mutual relationship: the metadata at path " + metadataPath + " of type " + getId() + " references the metadata '" + invertRelationPath + "' of type " + cTypeId + " but the latter does not reference it back."); 1156 } 1157 } 1158 } 1159 1160 // We are in a repeater if we were already in a repeater at the previous level, or if the current metadata definition is a repeater. 1161 int newRepeaterLevel = (metadataDef instanceof RepeaterDefinition) ? repeaterLevel + 1 : repeaterLevel; 1162 1163 // Check sub-metadatas. 1164 for (String subMetaName : metadataDef.getMetadataNames()) 1165 { 1166 MetadataDefinition subMetaDef = metadataDef.getMetadataDefinition(subMetaName); 1167 String subMetaPath = metadataPath + ContentConstants.METADATA_PATH_SEPARATOR + subMetaName; 1168 1169 _checkContentMutualReferences(subMetaDef, subMetaPath, newRepeaterLevel); 1170 } 1171 } 1172 1173 /** 1174 * Browse the indexing model and compute indexing field references. 1175 */ 1176 protected void _computeIndexingModelReferences() 1177 { 1178 // Impacted ContentType -> local IndexingField name -> path to impacted content. 1179 Map<String, Map<String, List<String>>> references = new HashMap<>(); 1180 1181 for (IndexingField field : _indexingModel.getFields()) 1182 { 1183 if (field instanceof MetadataIndexingField) 1184 { 1185 String metadataPath = ((MetadataIndexingField) field).getMetadataPath(); 1186 1187 List<MetadataDefinition> definitions = getIndexingFieldDefinitions(this, metadataPath); 1188 1189 List<String> joinPaths = new ArrayList<>(); 1190 boolean localContentType = true; 1191 StringBuilder currentContentPath = new StringBuilder(); 1192 for (MetadataDefinition definition : definitions) 1193 { 1194 if (currentContentPath.length() > 0) 1195 { 1196 currentContentPath.append(ContentConstants.METADATA_PATH_SEPARATOR); 1197 } 1198 currentContentPath.append(definition.getName()); 1199 1200 if (definition.getType() == MetadataType.CONTENT || definition.getType() == MetadataType.SUB_CONTENT) 1201 { 1202 if (!localContentType) 1203 { 1204 joinPaths.add(currentContentPath.toString()); 1205 currentContentPath.setLength(0); 1206 1207 String cTypeId = definition.getContentType(); 1208 1209 Map<String, List<String>> cTypeRefs = null; 1210 if (references.containsKey(cTypeId)) 1211 { 1212 cTypeRefs = references.get(cTypeId); 1213 } 1214 else 1215 { 1216 cTypeRefs = new HashMap<>(); 1217 references.put(cTypeId, cTypeRefs); 1218 } 1219 1220 cTypeRefs.put(field.getName(), new ArrayList<>(joinPaths)); 1221 } 1222 1223 localContentType = false; 1224 } 1225 } 1226 } 1227 } 1228 1229 _indexingModel.setReferences(references); 1230 } 1231 1232 /** 1233 * Get the list of metadata definitions "traversed" from the initial content type to the given metadata. 1234 * @param initialContentType the initial content type. 1235 * @param metadataPath the compound metadata path. 1236 * @return the list of metadata definitions. 1237 */ 1238 protected List<MetadataDefinition> getIndexingFieldDefinitions(ContentType initialContentType, String metadataPath) 1239 { 1240 List<MetadataDefinition> definitions = new ArrayList<>(); 1241 1242 String[] pathSegments = StringUtils.split(metadataPath, ContentConstants.METADATA_PATH_SEPARATOR); 1243 1244 if (pathSegments.length > 0) 1245 { 1246 IndexingModel indexingModel = _contentTypesHelper.getIndexingModel(new String[] {initialContentType.getId()}, new String[0]); 1247 1248 IndexingField refField = indexingModel.getField(pathSegments[0]); 1249 1250 MetadataDefinition metadataDef = null; 1251 if (refField != null && refField instanceof CustomMetadataIndexingField) 1252 { 1253 metadataDef = ((CustomMetadataIndexingField) refField).getMetadataDefinition(); 1254 } 1255 else 1256 { 1257 metadataDef = initialContentType.getMetadataDefinition(pathSegments[0]); 1258 } 1259 1260 if (metadataDef != null) 1261 { 1262 definitions.add(metadataDef); 1263 } 1264 1265 for (int i = 1; i < pathSegments.length && metadataDef != null; i++) 1266 { 1267 if (metadataDef.getType() == MetadataType.CONTENT || metadataDef.getType() == MetadataType.SUB_CONTENT) 1268 { 1269 String refCTypeId = metadataDef.getContentType(); 1270 if (refCTypeId != null && _cTypeEP.hasExtension(refCTypeId)) 1271 { 1272 ContentType refCType = _cTypeEP.getExtension(refCTypeId); 1273 1274 List<MetadataDefinition> followingDefs = getIndexingFieldDefinitions(refCType, StringUtils.join(pathSegments, '/', i, pathSegments.length)); 1275 definitions.addAll(followingDefs); 1276 1277 return definitions; 1278 } 1279 } 1280 else 1281 { 1282 refField = indexingModel.getField(pathSegments[i]); 1283 if (refField != null && refField instanceof CustomMetadataIndexingField) 1284 { 1285 metadataDef = ((CustomMetadataIndexingField) refField).getMetadataDefinition(); 1286 } 1287 else 1288 { 1289 metadataDef = metadataDef.getMetadataDefinition(pathSegments[i]); 1290 } 1291 definitions.add(metadataDef); 1292 } 1293 } 1294 } 1295 1296 return definitions; 1297 } 1298 1299 @Override 1300 public List<ContentValidator> getGlobalValidators() 1301 { 1302 return Collections.unmodifiableList(_globalValidators); 1303 } 1304 1305 @Override 1306 public RichTextUpdater getRichTextUpdater() 1307 { 1308 return _richTextUpdater; 1309 } 1310 1311 @Override 1312 public Set<String> getMetadataNames() 1313 { 1314 return Collections.unmodifiableSet(_metadata.keySet()); 1315 } 1316 1317 @Override 1318 public MetadataDefinition getMetadataDefinition(String metadataName) 1319 { 1320 return _metadata.get(metadataName); 1321 } 1322 1323 @Override 1324 public boolean hasMetadataDefinition(String metadataName) 1325 { 1326 return _metadata.containsKey(metadataName); 1327 } 1328 1329 @Override 1330 public MetadataDefinition getMetadataDefinitionByPath(String metadataPath) 1331 { 1332 String[] pathSegments = StringUtils.split(metadataPath, ContentConstants.METADATA_PATH_SEPARATOR); 1333 1334 if (pathSegments.length == 0) 1335 { 1336 return null; 1337 } 1338 1339 MetadataDefinition metadataDef = _metadata.get(pathSegments[0]); 1340 1341 for (int i = 1; i < pathSegments.length && metadataDef != null; i++) 1342 { 1343 metadataDef = metadataDef.getMetadataDefinition(pathSegments[i]); 1344 } 1345 1346 return metadataDef; 1347 } 1348 1349 @Override 1350 public IndexingModel getIndexingModel() 1351 { 1352 return _indexingModel; 1353 } 1354 1355 @Override 1356 public boolean canRead(Content content, MetadataDefinition metadataDef) throws AmetysRepositoryException 1357 { 1358 Restrictions restrictions = _getRestrictionsForPath(metadataDef); 1359 1360 if (restrictions == null) 1361 { 1362 return true; 1363 } 1364 1365 if (restrictions._cannotRead) 1366 { 1367 return false; 1368 } 1369 1370 if (content == null) 1371 { 1372 // Unable to check right (content is not yet created), assume user has right 1373 return true; 1374 } 1375 1376 boolean hasRights = _hasRights(content, restrictions._readRights); 1377 1378 if (!hasRights) 1379 { 1380 return false; 1381 } 1382 1383 if (content instanceof WorkflowAwareContent) 1384 { 1385 hasRights = _isInWorkflowStep((WorkflowAwareContent) content, restrictions._readWfSteps); 1386 1387 if (!hasRights) 1388 { 1389 return false; 1390 } 1391 } 1392 1393 String[] pathSegments = StringUtils.split(metadataDef.getId(), ContentConstants.METADATA_PATH_SEPARATOR); 1394 if (pathSegments.length > 1) 1395 { 1396 // Check read access on parent metadata definition 1397 String parentMetadataPath = metadataDef.getId().substring(0, metadataDef.getId().lastIndexOf(ContentConstants.METADATA_PATH_SEPARATOR)); 1398 MetadataDefinition parentMetadataDef = _contentTypesHelper.getMetadataDefinition(parentMetadataPath, content); 1399 return canRead(content, parentMetadataDef); 1400 } 1401 1402 return true; 1403 } 1404 1405 @Override 1406 public boolean canWrite(Content content, MetadataDefinition metadataDef) throws AmetysRepositoryException 1407 { 1408 Restrictions restrictions = _getRestrictionsForPath(metadataDef); 1409 1410 if (restrictions == null) 1411 { 1412 return true; 1413 } 1414 1415 if (restrictions._cannotWrite) 1416 { 1417 return false; 1418 } 1419 1420 if (content == null) 1421 { 1422 // Unable to check right (content is not yet created), assume user has right 1423 return true; 1424 } 1425 1426 boolean hasRights = _hasRights(content, restrictions._writeRights); 1427 1428 if (!hasRights) 1429 { 1430 return false; 1431 } 1432 1433 if (content instanceof WorkflowAwareContent) 1434 { 1435 hasRights = _isInWorkflowStep((WorkflowAwareContent) content, restrictions._writeWfSteps); 1436 1437 if (!hasRights) 1438 { 1439 return false; 1440 } 1441 } 1442 1443 String[] pathSegments = StringUtils.split(metadataDef.getId(), ContentConstants.METADATA_PATH_SEPARATOR); 1444 if (pathSegments.length > 1) 1445 { 1446 // Check write access on parent metadata definition 1447 String parentMetadataPath = metadataDef.getId().substring(0, metadataDef.getId().lastIndexOf(ContentConstants.METADATA_PATH_SEPARATOR)); 1448 MetadataDefinition parentMetadataDef = _contentTypesHelper.getMetadataDefinition(parentMetadataPath, content); 1449 return canWrite(content, parentMetadataDef); 1450 } 1451 1452 return canRead(content, metadataDef); 1453 } 1454 1455 @Override 1456 public Set<String> getTags() 1457 { 1458 return Collections.unmodifiableSet(_tags); 1459 } 1460 1461 @Override 1462 public boolean hasTag(String tagName) 1463 { 1464 return _tags.contains(tagName); 1465 } 1466 1467 @Override 1468 public boolean isPrivate() 1469 { 1470 return hasTag(TAG_PRIVATE); 1471 } 1472 1473 @Override 1474 public boolean isAbstract() 1475 { 1476 return _abstract; 1477 } 1478 1479 @Override 1480 public boolean isSimple() 1481 { 1482 return _isSimple; 1483 } 1484 1485 @Override 1486 public boolean isReferenceTable() 1487 { 1488 return hasTag(TAG_REFERENCE_TABLE) || hasTag(TAG_RENDERABLE_FERENCE_TABLE); 1489 } 1490 1491 @Override 1492 public boolean isMultilingual() 1493 { 1494 return _isMultilingual; 1495 } 1496 1497 @Override 1498 public boolean isMixin() 1499 { 1500 return hasTag(TAG_MIXIN); 1501 } 1502 1503 @Override 1504 public String getRight() 1505 { 1506 return _right; 1507 } 1508 1509 @Override 1510 public void saxContentTypeAdditionalData(org.xml.sax.ContentHandler contentHandler, Content content) throws AmetysRepositoryException, SAXException 1511 { 1512 // Nothing 1513 } 1514 1515 @Override 1516 public Map<String, Object> getAdditionalData(Content content) 1517 { 1518 return new HashMap<>(); 1519 } 1520 1521 /** 1522 * Retrieves the restrictions for a given path. 1523 * @param metadataDef the metadata definition. 1524 * @return the restrictions or <code>null</code> if not found. 1525 */ 1526 protected Restrictions _getRestrictionsForPath(MetadataDefinition metadataDef) 1527 { 1528 if (metadataDef != null && metadataDef instanceof RestrictedDefinition) 1529 { 1530 return ((RestrictedDefinition) metadataDef).getRestrictions(); 1531 } 1532 1533 // Not found 1534 return null; 1535 } 1536 1537 /** 1538 * Check if current user has the given rights. 1539 * @param rightLimitations the right limitations. 1540 * @param content the content. 1541 * @return <code>true</code> if it is on at least one step, 1542 * <code>false</code> otherwise. 1543 */ 1544 protected boolean _hasRights(Content content, Set<String> rightLimitations) 1545 { 1546 if (rightLimitations.isEmpty()) 1547 { 1548 return true; 1549 } 1550 1551 UserIdentity user = _currentUserProvider.getUser(); 1552 1553 for (String rightId : rightLimitations) 1554 { 1555 if (_rightManager.hasRight(user, rightId, content) == RightResult.RIGHT_ALLOW) 1556 { 1557 return true; 1558 } 1559 } 1560 1561 return false; 1562 } 1563 1564 /** 1565 * Check if the workflow of the content is in a given current step. 1566 * @param workflowLimitations the workflow limitations. 1567 * @param content the content. 1568 * @return <code>true</code> if it is on at least one step, 1569 * <code>false</code> otherwise. 1570 * @throws AmetysRepositoryException if an error occurs. 1571 */ 1572 protected boolean _isInWorkflowStep(WorkflowAwareContent content, Set<Integer> workflowLimitations) throws AmetysRepositoryException 1573 { 1574 if (workflowLimitations.isEmpty()) 1575 { 1576 return true; 1577 } 1578 1579 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content); 1580 1581 List<Step> workflowCurrentSteps = workflow.getCurrentSteps(content.getWorkflowId()); 1582 1583 for (Step step : workflowCurrentSteps) 1584 { 1585 for (int stepId : workflowLimitations) 1586 { 1587 if (step.getStepId() == stepId) 1588 { 1589 return true; 1590 } 1591 } 1592 } 1593 1594 // No match 1595 return false; 1596 } 1597 1598 @Override 1599 public String toString() 1600 { 1601 return "'" + getId() + "'"; 1602 } 1603 1604 /** 1605 * Restrictions provided with a metadata definition. 1606 */ 1607 protected static class Restrictions 1608 { 1609 /** Cannot read status. */ 1610 protected boolean _cannotRead; 1611 /** Cannot write status. */ 1612 protected boolean _cannotWrite; 1613 /** Read right ids. */ 1614 protected Set<String> _readRights = new HashSet<>(); 1615 /** Write right ids. */ 1616 protected Set<Integer> _readWfSteps = new HashSet<>(); 1617 /** Read workflow step ids. */ 1618 protected Set<String> _writeRights = new HashSet<>(); 1619 /** Write workflow step ids. */ 1620 protected Set<Integer> _writeWfSteps = new HashSet<>(); 1621 } 1622 1623 /** 1624 * Restricted definition. 1625 */ 1626 protected interface RestrictedDefinition 1627 { 1628 /** 1629 * Provides the restrictions. 1630 * @return the restrictions. 1631 */ 1632 Restrictions getRestrictions(); 1633 } 1634 1635 /** 1636 * Definition with semantic annotations 1637 */ 1638 protected interface AnnotableDefinition 1639 { 1640 /** 1641 * Provides the semantic annotations 1642 * @return the semantic annotations 1643 */ 1644 List<SemanticAnnotation> getSemanticAnnotations(); 1645 1646 /** 1647 * Set the semantic annotations 1648 * @param annotations the semantic annotations to set 1649 */ 1650 void setSemanticAnnotations(List<SemanticAnnotation> annotations); 1651 } 1652 1653 /** 1654 * Internal {@link MetadataDefinition} storage contains instances of this class. 1655 */ 1656 protected static class RestrictedMetadataDefinition extends MetadataDefinition implements RestrictedDefinition 1657 { 1658 /** Restrictions. */ 1659 protected Restrictions _restrictions = new Restrictions(); 1660 1661 @Override 1662 public Restrictions getRestrictions() 1663 { 1664 return _restrictions; 1665 } 1666 } 1667 1668 /** 1669 * Internal {@link MetadataDefinition} storage contains instances of this class. 1670 */ 1671 protected static class RestrictedRichTextDefinition extends RichTextMetadataDefinition implements RestrictedDefinition 1672 { 1673 /** Restrictions. */ 1674 protected Restrictions _restrictions = new Restrictions(); 1675 1676 @Override 1677 public Restrictions getRestrictions() 1678 { 1679 return _restrictions; 1680 } 1681 } 1682 1683 /** 1684 * Internal {@link RepeaterDefinition} storage contains instances of this class. 1685 */ 1686 protected static class RestrictedRepeaterDefinition extends RepeaterDefinition implements RestrictedDefinition 1687 { 1688 /** Restrictions. */ 1689 protected Restrictions _restrictions = new Restrictions(); 1690 1691 @Override 1692 public Restrictions getRestrictions() 1693 { 1694 return _restrictions; 1695 } 1696 } 1697 1698 /** 1699 * {@link RestrictedMetadataDefinition} and {@link RestrictedRepeaterDefinition} parser. 1700 */ 1701 protected class MetadataAndRepeaterDefinitionParser extends AbstractParameterParser<MetadataDefinition, MetadataType> 1702 { 1703 /** Parent prefix. */ 1704 protected String _parentPrefix = ""; 1705 1706 /** 1707 * Creates an {@link MetadataAndRepeaterDefinitionParser}. 1708 * @param enumeratorManager the enumerator component manager. 1709 * @param validatorManager the validator component manager. 1710 */ 1711 public MetadataAndRepeaterDefinitionParser(ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager) 1712 { 1713 super(enumeratorManager, validatorManager); 1714 } 1715 1716 @Override 1717 protected MetadataDefinition _createParameter(Configuration metadataConfiguration) throws ConfigurationException 1718 { 1719 String defName = metadataConfiguration.getName(); 1720 1721 if (defName.equals("metadata")) 1722 { 1723 String defType = metadataConfiguration.getAttribute("type"); 1724 if (defType.equals("rich-text")) 1725 { 1726 return new RestrictedRichTextDefinition(); 1727 } 1728 else 1729 { 1730 return new RestrictedMetadataDefinition(); 1731 } 1732 1733 } 1734 else if (defName.equals("repeater")) 1735 { 1736 return new RestrictedRepeaterDefinition(); 1737 } 1738 1739 throw new ConfigurationException("Unsupported metadata or repeater configuration", metadataConfiguration); 1740 } 1741 1742 @Override 1743 protected String _parseId(Configuration metadataConfiguration) throws ConfigurationException 1744 { 1745 String metadataName = metadataConfiguration.getAttribute("name"); 1746 1747 if (!metadataName.matches("^[a-zA-Z]((?!__)[a-zA-Z0-9_-])*$")) 1748 { 1749 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); 1750 } 1751 1752 return _parentPrefix + (_parentPrefix.length() > 0 ? ContentConstants.METADATA_PATH_SEPARATOR : "") + metadataName; 1753 } 1754 1755 @Override 1756 protected MetadataType _parseType(Configuration metadataConfiguration) throws ConfigurationException 1757 { 1758 if (metadataConfiguration.getName().equals("repeater")) 1759 { 1760 // A repeater is a composite 1761 return MetadataType.COMPOSITE; 1762 } 1763 1764 try 1765 { 1766 return MetadataType.valueOf(metadataConfiguration.getAttribute("type").toUpperCase().replaceAll("-", "_")); 1767 } 1768 catch (IllegalArgumentException e) 1769 { 1770 throw new ConfigurationException("Invalid type", metadataConfiguration, e); 1771 } 1772 } 1773 1774 @Override 1775 protected I18nizableText _parseI18nizableText(Configuration config, String pluginName, String name) throws ConfigurationException 1776 { 1777 // Override i18n parsing to use the default catalog (which can be application for automatic content types) 1778 return I18nizableText.parseI18nizableText(config.getChild(name), _getDefaultCatalogue()); 1779 } 1780 1781 @Override 1782 protected Object _parseDefaultValue(Configuration metadataConfiguration, MetadataDefinition metadataDef) 1783 { 1784 String value; 1785 1786 Configuration childNode = metadataConfiguration.getChild("default-value", false); 1787 if (childNode == null) 1788 { 1789 value = null; 1790 } 1791 else 1792 { 1793 value = childNode.getValue(""); 1794 } 1795 1796 return value; 1797 } 1798 1799 @Override 1800 protected void _additionalParsing(ServiceManager manager, String pluginName, Configuration metadataConfiguration, String metadataId, MetadataDefinition metadataDefinition) throws ConfigurationException 1801 { 1802 super._additionalParsing(manager, pluginName, metadataConfiguration, metadataId, metadataDefinition); 1803 String metadataName = metadataConfiguration.getAttribute("name"); 1804 1805 metadataDefinition.setReferenceContentType(_id); 1806 metadataDefinition.setName(metadataName); 1807 metadataDefinition.setMultiple(metadataConfiguration.getAttributeAsBoolean("multiple", false)); 1808 // Use default transformer (docbook) 1809 metadataDefinition.setRichTextTransformer(_richTextTransformer); 1810 // Use docbook outgoing consistency extractor 1811 metadataDefinition.setRichTextOutgoingReferencesExtractor(_richTextOutgoingReferencesExtractor); 1812 1813 if (metadataDefinition instanceof RepeaterDefinition) 1814 { 1815 RepeaterDefinition repeaterDefinition = (RepeaterDefinition) metadataDefinition; 1816 1817 _parseRepeaterDefinition(manager, pluginName, metadataConfiguration, repeaterDefinition); 1818 } 1819 1820 if (metadataDefinition instanceof AnnotableDefinition) 1821 { 1822 AnnotableDefinition definitionWithSemAnnotations = (AnnotableDefinition) metadataDefinition; 1823 1824 _parseDefinitionWithAnnotations(manager, pluginName, metadataConfiguration, definitionWithSemAnnotations); 1825 } 1826 1827 _populateRestrictions(metadataConfiguration, ((RestrictedDefinition) metadataDefinition).getRestrictions()); 1828 1829 if (metadataDefinition.getType() == MetadataType.CONTENT || metadataDefinition.getType() == MetadataType.SUB_CONTENT) 1830 { 1831 // Content metadata: parse and set the content type restriction. 1832 String contentType = metadataConfiguration.getAttribute("contentType", null); 1833 if (StringUtils.isNotEmpty(contentType)) 1834 { 1835 metadataDefinition.setContentType(contentType); 1836 } 1837 1838 if (metadataDefinition.getType() == MetadataType.CONTENT) 1839 { 1840 _parseContentRelations(metadataConfiguration, metadataDefinition, contentType); 1841 } 1842 } 1843 else if (metadataDefinition.getType() == MetadataType.COMPOSITE) 1844 { 1845 for (Configuration childConfig : metadataConfiguration.getChildren()) 1846 { 1847 String childName = childConfig.getName(); 1848 1849 if (childName.equals("metadata") || childName.equals("repeater")) 1850 { 1851 String oldParentPrefix = _parentPrefix; 1852 // Stack new parent prefix 1853 _parentPrefix = _parentPrefix + (_parentPrefix.length() > 0 ? ContentConstants.METADATA_PATH_SEPARATOR : "") + metadataName; 1854 MetadataDefinition subMetaDef = parseParameter(manager, pluginName, childConfig); 1855 // Restore parent prefix 1856 _parentPrefix = oldParentPrefix; 1857 1858 if (!metadataDefinition.addMetadata(subMetaDef)) 1859 { 1860 throw new ConfigurationException("Metadata with name " + subMetaDef.getName() + " is already defined", childConfig); 1861 } 1862 } 1863 } 1864 } 1865 } 1866 1867 /** 1868 * Parse content mutual relations. 1869 * @param metadataConfiguration the metadata configuration. 1870 * @param metadataDefinition the metadata definition to fill. 1871 * @param contentType the content type. 1872 */ 1873 protected void _parseContentRelations(Configuration metadataConfiguration, MetadataDefinition metadataDefinition, String contentType) 1874 { 1875 String invert = metadataConfiguration.getAttribute("invert", null); 1876 if (StringUtils.isNotEmpty(invert)) 1877 { 1878 metadataDefinition.setInvertRelationPath(invert); 1879 1880 boolean forceInvert = metadataConfiguration.getAttributeAsBoolean("forceInvert", false); 1881 metadataDefinition.setForceInvert(forceInvert); 1882 } 1883 } 1884 1885 /** 1886 * Parses the repeater definition. 1887 * @param manager the service manager. 1888 * @param pluginName the plugin name declaring this parameter. 1889 * @param metadataConfiguration the metadata configuration to use. 1890 * @param repeaterDefinition the repeater definition. 1891 * @throws ConfigurationException if the configuration is not valid. 1892 */ 1893 protected void _parseRepeaterDefinition(ServiceManager manager, String pluginName, Configuration metadataConfiguration, RepeaterDefinition repeaterDefinition) throws ConfigurationException 1894 { 1895 repeaterDefinition.setAddLabel(_parseI18nizableText(metadataConfiguration, pluginName, "add-label")); 1896 repeaterDefinition.setDeleteLabel(_parseI18nizableText(metadataConfiguration, pluginName, "del-label")); 1897 repeaterDefinition.setHeaderLabel(metadataConfiguration.getChild("header-label").getValue(null)); 1898 repeaterDefinition.setInitialSize(metadataConfiguration.getAttributeAsInteger("initial-size", 0)); 1899 repeaterDefinition.setMinSize(metadataConfiguration.getAttributeAsInteger("min-size", 0)); 1900 repeaterDefinition.setMaxSize(metadataConfiguration.getAttributeAsInteger("max-size", -1)); 1901 } 1902 1903 /** 1904 * Parses the definition with semantic annotations. 1905 * @param manager the service manager. 1906 * @param pluginName the plugin name declaring this parameter. 1907 * @param metadataConfiguration the metadata configuration to use. 1908 * @param annotableDefinition the metadata definition 1909 * @throws ConfigurationException if the configuration is not valid. 1910 */ 1911 protected void _parseDefinitionWithAnnotations(ServiceManager manager, String pluginName, Configuration metadataConfiguration, AnnotableDefinition annotableDefinition) throws ConfigurationException 1912 { 1913 Configuration annotationsConfiguration = metadataConfiguration.getChild("annotations"); 1914 List<SemanticAnnotation> semAnnotations = _parseSemAnnotations(pluginName, annotationsConfiguration); 1915 annotableDefinition.setSemanticAnnotations(semAnnotations); 1916 } 1917 1918 /** 1919 * Extract the list of the declared annotations 1920 * @param pluginName the plugin name declaring this parameter. 1921 * @param annotationsConfiguration the annotations configuration to use. 1922 * @return the list of the declared annotations 1923 * @throws ConfigurationException if the configuration is not valid. 1924 */ 1925 protected List<SemanticAnnotation> _parseSemAnnotations(String pluginName, Configuration annotationsConfiguration) throws ConfigurationException 1926 { 1927 List<SemanticAnnotation> annotations = new ArrayList<>(); 1928 1929 for (Configuration annotationConfig : annotationsConfiguration.getChildren("annotation")) 1930 { 1931 String id = annotationConfig.getAttribute("name"); 1932 1933 if (!_getAnnotationNamePattern().matcher(id).matches()) 1934 { 1935 throw new ConfigurationException("Invalid annonation name '" + id + "'. This value is not permitted: only [a-zA-Z][a-zA-Z0-9-_]* are allowed."); 1936 } 1937 1938 I18nizableText label = _parseI18nizableText(annotationConfig, pluginName, "label"); 1939 I18nizableText description = _parseI18nizableText(annotationConfig, pluginName, "description"); 1940 annotations.add(new SemanticAnnotation(id, label, description)); 1941 } 1942 1943 return annotations; 1944 } 1945 1946 /** 1947 * Get the annotation name pattern to test validity. 1948 * @return The annotation name pattern. 1949 */ 1950 protected Pattern _getAnnotationNamePattern() 1951 { 1952 if (__annotationNamePattern == null) 1953 { 1954 // [a-zA-Z][a-zA-Z0-9_]* 1955 __annotationNamePattern = Pattern.compile("[a-z][a-z0-9-_]*", Pattern.CASE_INSENSITIVE); 1956 } 1957 1958 return __annotationNamePattern; 1959 } 1960 1961 /** 1962 * Populates the restrictions into a metadata definition. 1963 * @param metadataConfiguration the metadata configuration to use. 1964 * @param restrictions the restrictions. 1965 * @throws ConfigurationException if the configuration is not valid. 1966 */ 1967 protected void _populateRestrictions(Configuration metadataConfiguration, Restrictions restrictions) throws ConfigurationException 1968 { 1969 Configuration restrictToConf = metadataConfiguration.getChild("restrict-to", true); 1970 1971 _populateNegativeRestrictions(restrictToConf, restrictions); 1972 _populateRightRestrictions(restrictToConf, restrictions); 1973 _populateWorkflowRestrictions(restrictToConf, restrictions); 1974 } 1975 1976 /** 1977 * Populates the negative restrictions. 1978 * @param restrictionsConfig the restrictions configuration to use. 1979 * @param restrictions the restrictions. 1980 * @throws ConfigurationException if the configuration is not valid. 1981 */ 1982 protected void _populateNegativeRestrictions(Configuration restrictionsConfig, Restrictions restrictions) throws ConfigurationException 1983 { 1984 for (Configuration noRightConfig : restrictionsConfig.getChildren("cannot")) 1985 { 1986 boolean isRead = _parseAccessType(noRightConfig); 1987 1988 if (isRead) 1989 { 1990 restrictions._cannotRead = true; 1991 } 1992 else 1993 { 1994 restrictions._cannotWrite = true; 1995 } 1996 } 1997 } 1998 1999 /** 2000 * Parses type attribute from a configuration. 2001 * @param configuration the configuration. 2002 * @return <code>true</code> for <code>read</code> type, 2003 * <code>false</code> for <code>write</code> type. 2004 * @throws ConfigurationException if the configuration is not valid. 2005 */ 2006 protected boolean _parseAccessType(Configuration configuration) throws ConfigurationException 2007 { 2008 String type = configuration.getAttribute("read-write-direction"); 2009 2010 if ("read".equalsIgnoreCase(type)) 2011 { 2012 return true; 2013 } 2014 else if ("write".equalsIgnoreCase(type)) 2015 { 2016 return false; 2017 } 2018 else 2019 { 2020 throw new ConfigurationException("Attribute 'type' must be 'read' or 'write'.", configuration); 2021 } 2022 } 2023 2024 /** 2025 * Populates the rights restrictions. 2026 * @param restrictionsConfig the restrictions configuration to use. 2027 * @param restrictions the restrictions. 2028 * @throws ConfigurationException if the configuration is not valid. 2029 */ 2030 protected void _populateRightRestrictions(Configuration restrictionsConfig, Restrictions restrictions) throws ConfigurationException 2031 { 2032 for (Configuration rightConfig : restrictionsConfig.getChildren("right")) 2033 { 2034 String rightId = rightConfig.getAttribute("id", null); 2035 2036 if (rightId == null) 2037 { 2038 throw new ConfigurationException("Attribute 'id' is mandatory on 'right' element in a content tye configuration.", rightConfig); 2039 } 2040 2041 boolean isRead = _parseAccessType(rightConfig); 2042 2043 if (isRead) 2044 { 2045 restrictions._readRights.add(rightId); 2046 } 2047 else 2048 { 2049 restrictions._writeRights.add(rightId); 2050 } 2051 } 2052 } 2053 2054 /** 2055 * Populates the workflows restrictions. 2056 * @param restrictionsConfig the restrictions configuration to use. 2057 * @param restrictions the restrictions. 2058 * @throws ConfigurationException if the configuration is not valid. 2059 */ 2060 protected void _populateWorkflowRestrictions(Configuration restrictionsConfig, Restrictions restrictions) throws ConfigurationException 2061 { 2062 for (Configuration workflowConfig : restrictionsConfig.getChildren("workflow")) 2063 { 2064 String stepId = workflowConfig.getAttribute("step", null); 2065 int stepIdValue = -1; 2066 2067 if (stepId != null) 2068 { 2069 try 2070 { 2071 stepIdValue = Integer.valueOf(stepId); 2072 } 2073 catch (NumberFormatException e) 2074 { 2075 // Handled just below 2076 } 2077 } 2078 2079 if (stepIdValue == -1) 2080 { 2081 throw new ConfigurationException("Attribute 'step' is mandatory and must be an integer on 'workflow' element in a content type configuration.", workflowConfig); 2082 } 2083 2084 boolean isRead = _parseAccessType(workflowConfig); 2085 2086 if (isRead) 2087 { 2088 restrictions._readWfSteps.add(stepIdValue); 2089 } 2090 else 2091 { 2092 restrictions._writeWfSteps.add(stepIdValue); 2093 } 2094 } 2095 } 2096 } 2097 2098 @Override 2099 public MetadataDefinition getParentMetadata() 2100 { 2101 return _parentMetadataName != null ? _metadata.get(_parentMetadataName) : null; 2102 } 2103}