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.repository; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Date; 021import java.util.HashMap; 022import java.util.LinkedHashSet; 023import java.util.List; 024import java.util.Locale; 025import java.util.Map; 026import java.util.Set; 027 028import javax.jcr.Node; 029import javax.jcr.NodeIterator; 030import javax.jcr.RepositoryException; 031import javax.jcr.Value; 032import javax.jcr.lock.Lock; 033import javax.jcr.lock.LockManager; 034 035import org.apache.commons.lang3.ArrayUtils; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039import org.ametys.cms.content.references.OutgoingReferences; 040import org.ametys.cms.content.references.OutgoingReferencesHelper; 041import org.ametys.cms.contenttype.ContentType; 042import org.ametys.cms.tag.jcr.TaggableAmetysObjectHelper; 043import org.ametys.core.user.UserIdentity; 044import org.ametys.plugins.explorer.resources.ResourceCollection; 045import org.ametys.plugins.repository.AmetysObject; 046import org.ametys.plugins.repository.AmetysObjectIterable; 047import org.ametys.plugins.repository.AmetysObjectResolver; 048import org.ametys.plugins.repository.AmetysRepositoryException; 049import org.ametys.plugins.repository.CopiableAmetysObject; 050import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 051import org.ametys.plugins.repository.RepositoryConstants; 052import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 053import org.ametys.plugins.repository.UnknownAmetysObjectException; 054import org.ametys.plugins.repository.data.UnknownDataException; 055import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 056import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper; 057import org.ametys.plugins.repository.data.holder.impl.DefaultModelAwareDataHolder; 058import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 059import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 060import org.ametys.plugins.repository.dublincore.DCMITypes; 061import org.ametys.plugins.repository.jcr.DefaultAmetysObject; 062import org.ametys.plugins.repository.jcr.DublinCoreHelper; 063import org.ametys.plugins.repository.jcr.JCRTraversableAmetysObject; 064import org.ametys.plugins.repository.metadata.MultilingualString; 065import org.ametys.runtime.model.Model; 066import org.ametys.runtime.model.ModelItem; 067 068/** 069 * Default implementation of a {@link Content}, also versionable, lockable and workflow-aware. 070 * @param <F> the actual type of factory. 071 */ 072public class DefaultContent<F extends ContentFactory> extends DefaultAmetysObject<F> implements Content, CopiableAmetysObject, JCRTraversableAmetysObject, ReactionableObject 073{ 074 /** Constants for the root outgoing references node */ 075 public static final String METADATA_ROOT_OUTGOING_REFERENCES = "root-outgoing-references"; 076 077 /** Constants for the outgoing references node */ 078 public static final String METADATA_OUTGOING_REFERENCES = "outgoing-references"; 079 080 /** Constants for the outgoing references path property */ 081 public static final String METADATA_OUTGOING_REFERENCES_PATH_PROPERTY = "path"; 082 083 /** Constants for the outgoing reference property */ 084 public static final String METADATA_OUTGOING_REFERENCE_PROPERTY = "reference"; 085 086 /** Constants for the outgoing reference nodetype */ 087 public static final String METADATA_OUTGOING_REFERENCE_NODETYPE = "reference"; 088 089 /** Constants for language Metadata* */ 090 public static final String METADATA_LANGUAGE = "language"; 091 092 /** Constants for author Metadata* */ 093 public static final String METADATA_CREATOR = "creator"; 094 095 /** Constants for lastModified Metadata* */ 096 public static final String METADATA_CREATION = "creationDate"; 097 098 /** Constants for firstValidationDate Metadata* */ 099 public static final String METADATA_FIRST_VALIDATION = "firstValidationDate"; 100 101 /** Constants for lastValidationDate Metadata* */ 102 public static final String METADATA_LAST_VALIDATION = "lastValidationDate"; 103 104 /** Constants for lastMajorValidationDate Metadata* */ 105 public static final String METADATA_LAST_MAJORVALIDATION = "lastMajorValidationDate"; 106 107 /** Constants for last contributor Metadata* */ 108 public static final String METADATA_CONTRIBUTOR = "contributor"; 109 110 /** Constants for lastModified Metadata* */ 111 public static final String METADATA_MODIFIED = "lastModified"; 112 113 /** Constants for contentType Metadata* */ 114 public static final String METADATA_CONTENTTYPE = "contentType"; 115 116 /** Constants for contentType Metadata* */ 117 public static final String METADATA_MIXINCONTENTTYPES = "mixins"; 118 119 /** Constant for the attachment node name. */ 120 public static final String ATTACHMENTS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":attachments"; 121 122 /** The default locale for content */ 123 public static final Locale DEFAULT_CONTENT_LOCALE = Locale.ENGLISH; 124 125 // Logger for traces 126 private static Logger __logger = LoggerFactory.getLogger(DefaultContent.class); 127 128 private boolean _lockAlreadyChecked; 129 private List<Model> _models; 130 131 /** 132 * Creates a JCR-based Content. 133 * @param node the JCR Node backing this Content. 134 * @param parentPath the parent path in the Ametys hierarchy. 135 * @param factory the corresponding {@link ContentFactory}. 136 */ 137 public DefaultContent(Node node, String parentPath, F factory) 138 { 139 super(node, parentPath, factory); 140 } 141 142 @Override 143 public String[] getTypes() throws AmetysRepositoryException 144 { 145 try 146 { 147 if (getNode().hasProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_CONTENTTYPE)) 148 { 149 Value[] values = getNode().getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_CONTENTTYPE).getValues(); 150 151 String[] types = new String[values.length]; 152 for (int i = 0; i < values.length; i++) 153 { 154 Value value = values[i]; 155 types[i] = value.getString(); 156 } 157 return types; 158 } 159 else 160 { 161 return new String[0]; 162 } 163 } 164 catch (javax.jcr.RepositoryException ex) 165 { 166 throw new AmetysRepositoryException("Unable to get contentType property", ex); 167 } 168 } 169 170 @Override 171 public String[] getMixinTypes() throws AmetysRepositoryException 172 { 173 try 174 { 175 if (getNode().hasProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_MIXINCONTENTTYPES)) 176 { 177 Value[] values = getNode().getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_MIXINCONTENTTYPES).getValues(); 178 179 String[] mixins = new String[values.length]; 180 for (int i = 0; i < values.length; i++) 181 { 182 Value value = values[i]; 183 mixins[i] = value.getString(); 184 } 185 return mixins; 186 } 187 else 188 { 189 return new String[0]; 190 } 191 } 192 catch (javax.jcr.RepositoryException ex) 193 { 194 throw new AmetysRepositoryException("Unable to get contentType property", ex); 195 } 196 } 197 198 @Override 199 public String getLanguage() throws AmetysRepositoryException 200 { 201 try 202 { 203 if (getNode().hasProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_LANGUAGE)) 204 { 205 return getNode().getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_LANGUAGE).getString(); 206 } 207 else 208 { 209 // A multilingual content has no language property 210 return null; 211 } 212 } 213 catch (javax.jcr.RepositoryException ex) 214 { 215 throw new AmetysRepositoryException("Unable to get language property", ex); 216 } 217 } 218 219 public String getTitle(Locale locale) throws UnknownDataException, AmetysRepositoryException 220 { 221 Object value = getValue(ATTRIBUTE_TITLE); 222 if (value == null) 223 { 224 throw new UnknownDataException("Unknown attribute " + ATTRIBUTE_TITLE + " for content " + getId()); 225 } 226 else if (value instanceof MultilingualString) 227 { 228 MultilingualString multilingual = (MultilingualString) value; 229 230 if (locale != null && multilingual.hasLocale(locale)) 231 { 232 return multilingual.getValue(locale); 233 } 234 else if (multilingual.hasLocale(DEFAULT_CONTENT_LOCALE)) 235 { 236 return multilingual.getValue(DEFAULT_CONTENT_LOCALE); 237 } 238 else if (multilingual.getValues().isEmpty()) 239 { 240 throw new UnknownDataException("Unknown attribute " + ATTRIBUTE_TITLE + " for content " + getId()); 241 } 242 else 243 { 244 return multilingual.getValues().get(0); 245 } 246 } 247 else 248 { 249 return (String) value; 250 } 251 } 252 253 public String getTitle() throws UnknownDataException, AmetysRepositoryException 254 { 255 return getTitle(null); 256 } 257 258 @Override 259 public UserIdentity getCreator() throws UnknownDataException, AmetysRepositoryException 260 { 261 try 262 { 263 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 264 RepositoryData creatorReposioryData = repositoryData.getRepositoryData(METADATA_CREATOR); 265 return new UserIdentity(creatorReposioryData.getString("login"), creatorReposioryData.getString("population")); 266 } 267 catch (AmetysRepositoryException e) 268 { 269 throw new AmetysRepositoryException("Error while getting creator property for content " + this, e); 270 } 271 } 272 273 @Override 274 public Date getCreationDate() throws UnknownDataException, AmetysRepositoryException 275 { 276 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 277 return repositoryData.getDate(METADATA_CREATION).getTime(); 278 } 279 280 @Override 281 public UserIdentity getLastContributor() throws UnknownDataException, AmetysRepositoryException 282 { 283 try 284 { 285 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 286 RepositoryData contributorReposioryData = repositoryData.getRepositoryData(METADATA_CONTRIBUTOR); 287 return new UserIdentity(contributorReposioryData.getString("login"), contributorReposioryData.getString("population")); 288 } 289 catch (AmetysRepositoryException e) 290 { 291 throw new AmetysRepositoryException("Error while getting creator property for content " + this, e); 292 } 293 } 294 295 @Override 296 public Date getLastModified() throws UnknownDataException, AmetysRepositoryException 297 { 298 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 299 return repositoryData.getDate(METADATA_MODIFIED).getTime(); 300 } 301 302 @Override 303 public Date getFirstValidationDate() throws UnknownDataException, AmetysRepositoryException 304 { 305 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 306 if (repositoryData.hasValue(METADATA_FIRST_VALIDATION)) 307 { 308 return repositoryData.getDate(METADATA_FIRST_VALIDATION).getTime(); 309 } 310 return null; 311 } 312 313 @Override 314 public Date getLastValidationDate() throws UnknownDataException, AmetysRepositoryException 315 { 316 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 317 if (repositoryData.hasValue(METADATA_LAST_VALIDATION)) 318 { 319 return repositoryData.getDate(METADATA_LAST_VALIDATION).getTime(); 320 } 321 return null; 322 } 323 324 @Override 325 public Date getLastMajorValidationDate() throws AmetysRepositoryException 326 { 327 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 328 if (repositoryData.hasValue(METADATA_LAST_MAJORVALIDATION)) 329 { 330 return repositoryData.getDate(METADATA_LAST_MAJORVALIDATION).getTime(); 331 } 332 return null; 333 } 334 335 // Tag management. 336 @Override 337 public Set<String> getTags() throws AmetysRepositoryException 338 { 339 return TaggableAmetysObjectHelper.getTags(this); 340 } 341 342 @Override 343 public Map<String, OutgoingReferences> getOutgoingReferences() throws AmetysRepositoryException 344 { 345 Map<String, OutgoingReferences> outgoingReferencesByPath = new HashMap<>(); 346 347 try 348 { 349 Node contentNode = getNode(); 350 if (contentNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES)) 351 { 352 Node rootOutgoingRefsNode = contentNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES); 353 354 // Loop on outgoing ref node by (metadata) path. 355 NodeIterator outgoingRefsNodeIterator = rootOutgoingRefsNode.getNodes(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES); 356 while (outgoingRefsNodeIterator.hasNext()) 357 { 358 Node outgoingRefsNode = outgoingRefsNodeIterator.nextNode(); 359 String path = outgoingRefsNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES_PATH_PROPERTY).getString(); 360 361 // Loop on each outgoing ref node values for each reference type and collecting outgoing references. 362 OutgoingReferences outgoingReferences = new OutgoingReferences(); 363 NodeIterator outgoingReferenceNodeIterator = outgoingRefsNode.getNodes(); 364 while (outgoingReferenceNodeIterator.hasNext()) 365 { 366 Node outgoingReferenceNode = outgoingReferenceNodeIterator.nextNode(); 367 Value[] referenceValues = outgoingReferenceNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_OUTGOING_REFERENCE_PROPERTY).getValues(); 368 369 List<String> referenceValuesAsList = new ArrayList<>(referenceValues.length); 370 for (Value value : referenceValues) 371 { 372 referenceValuesAsList.add(value.getString()); 373 } 374 375 outgoingReferences.put(outgoingReferenceNode.getName(), referenceValuesAsList); 376 } 377 378 // Updating the outgoing references map 379 if (!outgoingReferences.isEmpty()) 380 { 381 if (outgoingReferencesByPath.containsKey(path)) 382 { 383 outgoingReferencesByPath.get(path).merge(outgoingReferences); 384 } 385 else 386 { 387 outgoingReferencesByPath.put(path, outgoingReferences); 388 } 389 } 390 } 391 } 392 } 393 catch (RepositoryException e) 394 { 395 throw new AmetysRepositoryException(e); 396 } 397 398 return outgoingReferencesByPath; 399 } 400 401 @Override 402 public Collection<Content> getReferencingContents() throws AmetysRepositoryException 403 { 404 Set<Content> contents = new LinkedHashSet<>(); 405 try 406 { 407 NodeIterator results = OutgoingReferencesHelper.getContentOutgoingReferences(this); 408 AmetysObjectResolver resolver = _getFactory()._getAOResolver(); 409 while (results.hasNext()) 410 { 411 Node node = results.nextNode(); 412 Node contentNode = node.getParent() // go up towards node 'ametys-internal:outgoing-references 413 .getParent() // go up towards node 'ametys-internal:root-outgoing-references 414 .getParent(); // go up towards node of the content 415 Content content = resolver.resolve(contentNode, false); 416 contents.add(content); 417 } 418 } 419 catch (RepositoryException e) 420 { 421 throw new AmetysRepositoryException("Unable to resolve references for content " + getId(), e); 422 } 423 return contents; 424 } 425 426 @Override 427 public boolean hasReferencingContents() throws AmetysRepositoryException 428 { 429 try 430 { 431 return OutgoingReferencesHelper.getContentOutgoingReferences(this).getSize() != 0; 432 } 433 catch (RepositoryException e) 434 { 435 throw new AmetysRepositoryException("A repository exception occured: ", e); 436 } 437 } 438 439 @Override 440 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException 441 { 442 return copyTo(parent, name); 443 } 444 445 @Override 446 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException 447 { 448 return copyTo(parent, name, 0); 449 } 450 451 /** 452 * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint) 453 * @param parent The parent of the new object. Can not be null. 454 * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object. 455 * @param initWorkflowActionId The initial workflow action id 456 * @return the created object 457 * @throws AmetysRepositoryException if an error occurs. 458 */ 459 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, int initWorkflowActionId) throws AmetysRepositoryException 460 { 461 return _getFactory()._getContentDAO().copy(this, parent, name, initWorkflowActionId); 462 } 463 464 /** 465 * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint) 466 * @param parent The parent of the new object. Can not be null. 467 * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object. 468 * @param lang Language of the new object. Can be null. If null, the new language will be get from the copied object. 469 * @param initWorkflowActionId The initial workflow action id 470 * @return the created object 471 * @throws AmetysRepositoryException if an error occurs. 472 */ 473 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, String lang, int initWorkflowActionId) throws AmetysRepositoryException 474 { 475 return _getFactory()._getContentDAO().copy(this, parent, name, lang, initWorkflowActionId); 476 } 477 478 /** 479 * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint) 480 * @param parent The parent of the new object. Can not be null. 481 * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object. 482 * @param lang Language of the new object. Can be null. If null, the new language will be get from the copied object. 483 * @param initWorkflowActionId The initial workflow action id 484 * @param waitAsyncObservers if true, waits if necessary for the asynchronous observers to complete 485 * @param copyACL true to copy ACL of source content 486 * @return the created object 487 * @throws AmetysRepositoryException if an error occurs. 488 */ 489 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, String lang, int initWorkflowActionId, boolean waitAsyncObservers, boolean copyACL) throws AmetysRepositoryException 490 { 491 return _getFactory()._getContentDAO().copy(this, parent, name, lang, initWorkflowActionId, true, waitAsyncObservers, copyACL); 492 } 493 494 void _checkLock() throws RepositoryException 495 { 496 Node node = getNode(); 497 if (!_lockAlreadyChecked && getNode().isLocked()) 498 { 499 LockManager lockManager = node.getSession().getWorkspace().getLockManager(); 500 501 Lock lock = lockManager.getLock(node.getPath()); 502 Node lockHolder = lock.getNode(); 503 504 lockManager.addLockToken(lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString()); 505 _lockAlreadyChecked = true; 506 } 507 } 508 509 // Dublin Core metadata. // 510 511 @Override 512 public String getDCTitle() throws AmetysRepositoryException 513 { 514 return DublinCoreHelper.getDCTitle(this, getTitle()); 515 } 516 517 @Override 518 public String getDCCreator() throws AmetysRepositoryException 519 { 520 return DublinCoreHelper.getDCCreator(this, UserIdentity.userIdentityToString(getCreator())); 521 } 522 523 @Override 524 public String[] getDCSubject() throws AmetysRepositoryException 525 { 526 return DublinCoreHelper.getDCSubject(this); 527 } 528 529 @Override 530 public String getDCDescription() throws AmetysRepositoryException 531 { 532 String seoDesc = null; 533 try 534 { 535 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 536 RepositoryData seoReposioryData = repositoryData.getRepositoryData("seo"); 537 seoDesc = seoReposioryData.getString("description"); 538 } 539 catch (UnknownDataException me) 540 { 541 seoDesc = null; 542 } 543 544 return DublinCoreHelper.getDCDescription(this, seoDesc); 545 } 546 547 @Override 548 public String getDCPublisher() throws AmetysRepositoryException 549 { 550 return DublinCoreHelper.getDCPublisher(this); 551 } 552 553 @Override 554 public String getDCContributor() throws AmetysRepositoryException 555 { 556 return DublinCoreHelper.getDCContributor(this, UserIdentity.userIdentityToString(getLastContributor())); 557 } 558 559 @Override 560 public Date getDCDate() throws AmetysRepositoryException 561 { 562 return DublinCoreHelper.getDCDate(this, getLastValidationDate()); 563 564 } 565 566 @Override 567 public String getDCType() throws AmetysRepositoryException 568 { 569 return DublinCoreHelper.getDCType(this, DCMITypes.TEXT); 570 } 571 572 @Override 573 public String getDCFormat() throws AmetysRepositoryException 574 { 575 return DublinCoreHelper.getDCFormat(this, "text/html"); 576 } 577 578 @Override 579 public String getDCIdentifier() throws AmetysRepositoryException 580 { 581 return DublinCoreHelper.getDCIdentifier(this, getId()); 582 } 583 584 @Override 585 public String getDCSource() throws AmetysRepositoryException 586 { 587 return DublinCoreHelper.getDCSource(this); 588 } 589 590 @Override 591 public String getDCLanguage() throws AmetysRepositoryException 592 { 593 return DublinCoreHelper.getDCLanguage(this, getLanguage()); 594 } 595 596 @Override 597 public String getDCRelation() throws AmetysRepositoryException 598 { 599 return DublinCoreHelper.getDCRelation(this); 600 } 601 602 @Override 603 public String getDCCoverage() throws AmetysRepositoryException 604 { 605 return DublinCoreHelper.getDCCoverage(this, getDCLanguage()); 606 } 607 608 @Override 609 public String getDCRights() throws AmetysRepositoryException 610 { 611 return DublinCoreHelper.getDCRights(this); 612 } 613 614 @Override 615 public ResourceCollection getRootAttachments() throws AmetysRepositoryException 616 { 617 ResourceCollection attachments = null; 618 619 if (hasChild(ATTACHMENTS_NODE_NAME)) 620 { 621 attachments = getChild(ATTACHMENTS_NODE_NAME); 622 } 623 624 return attachments; 625 } 626 627 @Override 628 public boolean hasChild(String name) throws AmetysRepositoryException 629 { 630 return _getFactory().hasChild(this, name); 631 } 632 633 @SuppressWarnings("unchecked") 634 @Override 635 public <A extends AmetysObject> A createChild(String name, String type) throws AmetysRepositoryException, RepositoryIntegrityViolationException 636 { 637 return (A) _getFactory().createChild(this, name, type); 638 } 639 640 @SuppressWarnings("unchecked") 641 @Override 642 public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException 643 { 644 return (A) _getFactory().getChild(this, path); 645 } 646 647 @Override 648 public <A extends AmetysObject> AmetysObjectIterable<A> getChildren() throws AmetysRepositoryException 649 { 650 return _getFactory().getChildren(this); 651 } 652 653 public ModelItem getDefinition(String dataPath) 654 { 655 return DataHolderHelper.getModelItem(dataPath, getModels()); 656 } 657 658 public ModelAwareDataHolder getDataHolder() 659 { 660 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 661 return new DefaultModelAwareDataHolder(repositoryData, getModels()); 662 } 663 664 /** 665 * Retrieves the model of the content as an array 666 * @return the model of the content 667 */ 668 protected List<Model> getModels() 669 { 670 if (_models == null) 671 { 672 _models = new ArrayList<>(); 673 674 String[] allContentTypes = ArrayUtils.addAll(getTypes(), getMixinTypes()); 675 for (String contentTypeId : allContentTypes) 676 { 677 ContentType contentType = _getFactory()._getContentTypeExtensionPoint().getExtension(contentTypeId); 678 if (contentType != null) 679 { 680 _models.add(contentType); 681 } 682 else 683 { 684 __logger.warn("Unknown content type identifier: {}", contentTypeId); 685 } 686 } 687 } 688 return _models; 689 } 690 691 @Override 692 public void addReaction(UserIdentity user, ReactionType reactionType) 693 { 694 ReactionableObjectHelper.addReaction(getUnversionedMetadataHolder(), user, reactionType); 695 } 696 697 @Override 698 public void removeReaction(UserIdentity user, ReactionType reactionType) 699 { 700 ReactionableObjectHelper.removeReaction(getUnversionedMetadataHolder(), user, reactionType); 701 } 702 703 @Override 704 public List<UserIdentity> getReactionUsers(ReactionType reactionType) 705 { 706 return ReactionableObjectHelper.getReactionUsers(getUnversionedMetadataHolder(), reactionType); 707 } 708}