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