001/* 002 * Copyright 2014 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.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Iterator; 026import java.util.LinkedHashMap; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Optional; 031import java.util.Set; 032import java.util.TreeSet; 033import java.util.function.Predicate; 034 035import org.apache.avalon.framework.activity.Disposable; 036import org.apache.avalon.framework.component.Component; 037import org.apache.avalon.framework.configuration.ConfigurationException; 038import org.apache.avalon.framework.logger.AbstractLogEnabled; 039import org.apache.avalon.framework.service.ServiceException; 040import org.apache.avalon.framework.service.ServiceManager; 041import org.apache.avalon.framework.service.Serviceable; 042import org.apache.avalon.framework.thread.ThreadSafe; 043import org.apache.commons.collections.CollectionUtils; 044import org.apache.commons.lang3.ArrayUtils; 045import org.apache.commons.lang3.StringUtils; 046 047import org.ametys.cms.content.RootContentHelper; 048import org.ametys.cms.contenttype.indexing.IndexingField; 049import org.ametys.cms.contenttype.indexing.IndexingModel; 050import org.ametys.cms.repository.Content; 051import org.ametys.core.right.RightManager; 052import org.ametys.core.right.RightManager.RightResult; 053import org.ametys.core.ui.Callable; 054import org.ametys.core.user.CurrentUserProvider; 055import org.ametys.core.user.UserIdentity; 056import org.ametys.plugins.repository.AmetysRepositoryException; 057import org.ametys.runtime.i18n.I18nizableText; 058 059/** 060 * Helper for manipulating {@link ContentType}s 061 * 062 */ 063public class ContentTypesHelper extends AbstractLogEnabled implements Component, Serviceable, ThreadSafe, Disposable 064{ 065 /** The Avalon role */ 066 public static final String ROLE = ContentTypesHelper.class.getName(); 067 068 /** The content types extension point */ 069 protected ContentTypeExtensionPoint _cTypeEP; 070 /** The current user provider */ 071 protected CurrentUserProvider _userProvider; 072 /** The rights manager */ 073 protected RightManager _rightManager; 074 /** Helper for root content */ 075 protected RootContentHelper _rootContentHelper; 076 077 private DynamicContentTypeDescriptorExtentionPoint _dynamicCTDescriptorEP; 078 079 // Cache 080 private Map<String, Map<String, MetadataSet>> _cacheForView = new HashMap<>(); 081 private Map<String, Map<String, MetadataSet>> _cacheForEdition = new HashMap<>(); 082 083 private ServiceManager _smanager; 084 085 @Override 086 public void service(ServiceManager smanager) throws ServiceException 087 { 088 _smanager = smanager; 089 _dynamicCTDescriptorEP = (DynamicContentTypeDescriptorExtentionPoint) smanager.lookup(DynamicContentTypeDescriptorExtentionPoint.ROLE); 090 _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 091 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 092 _rootContentHelper = (RootContentHelper) smanager.lookup(RootContentHelper.ROLE); 093 } 094 095 @Override 096 public void dispose() 097 { 098 _cacheForView = new HashMap<>(); 099 _cacheForEdition = new HashMap<>(); 100 } 101 102 /** 103 * Lazy lookup of {@link ContentTypeExtensionPoint} 104 * @return the content type extension point 105 */ 106 protected ContentTypeExtensionPoint _getContentTypeEP() 107 { 108 if (_cTypeEP == null) 109 { 110 try 111 { 112 _cTypeEP = (ContentTypeExtensionPoint) _smanager.lookup(ContentTypeExtensionPoint.ROLE); 113 } 114 catch (ServiceException e) 115 { 116 throw new RuntimeException("Unable to lookup ContentTypeExtensionPoint component", e); 117 } 118 } 119 return _cTypeEP; 120 } 121 122 /** 123 * Determines if a content is a instance of given content type id 124 * 125 * @param content The content 126 * @param cTypeId The id of content type or mixin 127 * @return <code>true</code> if the content is an instance of content type 128 */ 129 public boolean isInstanceOf(Content content, String cTypeId) 130 { 131 String[] types = content.getTypes(); 132 if (ArrayUtils.contains(types, cTypeId)) 133 { 134 return true; 135 } 136 137 String[] mixins = content.getMixinTypes(); 138 if (ArrayUtils.contains(mixins, cTypeId)) 139 { 140 return true; 141 } 142 143 return _containsContentType(ArrayUtils.addAll(types, mixins), cTypeId); 144 } 145 146 private boolean _containsContentType(String[] cTypesId, String cTypeId) 147 { 148 for (String id : cTypesId) 149 { 150 ContentType cType = _getContentTypeEP().getExtension(id); 151 if (cType != null) 152 { 153 if (ArrayUtils.contains(cType.getSupertypeIds(), cTypeId)) 154 { 155 return true; 156 } 157 else if (_containsContentType(cType.getSupertypeIds(), cTypeId)) 158 { 159 return true; 160 } 161 } 162 } 163 return false; 164 } 165 166 /** 167 * Get the id of the content type common ancestor 168 * 169 * @param contentTypes The content types to compare 170 * @return The id of common ancestor or <code>null</code> if not found 171 */ 172 public String getCommonAncestor(Collection<String> contentTypes) 173 { 174 if (contentTypes.isEmpty()) 175 { 176 return null; 177 } 178 179 if (contentTypes.size() == 1) 180 { 181 return contentTypes.iterator().next(); 182 } 183 184 List<Collection<String>> superTypesByCType = new ArrayList<>(); 185 for (String id : contentTypes) 186 { 187 Set<String> superTypes = new HashSet<>(); 188 189 superTypes.add(id); 190 superTypes.addAll(getAncestors(id)); 191 192 superTypesByCType.add(superTypes); 193 } 194 195 Iterator<Collection<String>> superTypesByCTypeIt = superTypesByCType.iterator(); 196 197 Set<String> commonCTypes = new HashSet<>(superTypesByCTypeIt.next()); 198 while (superTypesByCTypeIt.hasNext() && !commonCTypes.isEmpty()) 199 { 200 commonCTypes.retainAll(superTypesByCTypeIt.next()); 201 } 202 203 commonCTypes = removeAncestors(commonCTypes); 204 205 if (commonCTypes.size() == 1) 206 { 207 return commonCTypes.iterator().next(); 208 } 209 210 return null; // No common ancestor 211 } 212 213 /** 214 * Remove all content types in the set that are ancestors of other content types in the set. 215 * @param contentTypes a Set of content type IDs. 216 * @return a Set of content type IDs without ancestors. 217 */ 218 protected Set<String> removeAncestors(Set<String> contentTypes) 219 { 220 Set<String> noAncestors = new HashSet<>(contentTypes); 221 222 Iterator<String> it1 = contentTypes.iterator(); 223 while (it1.hasNext()) 224 { 225 ContentType cType1 = _getContentTypeEP().getExtension(it1.next()); 226 227 Iterator<String> it2 = contentTypes.iterator(); 228 while (it2.hasNext()) 229 { 230 String cType2 = it2.next(); 231 String[] supertypeIds = cType1.getSupertypeIds(); 232 233 // CType2 is an ancestor of CType1: remove it. 234 if (ArrayUtils.contains(supertypeIds, cType2)) 235 { 236 noAncestors.remove(cType2); 237 } 238 } 239 } 240 241 return noAncestors; 242 } 243 244 /** 245 * Get all ancestors for the given content type 246 * 247 * @param contentTypeId The content type id to test 248 * @return A non-null set of all ancestors. Does not contains the contentTypeId itself. 249 * @throws IllegalArgumentException if the content type does not exist. 250 */ 251 public Set<String> getAncestors(String contentTypeId) 252 { 253 Set<String> superTypes = new HashSet<>(); 254 255 ContentType cType = _getContentTypeEP().getExtension(contentTypeId); 256 257 if (cType == null) 258 { 259 throw new IllegalArgumentException("Unable to get anscestors of unknown content type '" + contentTypeId + "'"); 260 } 261 262 263 String[] supertypeIds = cType.getSupertypeIds(); 264 265 for (String superTypeId : supertypeIds) 266 { 267 superTypes.add(superTypeId); 268 superTypes.addAll(getAncestors(superTypeId)); 269 } 270 271 return superTypes; 272 273 } 274 275 /** 276 * Builds the reverse hierarchies of ancestors of a content type 277 * @param contentTypeId The content type's id 278 * @return the reverse hierarchies with ancestors 279 */ 280 public List<Set<String>> buildReverseHierarchies(String contentTypeId) 281 { 282 Set<String> hierarchy = new LinkedHashSet<>(); 283 hierarchy.add(contentTypeId); 284 return _buildReverseHierarchies (contentTypeId, hierarchy); 285 } 286 287 private List<Set<String>> _buildReverseHierarchies(String contentTypeId, Set<String> hierarchy) 288 { 289 List<Set<String>> hierarchies = new ArrayList<>(); 290 291 ContentType cType = _getContentTypeEP().getExtension(contentTypeId); 292 if (cType == null) 293 { 294 throw new IllegalArgumentException("Unable to get anscestors of unknown content type '" + contentTypeId + "'"); 295 } 296 297 String[] supertypeIds = cType.getSupertypeIds(); 298 if (supertypeIds.length > 0) 299 { 300 for (String superTypeId : supertypeIds) 301 { 302 Set<String> superHierarchy = new LinkedHashSet<>(hierarchy); 303 superHierarchy.add(superTypeId); 304 hierarchies.addAll(_buildReverseHierarchies(superTypeId, superHierarchy)); 305 } 306 } 307 else 308 { 309 hierarchies.add(hierarchy); 310 } 311 312 return hierarchies; 313 } 314 315 /** 316 * Retrieves the root metadata names of a content. 317 * @param content The content. 318 * @return the metadata names. 319 */ 320 public Set<String> getMetadataNames(Content content) 321 { 322 return getMetadataNames(content.getTypes(), content.getMixinTypes()); 323 } 324 325 /** 326 * Retrieves the metadata names resulting of the union of metadata 327 * names of given content types and mixins 328 * 329 * @param cTypes The id of content types 330 * @param mixins The id of mixins 331 * @return the metadata names. 332 */ 333 public Set<String> getMetadataNames(String[] cTypes, String[] mixins) 334 { 335 Set<String> metadataNames = new HashSet<>(); 336 337 for (String id : cTypes) 338 { 339 ContentType cType = _getContentTypeEP().getExtension(id); 340 metadataNames.addAll(cType.getMetadataNames()); 341 } 342 343 for (String id : mixins) 344 { 345 ContentType cType = _getContentTypeEP().getExtension(id); 346 metadataNames.addAll(cType.getMetadataNames()); 347 } 348 349 return metadataNames; 350 } 351 352 /** 353 * Get all metadata sets for view resulting of the concatenation of metadata 354 * sets of given content types and mixins. 355 * 356 * @param cTypes The id of content types 357 * @param mixins The id of mixins 358 * @param includeInternal <code>true</code> True to include internal 359 * metadata sets 360 * @return The metadata sets 361 */ 362 public Map<String, MetadataSet> getMetadataSetsForView(String[] cTypes, String[] mixins, boolean includeInternal) 363 { 364 Map<String, MetadataSet> metadataSets = new HashMap<>(); 365 366 Set<String> viewMetadataSetNames = new HashSet<>(); 367 for (String id : cTypes) 368 { 369 ContentType cType = _getContentTypeEP().getExtension(id); 370 for (String name : cType.getViewMetadataSetNames(includeInternal)) 371 { 372 if (!viewMetadataSetNames.contains(name)) 373 { 374 viewMetadataSetNames.add(name); 375 } 376 } 377 } 378 379 for (String viewMetadataSetName : viewMetadataSetNames) 380 { 381 metadataSets.put(viewMetadataSetName, getMetadataSetForView(viewMetadataSetName, cTypes, mixins)); 382 } 383 384 return metadataSets; 385 } 386 387 /** 388 * Get all metadata sets for edition resulting of the concatenation of 389 * metadata sets of given content types and mixins. 390 * 391 * @param cTypes The id of content types 392 * @param mixins The id of mixins 393 * @param includeInternal <code>true</code> True to include internal 394 * metadata sets 395 * @return The metadata sets 396 */ 397 public Map<String, MetadataSet> getMetadataSetsForEdition(String[] cTypes, String[] mixins, boolean includeInternal) 398 { 399 Map<String, MetadataSet> metadataSets = new HashMap<>(); 400 401 Set<String> editionMetadataSetNames = new HashSet<>(); 402 for (String id : cTypes) 403 { 404 ContentType cType = _getContentTypeEP().getExtension(id); 405 for (String name : cType.getEditionMetadataSetNames(includeInternal)) 406 { 407 if (!editionMetadataSetNames.contains(name)) 408 { 409 editionMetadataSetNames.add(name); 410 } 411 } 412 } 413 414 for (String editionMetadataSetName : editionMetadataSetNames) 415 { 416 metadataSets.put(editionMetadataSetName, getMetadataSetForEdition(editionMetadataSetName, cTypes, mixins)); 417 } 418 419 return metadataSets; 420 } 421 422 /** 423 * Get the metadata set for view resulting of the concatenation of metadata 424 * sets of given content types and mixins. 425 * 426 * @param metadataSetName the name of metadata set to retrieve 427 * @param cTypes The id of content types 428 * @param mixins The id of mixins 429 * @return The list of metadata set names. 430 */ 431 public MetadataSet getMetadataSetForView(String metadataSetName, String[] cTypes, String[] mixins) 432 { 433 String cacheId = _getCacheIdentifier(cTypes, mixins); 434 435 if (_cacheForView.containsKey(cacheId) && _cacheForView.get(cacheId).containsKey(metadataSetName)) 436 { 437 return _cacheForView.get(cacheId).get(metadataSetName); 438 } 439 440 MetadataSet joinMetadataSet = new MetadataSet(); 441 boolean foundOne = false; 442 443 for (String id : cTypes) 444 { 445 ContentType cType = _getContentTypeEP().getExtension(id); 446 447 MetadataSet metadataSet = cType.getMetadataSetForView(metadataSetName); 448 if (metadataSet != null) 449 { 450 foundOne = true; 451 copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet); 452 453 if (joinMetadataSet.getName() == null) 454 { 455 joinMetadataSet.setName(metadataSetName); 456 joinMetadataSet.setLabel(metadataSet.getLabel()); 457 joinMetadataSet.setDescription(metadataSet.getDescription()); 458 joinMetadataSet.setIconGlyph(metadataSet.getIconGlyph()); 459 joinMetadataSet.setIconDecorator(metadataSet.getIconDecorator()); 460 joinMetadataSet.setSmallIcon(metadataSet.getSmallIcon()); 461 joinMetadataSet.setMediumIcon(metadataSet.getMediumIcon()); 462 joinMetadataSet.setLargeIcon(metadataSet.getLargeIcon()); 463 joinMetadataSet.setEdition(false); 464 } 465 } 466 } 467 468 for (String id : mixins) 469 { 470 ContentType mixin = _getContentTypeEP().getExtension(id); 471 472 MetadataSet metadataSet = mixin.getMetadataSetForView(metadataSetName); 473 if (metadataSet != null) 474 { 475 foundOne = true; 476 copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet); 477 } 478 } 479 480 if (!foundOne) 481 { 482 return null; 483 } 484 485 if (!_cacheForView.containsKey(cacheId)) 486 { 487 _cacheForView.put(cacheId, new HashMap<String, MetadataSet>()); 488 } 489 _cacheForView.get(cacheId).put(metadataSetName, joinMetadataSet); 490 491 return joinMetadataSet; 492 } 493 494 /** 495 * Get the metadata set for edition resulting of the concatenation of 496 * metadata sets of given content types and mixins. 497 * 498 * @param metadataSetName the name of metadata set to retrieve 499 * @param cTypes The id of content types 500 * @param mixins The id of mixins 501 * @return The metadata set 502 */ 503 public MetadataSet getMetadataSetForEdition(String metadataSetName, String[] cTypes, String[] mixins) 504 { 505 String cacheId = _getCacheIdentifier(cTypes, mixins); 506 507 if (_cacheForEdition.containsKey(cacheId) && _cacheForEdition.get(cacheId).containsKey(metadataSetName)) 508 { 509 return _cacheForEdition.get(cacheId).get(metadataSetName); 510 } 511 512 MetadataSet joinMetadataSet = new MetadataSet(); 513 boolean foundOne = false; 514 515 for (String id : cTypes) 516 { 517 ContentType cType = _getContentTypeEP().getExtension(id); 518 519 MetadataSet metadataSet = cType.getMetadataSetForEdition(metadataSetName); 520 if (metadataSet != null) 521 { 522 foundOne = true; 523 copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet); 524 525 if (joinMetadataSet.getName() == null) 526 { 527 joinMetadataSet.setName(metadataSetName); 528 joinMetadataSet.setLabel(metadataSet.getLabel()); 529 joinMetadataSet.setDescription(metadataSet.getDescription()); 530 joinMetadataSet.setIconGlyph(metadataSet.getIconGlyph()); 531 joinMetadataSet.setIconDecorator(metadataSet.getIconDecorator()); 532 joinMetadataSet.setSmallIcon(metadataSet.getSmallIcon()); 533 joinMetadataSet.setMediumIcon(metadataSet.getMediumIcon()); 534 joinMetadataSet.setLargeIcon(metadataSet.getLargeIcon()); 535 joinMetadataSet.setEdition(true); 536 } 537 } 538 } 539 540 for (String id : mixins) 541 { 542 ContentType mixin = _getContentTypeEP().getExtension(id); 543 544 MetadataSet metadataSet = mixin.getMetadataSetForEdition(metadataSetName); 545 if (metadataSet != null) 546 { 547 foundOne = true; 548 copyMetadataSetElementsIfNotExist(metadataSet, joinMetadataSet); 549 } 550 } 551 552 if (!foundOne) 553 { 554 return null; 555 } 556 557 if (!_cacheForEdition.containsKey(cacheId)) 558 { 559 _cacheForEdition.put(cacheId, new HashMap<String, MetadataSet>()); 560 } 561 _cacheForEdition.get(cacheId).put(metadataSetName, joinMetadataSet); 562 563 return joinMetadataSet; 564 } 565 566 /** 567 * Get the metadata sets of a content type 568 * @param cTypeId the content type id 569 * @param edition Set to true to get edition metadata set. False otherwise. 570 * @param includeInternal Set to true to include internal metadata sets. 571 * @return the metadata sets info 572 */ 573 @Callable 574 public List<Map<String, Object>> getMetadataSetsInfo(String cTypeId, boolean edition, boolean includeInternal) 575 { 576 List<Map<String, Object>> metadataSets = new ArrayList<>(); 577 578 ContentType cType = _getContentTypeEP().getExtension(cTypeId); 579 580 Set<String> metadataSetNames = edition ? cType.getEditionMetadataSetNames(includeInternal) : cType.getViewMetadataSetNames(includeInternal); 581 for (String metadataSetName : metadataSetNames) 582 { 583 MetadataSet metadataSet = edition ? cType.getMetadataSetForEdition(metadataSetName) : cType.getMetadataSetForView(metadataSetName); 584 585 Map<String, Object> viewInfos = new HashMap<>(); 586 viewInfos.put("name", metadataSetName); 587 viewInfos.put("label", metadataSet.getLabel()); 588 viewInfos.put("description", metadataSet.getDescription()); 589 metadataSets.add(viewInfos); 590 } 591 592 return metadataSets; 593 } 594 595 /** 596 * Get the indexing model resulting of the concatenation of indexing models of given content types and mixins. 597 * @param content The content. 598 * @return The indexing model 599 */ 600 public IndexingModel getIndexingModel(Content content) 601 { 602 return getIndexingModel(content.getTypes(), content.getMixinTypes()); 603 } 604 605 /** 606 * Get the indexing model resulting of the concatenation of indexing models of given content types and mixins. 607 * @param cTypes The id of content types 608 * @param mixins The id of mixins 609 * @return The indexing model 610 */ 611 public IndexingModel getIndexingModel(String[] cTypes, String[] mixins) 612 { 613 IndexingModel joinIndexingModel = new IndexingModel(); 614 615 for (String id : cTypes) 616 { 617 ContentType cType = _getContentTypeEP().getExtension(id); 618 619 if (cType != null) 620 { 621 IndexingModel indexingModel = cType.getIndexingModel(); 622 623 for (IndexingField field : indexingModel.getFields()) 624 { 625 joinIndexingModel.addIndexingField(field); 626 } 627 } 628 else 629 { 630 if (getLogger().isWarnEnabled()) 631 { 632 getLogger().warn(String.format("Trying to get indexing model for an unknown content type : '%s'.", id)); 633 } 634 } 635 } 636 637 for (String id : mixins) 638 { 639 ContentType mixin = _getContentTypeEP().getExtension(id); 640 641 if (mixin != null) 642 { 643 IndexingModel indexingModel = mixin.getIndexingModel(); 644 645 for (IndexingField field : indexingModel.getFields()) 646 { 647 joinIndexingModel.addIndexingField(field); 648 } 649 } 650 else 651 { 652 if (getLogger().isWarnEnabled()) 653 { 654 getLogger().warn(String.format("Trying to get indexing model for an unknown mixin type : '%s'.", id)); 655 } 656 } 657 } 658 659 return joinIndexingModel; 660 } 661 662 /** 663 * Copy the elements of metadata set into a metadata set of destination, 664 * only if the elements are not already presents 665 * 666 * @param src The metadata set to copy 667 * @param dest The metadata of destination 668 */ 669 public void copyMetadataSetElementsIfNotExist(AbstractMetadataSetElement src, AbstractMetadataSetElement dest) 670 { 671 _copyMetadataSetElementsIfNotExist(src, dest, null); 672 } 673 674 private void _copyMetadataSetElementsIfNotExist(AbstractMetadataSetElement src, AbstractMetadataSetElement dest, AbstractMetadataSetElement reference) 675 { 676 AbstractMetadataSetElement root = reference == null ? dest : reference; 677 678 for (AbstractMetadataSetElement elmt : src.getElements()) 679 { 680 if (elmt instanceof MetadataDefinitionReference) 681 { 682 String metadataName = ((MetadataDefinitionReference) elmt).getMetadataName(); 683 if (!root.hasMetadataDefinitionReference(metadataName)) 684 { 685 dest.addElement(elmt); 686 } 687 } 688 else if (elmt instanceof Fieldset) 689 { 690 Fieldset fieldset = new Fieldset(); 691 fieldset.setLabel(((Fieldset) elmt).getLabel()); 692 fieldset.setRole(((Fieldset) elmt).getRole()); 693 694 _copyMetadataSetElementsIfNotExist(elmt, fieldset, root); 695 696 if (fieldset.getElements().size() > 0) 697 { 698 dest.addElement(fieldset); 699 } 700 } 701 } 702 } 703 704 /** 705 * Retrieve the definition of a given content metadata value path. 706 * @param metadataValuePath the metadata value path: it is separated by slashes and repeaters are valued: eg: "mycomposite/myrepeater[3]/mymetadata". 707 * @param content the content. 708 * @return the metadata definition or <code>null</code> if not found 709 */ 710 public MetadataDefinition getMetadataDefinitionByMetadataValuePath(String metadataValuePath, Content content) 711 { 712 return getMetadataDefinitionByMetadataValuePath(metadataValuePath, content.getTypes(), content.getMixinTypes()); 713 } 714 715 /** 716 * Retrieve the definition of a given content metadata value path. 717 * @param metadataValuePath the metadata value path: it is separated by slashes and repeaters are valued: eg: "mycomposite.myrepeater[3].mymetadata". 718 * @param cTypes The id of content types 719 * @param mixins The id of mixins 720 * @return the metadata definition or <code>null</code> if not found 721 */ 722 public MetadataDefinition getMetadataDefinitionByMetadataValuePath(String metadataValuePath, String[] cTypes, String[] mixins) 723 { 724 for (String id : cTypes) 725 { 726 ContentType cType = _getContentTypeEP().getExtension(id); 727 728 MetadataDefinition metadataDef = getMetadataDefinitionByMetadataValuePath(metadataValuePath, cType); 729 if (metadataDef != null) 730 { 731 return metadataDef; 732 } 733 } 734 735 for (String id : mixins) 736 { 737 ContentType cType = _getContentTypeEP().getExtension(id); 738 739 MetadataDefinition metadataDef = getMetadataDefinitionByMetadataValuePath(metadataValuePath, cType); 740 if (metadataDef != null) 741 { 742 return metadataDef; 743 } 744 } 745 746 return null; 747 } 748 749 /** 750 * Retrieve the definition of a given content metadata value path. 751 * @param metadataValuePath the metadata value path: it is separated by slashes and repeaters are valued: eg: "mycomposite/myrepeater[3]/mymetadata". 752 * @param metadataDefHolder The metadata def holder (such as the content type) 753 * @return the metadata definition or <code>null</code> if not found 754 */ 755 private MetadataDefinition getMetadataDefinitionByMetadataValuePath(String metadataValuePath, MetadataDefinitionHolder metadataDefHolder) 756 { 757 String metadataName = StringUtils.substringBefore(metadataValuePath, ContentConstants.METADATA_PATH_SEPARATOR); 758 String subMetadataPath = StringUtils.substringAfter(metadataValuePath, ContentConstants.METADATA_PATH_SEPARATOR); 759 // Ignore the repeater entry if any (we're only browsing the model). 760 metadataName = StringUtils.substringBefore(metadataName, "["); 761 762 MetadataDefinition metadataDefinition = metadataDefHolder.getMetadataDefinition(metadataName); 763 if (metadataDefinition != null && StringUtils.isNotBlank(subMetadataPath)) 764 { 765 if (metadataDefinition.getType() == MetadataType.COMPOSITE) 766 { 767 return getMetadataDefinitionByMetadataValuePath(subMetadataPath, metadataDefinition); 768 } 769 else 770 { 771 return null; 772 } 773 } 774 775 return metadataDefinition; 776 } 777 778 /** 779 * Get a flat map of MetadataDefinition corresponding to a metadataset of a content 780 * @param metadataSet The metadataset to get 781 * @return The flat map of metadatapath and MetadataDefinition 782 */ 783 public Set<String> getMetadataPaths(MetadataSet metadataSet) 784 { 785 Set<String> results = new HashSet<>(); 786 787 for (AbstractMetadataSetElement subMetadataSetElement : metadataSet.getElements()) 788 { 789 results.addAll(getMetadataPaths(subMetadataSetElement, "")); 790 } 791 792 return results; 793 } 794 795 /** 796 * Get a flat map of metadatadefinition corresponding to a metadataset of a content 797 * @param metadataSet The metadataset to get 798 * @param prefix The metadata path of the parent 799 * @return The flat map of metadatapath and metadatadefinition 800 */ 801 protected Set<String> getMetadataPaths(AbstractMetadataSetElement metadataSet, String prefix) 802 { 803 Set<String> results = new HashSet<>(); 804 805 MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) metadataSet; 806 807 String metadataName = metadataDefRef.getMetadataName(); 808 809 String metadataPath = prefix + metadataName; 810 results.add(metadataPath); 811 812 for (AbstractMetadataSetElement subMetadataSetElement : metadataSet.getElements()) 813 { 814 results.addAll(getMetadataPaths(subMetadataSetElement, metadataPath + ContentConstants.METADATA_PATH_SEPARATOR)); 815 } 816 817 return results; 818 } 819 820 /** 821 * Retrieve the metadata definition represented by the given path. 822 * The path can represent a metadata on another content. 823 * @param metadataPath the metadata path separated by '/' 824 * @param content The content. 825 * @return the metadata definition or null. 826 */ 827 public MetadataDefinition getMetadataDefinition(String metadataPath, Content content) 828 { 829 List<MetadataDefinition> metadataDefinitionsByPath = getMetadataDefinitionPath(metadataPath, content); 830 return metadataDefinitionsByPath.size() > 0 ? metadataDefinitionsByPath.get(metadataDefinitionsByPath.size() - 1) : null; 831 } 832 833 /** 834 * Get a flat map of MetadataDefinition corresponding to a set of metadata paths 835 * @param metadataPaths A set of metadata path to analyse 836 * @param content The content associated 837 * @return The flat map of metadatapath and MetadataDefinition 838 */ 839 public Map<String, MetadataDefinition> getMetadataDefinitions(Set<String> metadataPaths, Content content) 840 { 841 Map<String, MetadataDefinition> results = new HashMap<>(); 842 843 for (String metadataPath : metadataPaths) 844 { 845 results.put(metadataPath, getMetadataDefinition(metadataPath, content)); 846 } 847 848 return results; 849 } 850 851 /** 852 * Get a flat map of MetadataDefinition corresponding to a metadataSet 853 * @param metadataSet A metadataSet to analyse 854 * @param content The content associated 855 * @return The flat map of metadatapath and MetadataDefinition 856 */ 857 public Map<String, MetadataDefinition> getMetadataDefinitions(MetadataSet metadataSet, Content content) 858 { 859 return getMetadataDefinitions(getMetadataPaths(metadataSet), content); 860 } 861 862 /** 863 * Retrieve the list of successive metadata definitions represented by the given path. 864 * The path can represent a metadata on another content. 865 * @param metadataPath the metadata path separated by '/' 866 * @param content The content. 867 * @return the list of metadata definitions, one by path element. 868 */ 869 public List<MetadataDefinition> getMetadataDefinitionPath(String metadataPath, Content content) 870 { 871 return getMetadataDefinitionPath(metadataPath, content.getTypes(), content.getMixinTypes()); 872 } 873 874 /** 875 * Get a flat map of MetadataDefinition path corresponding to a set of metadata paths 876 * @param metadataPaths A set of metadata path to analyse 877 * @param content The content associated 878 * @return The flat map of metadatapath and corresponding list of MetadataDefinition path 879 */ 880 public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(Set<String> metadataPaths, Content content) 881 { 882 Map<String, List<MetadataDefinition>> results = new HashMap<>(); 883 884 for (String metadataPath : metadataPaths) 885 { 886 results.put(metadataPath, getMetadataDefinitionPath(metadataPath, content)); 887 } 888 889 return results; 890 } 891 892 /** 893 * Get a flat map of MetadataDefinition path corresponding to a metadataset 894 * @param metadataSet A metadataset to analyse 895 * @param content The content associated 896 * @return The flat map of metadatapath and corresponding list of MetadataDefinition path 897 */ 898 public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(MetadataSet metadataSet, Content content) 899 { 900 return getMetadataDefinitionsPaths(getMetadataPaths(metadataSet), content); 901 } 902 903 /** 904 * Retrieve the metadata definition represented by the given path. 905 * The path can represent a metadata on another content. 906 * @param metadataPath the metadata path separated by '/' 907 * @param cTypes The content types. 908 * @param mixins The content mixins types. 909 * @return the metadata definition or null. 910 */ 911 public MetadataDefinition getMetadataDefinition(String metadataPath, String[] cTypes, String[] mixins) 912 { 913 List<MetadataDefinition> metadataDefinitionsByPath = getMetadataDefinitionPath(metadataPath, cTypes, mixins); 914 return metadataDefinitionsByPath.size() > 0 ? metadataDefinitionsByPath.get(metadataDefinitionsByPath.size() - 1) : null; 915 } 916 917 /** 918 * Get a flat map of MetadataDefinition corresponding to a set of metadata paths 919 * @param metadataPaths A set of metadata path to analyse 920 * @param cTypes The content types. 921 * @param mixins The content mixins types. 922 * @return The flat map of metadatapath and MetadataDefinition 923 */ 924 public Map<String, MetadataDefinition> getMetadataDefinitions(Set<String> metadataPaths, String[] cTypes, String[] mixins) 925 { 926 Map<String, MetadataDefinition> results = new HashMap<>(); 927 928 for (String metadataPath : metadataPaths) 929 { 930 results.put(metadataPath, getMetadataDefinition(metadataPath, cTypes, mixins)); 931 } 932 933 return results; 934 } 935 936 /** 937 * Get a flat map of MetadataDefinition corresponding to a metadataset 938 * @param metadataSet A metadataset to analyse 939 * @param cTypes The content types. 940 * @param mixins The content mixins types. 941 * @return The flat map of metadatapath and MetadataDefinition 942 */ 943 public Map<String, MetadataDefinition> getMetadataDefinitions(MetadataSet metadataSet, String[] cTypes, String[] mixins) 944 { 945 return getMetadataDefinitions(getMetadataPaths(metadataSet), cTypes, mixins); 946 } 947 948 /** 949 * Get a flat map of MetadataDefinition corresponding to the required predicate (boolean-valued function) 950 * For example, to get all metadata definitions of type CONTENT, we can do: 951 * getMetadataDefinitions(contentType, m -> m.getType() == MetadataType.CONTENT) 952 * 953 * @param metaDefHolder The metadata definition holder 954 * @param predicate The predicate (ex. m -> m.getType() == MetadataType.CONTENT) 955 * @return The flat map of metadata's path and their definition 956 */ 957 public Map<String, MetadataDefinition> getMetadataDefinitions(MetadataDefinitionHolder metaDefHolder, Predicate<MetadataDefinition> predicate) 958 { 959 Map<String, MetadataDefinition> metaDefs = new HashMap<>(); 960 961 Set<String> metadataNames = metaDefHolder.getMetadataNames(); 962 for (String metadataName : metadataNames) 963 { 964 MetadataDefinition metadataDef = metaDefHolder.getMetadataDefinition(metadataName); 965 if (predicate.test(metadataDef)) 966 { 967 metaDefs.put(metadataDef.getId(), metadataDef); 968 } 969 970 if (metadataDef.getType() == MetadataType.COMPOSITE || metadataDef instanceof RepeaterDefinition) 971 { 972 metaDefs.putAll(getMetadataDefinitions(metadataDef, predicate)); 973 } 974 } 975 976 return metaDefs; 977 } 978 979 /** 980 * Retrieve the list of successive metadata definitions represented by the given path. 981 * The path can represent a metadata on another content. 982 * @param metadataPath the metadata path separated by '/' 983 * @param cTypes The id of content types 984 * @param mixins The id of mixins 985 * @return the metadata definition or <code>null</code> if not found 986 */ 987 public List<MetadataDefinition> getMetadataDefinitionPath(String metadataPath, String[] cTypes, String[] mixins) 988 { 989 String[] allContentTypes = ArrayUtils.addAll(cTypes, mixins); 990 991 for (String cTypeId : allContentTypes) 992 { 993 ContentType cType = _getContentTypeEP().getExtension(cTypeId); 994 if (cType != null) 995 { 996 List<MetadataDefinition> metaDefs = getMetadataDefinitionPath(metadataPath, cType); 997 if (!metaDefs.isEmpty()) 998 { 999 return metaDefs; 1000 } 1001 } 1002 else 1003 { 1004 if (getLogger().isWarnEnabled()) 1005 { 1006 getLogger().warn("Unknown content type identifier : " + cTypeId); 1007 } 1008 } 1009 } 1010 1011 return Collections.emptyList(); 1012 } 1013 1014 /** 1015 * Get a flat map of MetadataDefinition path corresponding to a set of metadata paths 1016 * @param metadataPaths A set of metadata path to analyse 1017 * @param cTypes The id of content types 1018 * @param mixins The id of mixins 1019 * @return The flat map of metadatapath and corresponding list of MetadataDefinition path 1020 */ 1021 public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(Set<String> metadataPaths, String[] cTypes, String[] mixins) 1022 { 1023 Map<String, List<MetadataDefinition>> results = new HashMap<>(); 1024 1025 for (String metadataPath : metadataPaths) 1026 { 1027 results.put(metadataPath, getMetadataDefinitionPath(metadataPath, cTypes, mixins)); 1028 } 1029 1030 return results; 1031 } 1032 1033 /** 1034 * Get a flat map of MetadataDefinition path corresponding to a metadataset 1035 * @param metadataSet A metadataset to analyse 1036 * @param cTypes The id of content types 1037 * @param mixins The id of mixins 1038 * @return The flat map of metadatapath and corresponding list of MetadataDefinition path 1039 */ 1040 public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(MetadataSet metadataSet, String[] cTypes, String[] mixins) 1041 { 1042 return getMetadataDefinitionsPaths(getMetadataPaths(metadataSet), cTypes, mixins); 1043 } 1044 1045 /** 1046 * Retrieves a metadata definition from a path. The metadata can be defined in a referenced or sub content. 1047 * @param metadataPath the metadata path separated by '/' 1048 * @param initialContentType The initial content type to start the search 1049 * @return the metadata definition or <code>null</code> if not found 1050 */ 1051 public MetadataDefinition getMetadataDefinition(String metadataPath, ContentType initialContentType) 1052 { 1053 List<MetadataDefinition> metadataDefinitionsByPath = getMetadataDefinitionPath(metadataPath, initialContentType); 1054 return metadataDefinitionsByPath.size() > 0 ? metadataDefinitionsByPath.get(metadataDefinitionsByPath.size() - 1) : null; 1055 } 1056 1057 /** 1058 * Get a flat map of MetadataDefinition corresponding to a set of metadata paths 1059 * @param metadataPaths A set of metadata path to analyse 1060 * @param initialContentType The initial content type to start the search 1061 * @return The flat map of metadatapath and MetadataDefinition 1062 */ 1063 public Map<String, MetadataDefinition> getMetadataDefinitions(Set<String> metadataPaths, ContentType initialContentType) 1064 { 1065 Map<String, MetadataDefinition> results = new HashMap<>(); 1066 1067 for (String metadataPath : metadataPaths) 1068 { 1069 results.put(metadataPath, getMetadataDefinition(metadataPath, initialContentType)); 1070 } 1071 1072 return results; 1073 } 1074 1075 /** 1076 * Get a flat map of MetadataDefinition corresponding to a metadataset 1077 * @param metadataSet A metadataset to analyse 1078 * @param initialContentType The initial content type to start the search 1079 * @return The flat map of metadatapath and MetadataDefinition 1080 */ 1081 public Map<String, MetadataDefinition> getMetadataDefinitions(MetadataSet metadataSet, ContentType initialContentType) 1082 { 1083 return getMetadataDefinitions(getMetadataPaths(metadataSet), initialContentType); 1084 } 1085 1086 /** 1087 * Retrieve the list of successive metadata definitions represented by the given path. 1088 * The path can represent a metadata on another content. 1089 * @param initialContentType The initial content type to start the search 1090 * @param metadataPath the metadata path separated by '/' 1091 * @return the list of metadata definitions, one by path element. 1092 */ 1093 public List<MetadataDefinition> getMetadataDefinitionPath(String metadataPath, ContentType initialContentType) 1094 { 1095 List<MetadataDefinition> definitions = new ArrayList<>(); 1096 1097 String[] pathSegments = StringUtils.split(metadataPath, ContentConstants.METADATA_PATH_SEPARATOR); 1098 1099 if (pathSegments.length > 0) 1100 { 1101 MetadataDefinition metadataDef = initialContentType.getMetadataDefinition(pathSegments[0]); 1102 1103 if (metadataDef != null) 1104 { 1105 definitions.add(metadataDef); 1106 } 1107 else 1108 { 1109 return Collections.emptyList(); 1110 } 1111 1112 for (int i = 1; i < pathSegments.length; i++) 1113 { 1114 if (metadataDef.getType() == MetadataType.CONTENT || metadataDef.getType() == MetadataType.SUB_CONTENT) 1115 { 1116 String refCTypeId = metadataDef.getContentType(); 1117 if (refCTypeId != null && _getContentTypeEP().hasExtension(refCTypeId)) 1118 { 1119 ContentType refCType = _getContentTypeEP().getExtension(refCTypeId); 1120 1121 List<MetadataDefinition> followingDefs = getMetadataDefinitionPath(StringUtils.join(pathSegments, ContentConstants.METADATA_PATH_SEPARATOR, i, pathSegments.length), refCType); 1122 if (CollectionUtils.isEmpty(followingDefs)) 1123 { 1124 return Collections.emptyList(); 1125 } 1126 definitions.addAll(followingDefs); 1127 1128 return definitions; 1129 } 1130 else if ("title".equals(pathSegments[i])) 1131 { 1132 // No specific content type: allow only title. 1133 definitions.add(getTitleMetadataDefinition()); 1134 return definitions; 1135 } 1136 else 1137 { 1138 return Collections.emptyList(); 1139 } 1140 } 1141 else 1142 { 1143 metadataDef = metadataDef.getMetadataDefinition(pathSegments[i]); 1144 if (metadataDef != null) 1145 { 1146 definitions.add(metadataDef); 1147 } 1148 else 1149 { 1150 return Collections.emptyList(); 1151 } 1152 } 1153 } 1154 } 1155 1156 return definitions; 1157 } 1158 1159 /** 1160 * Get a flat map of MetadataDefinition path corresponding to a set of metadata paths 1161 * @param metadataPaths A set of metadata path to analyse 1162 * @param initialContentType The initial content type to start the search 1163 * @return The flat map of metadatapath and corresponding list of MetadataDefinition path 1164 */ 1165 public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(Set<String> metadataPaths, ContentType initialContentType) 1166 { 1167 Map<String, List<MetadataDefinition>> results = new HashMap<>(); 1168 1169 for (String metadataPath : metadataPaths) 1170 { 1171 results.put(metadataPath, getMetadataDefinitionPath(metadataPath, initialContentType)); 1172 } 1173 1174 return results; 1175 } 1176 1177 /** 1178 * Get a flat map of MetadataDefinition path corresponding to a metadataSet 1179 * @param metadataSet A semetadataSet to analyse 1180 * @param initialContentType The initial content type to start the search 1181 * @return The flat map of metadatapath and corresponding list of MetadataDefinition path 1182 */ 1183 public Map<String, List<MetadataDefinition>> getMetadataDefinitionsPaths(MetadataSet metadataSet, ContentType initialContentType) 1184 { 1185 return getMetadataDefinitionsPaths(getMetadataPaths(metadataSet), initialContentType); 1186 } 1187 1188 /** 1189 * Determines if the given content type can be added to content 1190 * 1191 * @param content The content 1192 * @param cTypeId The id of content type 1193 * @return <code>true</code> if the content type is compatible with content 1194 */ 1195 public boolean isCompatibleContentType(Content content, String cTypeId) 1196 { 1197 String[] currentContentTypes = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes()); 1198 1199 ArrayList<String> cTypes = new ArrayList<>(Arrays.asList(currentContentTypes)); 1200 cTypes.add(cTypeId); 1201 1202 try 1203 { 1204 getMetadataDefinitions(cTypes.toArray(new String[cTypes.size()])); 1205 return true; 1206 } 1207 catch (ConfigurationException e) 1208 { 1209 return false; 1210 } 1211 } 1212 1213 /** 1214 * Retrieves all definitions of a metadata resulting of the concatenation of 1215 * metadata of given content types. 1216 * 1217 * @param cTypes The id of content types 1218 * @return the metadata definitions 1219 * @throws ConfigurationException if an error occurred 1220 */ 1221 public Map<String, MetadataDefinition> getMetadataDefinitions(String[] cTypes) throws ConfigurationException 1222 { 1223 Map<String, MetadataDefinition> metadata = new LinkedHashMap<>(); 1224 1225 for (String id : cTypes) 1226 { 1227 ContentType cType = _getContentTypeEP().getExtension(id); 1228 1229 for (String name : cType.getMetadataNames()) 1230 { 1231 MetadataDefinition definition = cType.getMetadataDefinition(name); 1232 1233 if (metadata.containsKey(name)) 1234 { 1235 if (!definition.getReferenceContentType().equals(metadata.get(name).getReferenceContentType())) 1236 { 1237 // The definition does not provide from a common 1238 // ancestor 1239 throw new ConfigurationException("The metadata '" + name + "' defined in content-type '" + id + "' is already defined in another co-super-type '" 1240 + metadata.get(name).getReferenceContentType() + "'"); 1241 } 1242 continue; 1243 } 1244 1245 metadata.put(name, definition); 1246 } 1247 } 1248 1249 return metadata; 1250 } 1251 1252 /** 1253 * Retrieves the common metadata definitions for a list of content types 1254 * @param cTypeIds The list of content types to consider 1255 * @param metadataSetName The metadata set name to list metadata 1256 * @param isEdition Is the metadata set for edition (or for view) 1257 * @return The map of metadata definition. Key are the metadata path in the content type 1258 */ 1259 public Map<String, MetadataDefinition> getCommonMetadataDefinitions(Collection<String> cTypeIds, String metadataSetName, boolean isEdition) 1260 { 1261 Map<String, MetadataDefinition> commonMetadataDefinitions = null; 1262 1263 for (String cTypeId : cTypeIds) 1264 { 1265 ContentType cType = _getContentTypeEP().getExtension(cTypeId); 1266 AbstractMetadataSetElement metadataSetElement = _getMetadataSet(cType, metadataSetName, isEdition); 1267 1268 Map<String, MetadataDefinition> accumulator = new LinkedHashMap<>(); 1269 if (metadataSetElement != null) 1270 { 1271 _getMetadataDefinitionsAcc(accumulator, metadataSetElement, "", cType); 1272 } 1273 1274 if (commonMetadataDefinitions == null) 1275 { 1276 commonMetadataDefinitions = new LinkedHashMap<>(); 1277 for (String name : accumulator.keySet()) 1278 { 1279 commonMetadataDefinitions.put(name, accumulator.get(name)); 1280 } 1281 } 1282 else 1283 { 1284 // only retains common metadata (performs a set intersection) 1285 commonMetadataDefinitions.keySet().retainAll(accumulator.keySet()); 1286 } 1287 } 1288 1289 return commonMetadataDefinitions != null ? commonMetadataDefinitions : Collections.emptyMap(); 1290 } 1291 1292 /** 1293 * Populate the accumulator with the metadata definition 1294 * @param internalAcc the accumulator of metadata definition 1295 * @param metadataSetElement the metadata set of the content 1296 * @param prefix the path prefix preceding the names of the metadata 1297 * @param metaDefHolder the holder of the metadata definitions for the content 1298 */ 1299 protected void _getMetadataDefinitionsAcc(Map<String, MetadataDefinition> internalAcc, AbstractMetadataSetElement metadataSetElement, String prefix, MetadataDefinitionHolder metaDefHolder) 1300 { 1301 if (metadataSetElement instanceof MetadataDefinitionReference) 1302 { 1303 MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) metadataSetElement; 1304 String subMetadataName = metadataDefRef.getMetadataName(); 1305 MetadataDefinition subMetadataDef = metaDefHolder.getMetadataDefinition(subMetadataName); 1306 1307 if (subMetadataDef != null) 1308 { 1309 if (MetadataType.COMPOSITE.equals(subMetadataDef.getType())) 1310 { 1311 for (AbstractMetadataSetElement subElement : metadataSetElement.getElements()) 1312 { 1313 String path = prefix + subMetadataName; 1314 internalAcc.put(path, subMetadataDef); 1315 _getMetadataDefinitionsAcc(internalAcc, subElement, path + "/", subMetadataDef); 1316 } 1317 } 1318 else 1319 { 1320 String path = prefix + subMetadataName; 1321 internalAcc.put(path, subMetadataDef); 1322 } 1323 } 1324 } 1325 else 1326 { 1327 for (AbstractMetadataSetElement subElement : metadataSetElement.getElements()) 1328 { 1329 _getMetadataDefinitionsAcc(internalAcc, subElement, prefix, metaDefHolder); 1330 } 1331 } 1332 } 1333 1334 /** 1335 * Get the metadataset of a content given the parameters 1336 * @param contentType The content type to consider 1337 * @param metadataSetName The metadata set to get (that list the metadata to consider) 1338 * @param isEdition Is the metadata set for edition (or for view) 1339 * @return The metadataset 1340 */ 1341 protected MetadataSet _getMetadataSet(ContentType contentType, String metadataSetName, boolean isEdition) 1342 { 1343 String name = metadataSetName; 1344 if (StringUtils.isBlank(name)) 1345 { 1346 name = "main"; 1347 } 1348 1349 MetadataSet metadataSet = null; 1350 1351 if (isEdition) 1352 { 1353 metadataSet = contentType.getMetadataSetForEdition(name); 1354 } 1355 else 1356 { 1357 metadataSet = contentType.getMetadataSetForView(name); 1358 } 1359 1360 return metadataSet; 1361 } 1362 1363 /** 1364 * Get information on content types.<br> 1365 * @return A Map with content types 1366 */ 1367 public Set<Map<String, Object>> getContentTypesInformations() 1368 { 1369 Set<Map<String, Object>> result = new HashSet<>(); 1370 1371 // Collect content types. 1372 Collection<String> allContentTypesIds = _getContentTypeEP().getExtensionsIds(); 1373 for (String id : allContentTypesIds) 1374 { 1375 ContentType cType = _getContentTypeEP().getExtension(id); 1376 1377 if (cType != null) 1378 { 1379 Map<String, Object> contentTypeProperties = getContentTypeProperties(cType); 1380 contentTypeProperties.put("rightEvaluated", _hasRight(cType)); 1381 result.add(contentTypeProperties); 1382 } 1383 } 1384 1385 return result; 1386 } 1387 1388 /** 1389 * Get information on content types.<br> 1390 * 1391 * @param ids The id of content types to retrieve 1392 * @param inherited If true, the sub-types will be also returned. 1393 * @param checkRights If true, only content types allowed for creation will 1394 * be returned 1395 * @param includePrivate If true, the list will include the private content types. By default the list is restricted to the public content types. 1396 * @param includeMixins If true the list will include the mixins content types. 1397 * @param includeAbstract If true the list will include the abstract content types. 1398 * @return A Map with retrieved, unknown, not-allowed and private content types 1399 */ 1400 @Callable 1401 public Map<String, Object> getContentTypesList (List<String> ids, boolean inherited, boolean checkRights, boolean includePrivate, boolean includeMixins, boolean includeAbstract) 1402 { 1403 // Collect content types. 1404 Collection<String> allContentTypesIds = new ArrayList<>(); 1405 if (ids == null || ids.isEmpty()) 1406 { 1407 allContentTypesIds = _getContentTypeEP().getExtensionsIds(); 1408 } 1409 else 1410 { 1411 for (String id : ids) 1412 { 1413 _addIfNotPresent(allContentTypesIds, id); 1414 1415 if (inherited) 1416 { 1417 for (String subTypeId : _getContentTypeEP().getSubTypes(id)) 1418 { 1419 _addIfNotPresent(allContentTypesIds, subTypeId); 1420 } 1421 } 1422 } 1423 } 1424 1425 // Resolve and organize content types 1426 return _dispatchContentTypes(checkRights, includePrivate, includeMixins, includeAbstract, allContentTypesIds); 1427 } 1428 1429 private Map<String, Object> _dispatchContentTypes(boolean checkRights, boolean includePrivate, boolean includeMixins, boolean includeAbstract, Collection<String> allContentTypesIds) 1430 { 1431 List<Map<String, Object>> contentTypes = new ArrayList<>(); 1432 List<String> unknownContentTypes = new ArrayList<>(); 1433 List<String> noRightContentTypes = new ArrayList<>(); 1434 List<String> privateContentTypes = new ArrayList<>(); 1435 List<String> mixinContentTypes = new ArrayList<>(); 1436 List<String> abstractContentTypes = new ArrayList<>(); 1437 1438 for (String id : allContentTypesIds) 1439 { 1440 ContentType cType = _getContentTypeEP().getExtension(id); 1441 1442 if (cType != null) 1443 { 1444 if (cType.isAbstract() && !includeAbstract) 1445 { 1446 abstractContentTypes.add(id); 1447 } 1448 else if (cType.isPrivate() && !includePrivate) 1449 { 1450 privateContentTypes.add(id); 1451 } 1452 else if (cType.isMixin() && !includeMixins) 1453 { 1454 mixinContentTypes.add(id); 1455 } 1456 else if (!checkRights || _hasRight(cType)) 1457 { 1458 contentTypes.add(getContentTypeProperties(cType)); 1459 } 1460 else 1461 { 1462 noRightContentTypes.add(id); 1463 } 1464 } 1465 else 1466 { 1467 unknownContentTypes.add(id); 1468 } 1469 } 1470 1471 Map<String, Object> result = new HashMap<>(); 1472 1473 result.put("contentTypes", contentTypes); 1474 result.put("noRightContentTypes", noRightContentTypes); 1475 result.put("unknownContentTypes", unknownContentTypes); 1476 result.put("privateContentTypes", privateContentTypes); 1477 result.put("mixinContentTypes", mixinContentTypes); 1478 result.put("abstractContentTypes", abstractContentTypes); 1479 1480 return result; 1481 } 1482 1483 /** 1484 * Get the content type properties 1485 * 1486 * @param contentType The content type 1487 * @return The content type properties 1488 */ 1489 public Map<String, Object> getContentTypeProperties(ContentType contentType) 1490 { 1491 Map<String, Object> infos = new HashMap<>(); 1492 1493 infos.put("id", contentType.getId()); 1494 infos.put("label", contentType.getLabel()); 1495 infos.put("description", contentType.getDescription()); 1496 infos.put("defaultTitle", contentType.getDefaultTitle()); 1497 infos.put("iconGlyph", contentType.getIconGlyph()); 1498 infos.put("iconDecorator", contentType.getIconDecorator()); 1499 infos.put("iconSmall", contentType.getSmallIcon()); 1500 infos.put("iconMedium", contentType.getMediumIcon()); 1501 infos.put("iconLarge", contentType.getLargeIcon()); 1502 infos.put("right", contentType.getRight()); 1503 infos.put("isMultilingual", contentType.isMultilingual()); 1504 infos.put("isSimple", contentType.isSimple()); 1505 infos.put("isPrivate", contentType.isPrivate()); 1506 infos.put("isAbstract", contentType.isAbstract()); 1507 infos.put("isReferenceTable", contentType.isReferenceTable()); 1508 infos.put("isMixin", contentType.isMixin()); 1509 infos.put("superTypes", contentType.getSupertypeIds()); 1510 infos.put("tags", contentType.getTags()); 1511 infos.put("parentMetadataName", Optional.ofNullable(contentType.getParentMetadata()).map(MetadataDefinition::getId).orElse("")); 1512 1513 return infos; 1514 } 1515 1516 /** 1517 * Test if the current user has the right needed by the content type to create a content. 1518 * @param contentType The content type 1519 * @return true if the user has the right needed, false otherwise. 1520 */ 1521 protected boolean _hasRight(ContentType contentType) 1522 { 1523 boolean hasRight = false; 1524 1525 String right = contentType.getRight(); 1526 1527 if (right == null) 1528 { 1529 hasRight = true; 1530 } 1531 else 1532 { 1533 UserIdentity user = _userProvider.getUser(); 1534 hasRight = _rightManager.hasRight(user, right, "/cms") == RightResult.RIGHT_ALLOW || _rightManager.hasRight(user, right, _rootContentHelper.getRootContent()) == RightResult.RIGHT_ALLOW; 1535 } 1536 1537 return hasRight; 1538 } 1539 1540 private void _addIfNotPresent(Collection<String> collection, String value) 1541 { 1542 if (!collection.contains(value)) 1543 { 1544 collection.add(value); 1545 } 1546 } 1547 1548 /** 1549 * Determine whether a metadata can be read at this time. 1550 * 1551 * @param metadataDef the metadata definition 1552 * @param content The content where metadata is to be read on. 1553 * @return <code>true</code> if the current user is allowed to read the 1554 * metadata of this content. 1555 * @throws AmetysRepositoryException if an error occurs while accessing the 1556 * content. 1557 */ 1558 public boolean canRead(Content content, MetadataDefinition metadataDef) 1559 { 1560 String id = metadataDef.getReferenceContentType(); 1561 ContentType cType = _getContentTypeEP().getExtension(id); 1562 return cType.canRead(content, metadataDef); 1563 } 1564 1565 /** 1566 * Determine whether a metadata can be read at this time. 1567 * 1568 * @param metadataDef the metadata definition 1569 * @param content The content where metadata is to be read on. 1570 * @return <code>true</code> if the current user is allowed to read the 1571 * metadata of this content. 1572 * @throws AmetysRepositoryException if an error occurs while accessing the 1573 * content. 1574 */ 1575 public boolean canWrite(Content content, MetadataDefinition metadataDef) 1576 { 1577 String id = metadataDef.getReferenceContentType(); 1578 ContentType cType = _getContentTypeEP().getExtension(id); 1579 return cType.canWrite(content, metadataDef); 1580 } 1581 1582 /** 1583 * Get the id of content type to use for rendering 1584 * 1585 * @param content The content 1586 * @return the id of dynamic or standard content type 1587 */ 1588 public String getContentTypeIdForRendering(Content content) 1589 { 1590 DynamicContentTypeDescriptor dynamicContentType = _dynamicCTDescriptorEP.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1591 if (dynamicContentType != null) 1592 { 1593 return dynamicContentType.getId(); 1594 } 1595 1596 ContentType firstContentType = getFirstContentType(content); 1597 return firstContentType != null ? firstContentType.getId() : StringUtils.EMPTY; 1598 } 1599 1600 /** 1601 * Get the plugin name of content type to use for rendering 1602 * 1603 * @param content The content 1604 * @return the plugin name of dynamic or standard content type 1605 */ 1606 public String getContentTypePluginForRendering(Content content) 1607 { 1608 DynamicContentTypeDescriptor dynamicContentType = _dynamicCTDescriptorEP.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1609 if (dynamicContentType != null) 1610 { 1611 return dynamicContentType.getPluginName(); 1612 } 1613 1614 ContentType firstContentType = getFirstContentType(content); 1615 return firstContentType != null ? firstContentType.getPluginName() : StringUtils.EMPTY; 1616 } 1617 1618 /** 1619 * Retrieves the label of the content type. 1620 * 1621 * @param content The content 1622 * @return the label. 1623 */ 1624 public I18nizableText getContentTypeLabel(Content content) 1625 { 1626 DynamicContentTypeDescriptor dynamicContentType = _dynamicCTDescriptorEP.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1627 if (dynamicContentType != null) 1628 { 1629 return dynamicContentType.getLabel(); 1630 } 1631 1632 ContentType firstContentType = getFirstContentType(content); 1633 return firstContentType != null ? firstContentType.getLabel() : new I18nizableText(StringUtils.EMPTY); 1634 } 1635 1636 /** 1637 * Retrieves the description of the content type. 1638 * 1639 * @param content The content 1640 * @return the label. 1641 */ 1642 public I18nizableText getContentTypeDescription(Content content) 1643 { 1644 DynamicContentTypeDescriptor dynamicContentType = _dynamicCTDescriptorEP.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1645 if (dynamicContentType != null) 1646 { 1647 return dynamicContentType.getDescription(); 1648 } 1649 1650 ContentType firstContentType = getFirstContentType(content); 1651 return firstContentType != null ? firstContentType.getDescription() : new I18nizableText(StringUtils.EMPTY); 1652 } 1653 1654 /** 1655 * Retrieves the default title of the content type. 1656 * 1657 * @param content The content 1658 * @return the label. 1659 */ 1660 public I18nizableText getContentTypeDefaultTitle(Content content) 1661 { 1662 DynamicContentTypeDescriptor dynamicContentType = _dynamicCTDescriptorEP.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1663 if (dynamicContentType != null) 1664 { 1665 return dynamicContentType.getDefaultTitle(); 1666 } 1667 1668 ContentType firstContentType = getFirstContentType(content); 1669 return firstContentType != null ? firstContentType.getDefaultTitle() : new I18nizableText(StringUtils.EMPTY); 1670 } 1671 1672 /** 1673 * Retrieves the category of the content type. 1674 * 1675 * @param content The content 1676 * @return the label. 1677 */ 1678 public I18nizableText getContentTypeCategory(Content content) 1679 { 1680 DynamicContentTypeDescriptor dynamicCTDescriptor = _dynamicCTDescriptorEP.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1681 if (dynamicCTDescriptor != null) 1682 { 1683 return dynamicCTDescriptor.getCategory(); 1684 } 1685 1686 ContentType firstContentType = getFirstContentType(content); 1687 return firstContentType != null ? firstContentType.getCategory() : new I18nizableText(StringUtils.EMPTY); 1688 } 1689 1690 /** 1691 * Retrieves the CSS class to use as glyph icon of the content 1692 * @param content The content 1693 * @return the glyph 1694 */ 1695 public String getIconGlyph(Content content) 1696 { 1697 DynamicContentTypeDescriptor dynamicCTDescriptor = _dynamicCTDescriptorEP.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1698 if (dynamicCTDescriptor != null) 1699 { 1700 return dynamicCTDescriptor.getIconGlyph(); 1701 } 1702 1703 ContentType firstContentType = getFirstContentType(content); 1704 return firstContentType != null ? firstContentType.getIconGlyph() : null; 1705 } 1706 1707 /** 1708 * Retrieves the CSS class to use as decorator above the main icon 1709 * @param content The content 1710 * @return the decorator CSS class name 1711 */ 1712 public String getIconDecorator(Content content) 1713 { 1714 DynamicContentTypeDescriptor dynamicCTDescriptor = _dynamicCTDescriptorEP.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1715 if (dynamicCTDescriptor != null) 1716 { 1717 return dynamicCTDescriptor.getIconDecorator(); 1718 } 1719 1720 ContentType firstContentType = getFirstContentType(content); 1721 return firstContentType != null ? firstContentType.getIconDecorator() : null; 1722 } 1723 1724 /** 1725 * Retrieves the URL of the icon without the context path. 1726 * 1727 * @param content The content 1728 * @return the icon URL for the small image 16x16. 1729 */ 1730 public String getSmallIcon(Content content) 1731 { 1732 DynamicContentTypeDescriptor dynamicCTDescriptor = _dynamicCTDescriptorEP.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1733 if (dynamicCTDescriptor != null) 1734 { 1735 return dynamicCTDescriptor.getSmallIcon(); 1736 } 1737 1738 ContentType firstContentType = getFirstContentType(content); 1739 return firstContentType != null ? firstContentType.getSmallIcon() : StringUtils.EMPTY; 1740 } 1741 1742 /** 1743 * Retrieves the URL of the icon without the context path. 1744 * 1745 * @param content The content 1746 * @return the icon URL for the medium image 32x32. 1747 */ 1748 public String getMediumIcon(Content content) 1749 { 1750 DynamicContentTypeDescriptor dynamicCTDescriptor = _dynamicCTDescriptorEP.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1751 if (dynamicCTDescriptor != null) 1752 { 1753 return dynamicCTDescriptor.getMediumIcon(); 1754 } 1755 1756 ContentType firstContentType = getFirstContentType(content); 1757 return firstContentType != null ? firstContentType.getMediumIcon() : StringUtils.EMPTY; 1758 } 1759 1760 /** 1761 * Retrieves the URL of the icon without the context path. 1762 * 1763 * @param content The content 1764 * @return the icon URL for the large image 48x48. 1765 */ 1766 public String getLargeIcon(Content content) 1767 { 1768 DynamicContentTypeDescriptor dynamicCTDescriptor = _dynamicCTDescriptorEP.getMatchingDescriptor(content.getTypes(), content.getMixinTypes()); 1769 if (dynamicCTDescriptor != null) 1770 { 1771 return dynamicCTDescriptor.getLargeIcon(); 1772 } 1773 1774 ContentType firstContentType = getFirstContentType(content); 1775 return firstContentType != null ? firstContentType.getLargeIcon() : StringUtils.EMPTY; 1776 } 1777 1778 /** 1779 * Get the content type which determines the content icons and rendering 1780 * 1781 * @param content The content 1782 * @return The main content type 1783 */ 1784 public ContentType getFirstContentType(Content content) 1785 { 1786 TreeSet<ContentType> treeSet = new TreeSet<>(new ContentTypeComparator()); 1787 1788 for (String id : content.getTypes()) 1789 { 1790 ContentType contentType = _getContentTypeEP().getExtension(id); 1791 if (contentType != null) 1792 { 1793 treeSet.add(contentType); 1794 } 1795 else 1796 { 1797 if (getLogger().isWarnEnabled()) 1798 { 1799 getLogger().warn(String.format("Trying to get an unknown content type : '%s'.", id)); 1800 } 1801 } 1802 } 1803 1804 return !treeSet.isEmpty() ? treeSet.first() : null; 1805 } 1806 1807 /** 1808 * Get the metadata definition for the "title" standard metadata. 1809 * @return The standard title metadata definition. 1810 */ 1811 public static MetadataDefinition getTitleMetadataDefinition() 1812 { 1813 MetadataDefinition def = new MetadataDefinition(); 1814 def.setId("title"); 1815 def.setName("title"); 1816 def.setLabel(new I18nizableText("plugin.cms", "PLUGINS_CMS_METADATA_TITLE_LABEL")); 1817 def.setDescription(new I18nizableText("plugin.cms", "PLUGINS_CMS_METADATA_TITLE_DESCRIPTION")); 1818 def.setType(MetadataType.STRING); 1819 def.setMultiple(false); 1820 1821 return def; 1822 } 1823 1824 class ContentTypeComparator implements Comparator<ContentType> 1825 { 1826 @Override 1827 public int compare(ContentType c1, ContentType c2) 1828 { 1829 I18nizableText t1 = c1.getLabel(); 1830 I18nizableText t2 = c2.getLabel(); 1831 1832 String str1 = t1.isI18n() ? t1.getKey() : t1.getLabel(); 1833 String str2 = t2.isI18n() ? t2.getKey() : t2.getLabel(); 1834 1835 int compareTo = str1.toString().compareTo(str2.toString()); 1836 if (compareTo == 0) 1837 { 1838 // Content types have same keys but there are not equals, so do 1839 // not return 0 to add it in TreeSet 1840 // Indeed, in a TreeSet implementation two elements that are 1841 // equal by the method compareTo are, from the standpoint of the 1842 // set, equal 1843 return 1; 1844 } 1845 return compareTo; 1846 } 1847 } 1848 1849 private String _getCacheIdentifier(String[] contentTypes, String[] mixins) 1850 { 1851 Arrays.sort(contentTypes); 1852 String name = StringUtils.join(contentTypes, ";"); 1853 1854 if (mixins.length > 0) 1855 { 1856 Arrays.sort(mixins); 1857 name += ";"; 1858 name += StringUtils.join(mixins, ";"); 1859 } 1860 1861 return name; 1862 } 1863}