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.Optional; 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.xml.sax.ContentHandler; 037import org.xml.sax.SAXException; 038 039import org.ametys.cms.content.references.OutgoingReferences; 040import org.ametys.cms.content.references.OutgoingReferencesHelper; 041import org.ametys.core.user.UserIdentity; 042import org.ametys.plugins.explorer.resources.ResourceCollection; 043import org.ametys.plugins.repository.AmetysObject; 044import org.ametys.plugins.repository.AmetysObjectIterable; 045import org.ametys.plugins.repository.AmetysObjectResolver; 046import org.ametys.plugins.repository.AmetysRepositoryException; 047import org.ametys.plugins.repository.CopiableAmetysObject; 048import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 049import org.ametys.plugins.repository.RepositoryConstants; 050import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 051import org.ametys.plugins.repository.UnknownAmetysObjectException; 052import org.ametys.plugins.repository.data.UnknownDataException; 053import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 054import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder; 055import org.ametys.plugins.repository.data.holder.impl.DefaultModelAwareDataHolder; 056import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder; 057import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 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.plugins.repository.tag.TaggableAmetysObjectHelper; 066import org.ametys.runtime.model.View; 067import org.ametys.runtime.model.type.ModelItemTypeConstants; 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 private boolean _lockAlreadyChecked; 127 128 /** 129 * Creates a JCR-based Content. 130 * @param node the JCR Node backing this Content. 131 * @param parentPath the parent path in the Ametys hierarchy. 132 * @param factory the corresponding {@link ContentFactory}. 133 */ 134 public DefaultContent(Node node, String parentPath, F factory) 135 { 136 super(node, parentPath, factory); 137 } 138 139 @Override 140 public String[] getTypes() throws AmetysRepositoryException 141 { 142 return getInternalDataHolder().getValueOfType(METADATA_CONTENTTYPE, ModelItemTypeConstants.STRING_TYPE_ID, new String[0]); 143 } 144 145 @Override 146 public void setType(String type) throws AmetysRepositoryException 147 { 148 setTypes(new String[] {type}); 149 } 150 151 @Override 152 public void setTypes(String[] types) throws AmetysRepositoryException 153 { 154 getInternalDataHolder().setValue(DefaultContent.METADATA_CONTENTTYPE, types, ModelItemTypeConstants.STRING_TYPE_ID); 155 } 156 157 @Override 158 public String[] getMixinTypes() throws AmetysRepositoryException 159 { 160 return getInternalDataHolder().getValueOfType(METADATA_MIXINCONTENTTYPES, ModelItemTypeConstants.STRING_TYPE_ID, new String[0]); 161 } 162 163 @Override 164 public void setMixinTypes(String[] mixins) throws AmetysRepositoryException 165 { 166 getInternalDataHolder().setValue(DefaultContent.METADATA_MIXINCONTENTTYPES, mixins, ModelItemTypeConstants.STRING_TYPE_ID); 167 } 168 169 @Override 170 public String getLanguage() throws AmetysRepositoryException 171 { 172 return getInternalDataHolder().getValueOfType(METADATA_LANGUAGE, ModelItemTypeConstants.STRING_TYPE_ID); 173 } 174 175 @Override 176 public void setLanguage(String language) throws AmetysRepositoryException 177 { 178 getInternalDataHolder().setValue(DefaultContent.METADATA_LANGUAGE, language, ModelItemTypeConstants.STRING_TYPE_ID); 179 } 180 181 public String getTitle(Locale locale) throws UnknownDataException, AmetysRepositoryException 182 { 183 Object value = getValue(ATTRIBUTE_TITLE); 184 if (value == null) 185 { 186 throw new UnknownDataException("Unknown attribute " + ATTRIBUTE_TITLE + " for content " + getId()); 187 } 188 else if (value instanceof MultilingualString) 189 { 190 MultilingualString multilingual = (MultilingualString) value; 191 192 if (locale != null && multilingual.hasLocale(locale)) 193 { 194 return multilingual.getValue(locale); 195 } 196 else if (multilingual.hasLocale(DEFAULT_CONTENT_LOCALE)) 197 { 198 return multilingual.getValue(DEFAULT_CONTENT_LOCALE); 199 } 200 else if (multilingual.getValues().isEmpty()) 201 { 202 throw new UnknownDataException("Unknown attribute " + ATTRIBUTE_TITLE + " for content " + getId()); 203 } 204 else 205 { 206 return multilingual.getValues().get(0); 207 } 208 } 209 else 210 { 211 return (String) value; 212 } 213 } 214 215 public String getTitle() throws UnknownDataException, AmetysRepositoryException 216 { 217 return getTitle(null); 218 } 219 220 @Override 221 public UserIdentity getCreator() throws UnknownDataException, AmetysRepositoryException 222 { 223 try 224 { 225 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 226 RepositoryData creatorReposioryData = repositoryData.getRepositoryData(METADATA_CREATOR); 227 return new UserIdentity(creatorReposioryData.getString("login"), creatorReposioryData.getString("population")); 228 } 229 catch (AmetysRepositoryException e) 230 { 231 throw new AmetysRepositoryException("Error while getting creator property for content " + this, e); 232 } 233 } 234 235 @Override 236 public Date getCreationDate() throws UnknownDataException, AmetysRepositoryException 237 { 238 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 239 return repositoryData.getDate(METADATA_CREATION).getTime(); 240 } 241 242 @Override 243 public UserIdentity getLastContributor() throws UnknownDataException, AmetysRepositoryException 244 { 245 try 246 { 247 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 248 RepositoryData contributorReposioryData = repositoryData.getRepositoryData(METADATA_CONTRIBUTOR); 249 return new UserIdentity(contributorReposioryData.getString("login"), contributorReposioryData.getString("population")); 250 } 251 catch (AmetysRepositoryException e) 252 { 253 throw new AmetysRepositoryException("Error while getting creator property for content " + this, e); 254 } 255 } 256 257 @Override 258 public Date getLastModified() throws UnknownDataException, AmetysRepositoryException 259 { 260 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 261 return repositoryData.getDate(METADATA_MODIFIED).getTime(); 262 } 263 264 @Override 265 public Date getFirstValidationDate() throws UnknownDataException, AmetysRepositoryException 266 { 267 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 268 if (repositoryData.hasValue(METADATA_FIRST_VALIDATION)) 269 { 270 return repositoryData.getDate(METADATA_FIRST_VALIDATION).getTime(); 271 } 272 return null; 273 } 274 275 @Override 276 public Date getLastValidationDate() throws UnknownDataException, AmetysRepositoryException 277 { 278 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 279 if (repositoryData.hasValue(METADATA_LAST_VALIDATION)) 280 { 281 return repositoryData.getDate(METADATA_LAST_VALIDATION).getTime(); 282 } 283 return null; 284 } 285 286 @Override 287 public Date getLastMajorValidationDate() throws AmetysRepositoryException 288 { 289 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 290 if (repositoryData.hasValue(METADATA_LAST_MAJORVALIDATION)) 291 { 292 return repositoryData.getDate(METADATA_LAST_MAJORVALIDATION).getTime(); 293 } 294 return null; 295 } 296 297 // Tag management. 298 @Override 299 public Set<String> getTags() throws AmetysRepositoryException 300 { 301 return TaggableAmetysObjectHelper.getTags(this); 302 } 303 304 @Override 305 public Map<String, OutgoingReferences> getOutgoingReferences() throws AmetysRepositoryException 306 { 307 Map<String, OutgoingReferences> outgoingReferencesByPath = new HashMap<>(); 308 309 try 310 { 311 Node contentNode = getNode(); 312 if (contentNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES)) 313 { 314 Node rootOutgoingRefsNode = contentNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES); 315 316 // Loop on outgoing ref node by (metadata) path. 317 NodeIterator outgoingRefsNodeIterator = rootOutgoingRefsNode.getNodes(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES); 318 while (outgoingRefsNodeIterator.hasNext()) 319 { 320 Node outgoingRefsNode = outgoingRefsNodeIterator.nextNode(); 321 String path = outgoingRefsNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES_PATH_PROPERTY).getString(); 322 323 // Loop on each outgoing ref node values for each reference type and collecting outgoing references. 324 OutgoingReferences outgoingReferences = new OutgoingReferences(); 325 NodeIterator outgoingReferenceNodeIterator = outgoingRefsNode.getNodes(); 326 while (outgoingReferenceNodeIterator.hasNext()) 327 { 328 Node outgoingReferenceNode = outgoingReferenceNodeIterator.nextNode(); 329 Value[] referenceValues = outgoingReferenceNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_OUTGOING_REFERENCE_PROPERTY).getValues(); 330 331 List<String> referenceValuesAsList = new ArrayList<>(referenceValues.length); 332 for (Value value : referenceValues) 333 { 334 referenceValuesAsList.add(value.getString()); 335 } 336 337 outgoingReferences.put(outgoingReferenceNode.getName(), referenceValuesAsList); 338 } 339 340 // Updating the outgoing references map 341 if (!outgoingReferences.isEmpty()) 342 { 343 if (outgoingReferencesByPath.containsKey(path)) 344 { 345 outgoingReferencesByPath.get(path).merge(outgoingReferences); 346 } 347 else 348 { 349 outgoingReferencesByPath.put(path, outgoingReferences); 350 } 351 } 352 } 353 } 354 } 355 catch (RepositoryException e) 356 { 357 throw new AmetysRepositoryException(e); 358 } 359 360 return outgoingReferencesByPath; 361 } 362 363 @Override 364 public Collection<Content> getReferencingContents() throws AmetysRepositoryException 365 { 366 Set<Content> contents = new LinkedHashSet<>(); 367 try 368 { 369 NodeIterator results = OutgoingReferencesHelper.getContentOutgoingReferences(this); 370 AmetysObjectResolver resolver = _getFactory()._getAOResolver(); 371 while (results.hasNext()) 372 { 373 Node node = results.nextNode(); 374 Node contentNode = node.getParent() // go up towards node 'ametys-internal:outgoing-references 375 .getParent() // go up towards node 'ametys-internal:root-outgoing-references 376 .getParent(); // go up towards node of the content 377 Content content = resolver.resolve(contentNode, false); 378 contents.add(content); 379 } 380 } 381 catch (RepositoryException e) 382 { 383 throw new AmetysRepositoryException("Unable to resolve references for content " + getId(), e); 384 } 385 return contents; 386 } 387 388 @Override 389 public boolean hasReferencingContents() throws AmetysRepositoryException 390 { 391 try 392 { 393 return OutgoingReferencesHelper.getContentOutgoingReferences(this).getSize() != 0; 394 } 395 catch (RepositoryException e) 396 { 397 throw new AmetysRepositoryException("A repository exception occured: ", e); 398 } 399 } 400 401 @Override 402 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException 403 { 404 return copyTo(parent, name); 405 } 406 407 @Override 408 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException 409 { 410 return copyTo(parent, name, 0); 411 } 412 413 /** 414 * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint) 415 * @param parent The parent of the new object. Can not be null. 416 * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object. 417 * @param initWorkflowActionId The initial workflow action id 418 * @return the created object 419 * @throws AmetysRepositoryException if an error occurs. 420 */ 421 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, int initWorkflowActionId) throws AmetysRepositoryException 422 { 423 return _getFactory()._getContentDAO().copy(this, parent, name, initWorkflowActionId); 424 } 425 426 /** 427 * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint) 428 * @param parent The parent of the new object. Can not be null. 429 * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object. 430 * @param lang Language of the new object. Can be null. If null, the new language will be get from the copied object. 431 * @param initWorkflowActionId The initial workflow action id 432 * @return the created object 433 * @throws AmetysRepositoryException if an error occurs. 434 */ 435 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, String lang, int initWorkflowActionId) throws AmetysRepositoryException 436 { 437 return _getFactory()._getContentDAO().copy(this, parent, name, lang, initWorkflowActionId); 438 } 439 440 /** 441 * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint) 442 * @param parent The parent of the new object. Can not be null. 443 * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object. 444 * @param lang Language of the new object. Can be null. If null, the new language will be get from the copied object. 445 * @param initWorkflowActionId The initial workflow action id 446 * @param waitAsyncObservers if true, waits if necessary for the asynchronous observers to complete 447 * @param copyACL true to copy ACL of source content 448 * @return the created object 449 * @throws AmetysRepositoryException if an error occurs. 450 */ 451 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, String lang, int initWorkflowActionId, boolean waitAsyncObservers, boolean copyACL) throws AmetysRepositoryException 452 { 453 return _getFactory()._getContentDAO().copy(this, parent, name, lang, initWorkflowActionId, true, waitAsyncObservers, copyACL); 454 } 455 456 void _checkLock() throws RepositoryException 457 { 458 Node node = getNode(); 459 if (!_lockAlreadyChecked && getNode().isLocked()) 460 { 461 LockManager lockManager = node.getSession().getWorkspace().getLockManager(); 462 463 Lock lock = lockManager.getLock(node.getPath()); 464 Node lockHolder = lock.getNode(); 465 466 lockManager.addLockToken(lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString()); 467 _lockAlreadyChecked = true; 468 } 469 } 470 471 // Dublin Core metadata. // 472 473 @Override 474 public String getDCTitle() throws AmetysRepositoryException 475 { 476 return DublinCoreHelper.getDCTitle(this, getTitle()); 477 } 478 479 @Override 480 public String getDCCreator() throws AmetysRepositoryException 481 { 482 return DublinCoreHelper.getDCCreator(this, UserIdentity.userIdentityToString(getCreator())); 483 } 484 485 @Override 486 public String[] getDCSubject() throws AmetysRepositoryException 487 { 488 return DublinCoreHelper.getDCSubject(this); 489 } 490 491 @Override 492 public String getDCDescription() throws AmetysRepositoryException 493 { 494 String seoDesc = null; 495 try 496 { 497 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 498 RepositoryData seoReposioryData = repositoryData.getRepositoryData("seo"); 499 seoDesc = seoReposioryData.getString("description"); 500 } 501 catch (UnknownDataException me) 502 { 503 seoDesc = null; 504 } 505 506 return DublinCoreHelper.getDCDescription(this, seoDesc); 507 } 508 509 @Override 510 public String getDCPublisher() throws AmetysRepositoryException 511 { 512 return DublinCoreHelper.getDCPublisher(this); 513 } 514 515 @Override 516 public String getDCContributor() throws AmetysRepositoryException 517 { 518 return DublinCoreHelper.getDCContributor(this, UserIdentity.userIdentityToString(getLastContributor())); 519 } 520 521 @Override 522 public Date getDCDate() throws AmetysRepositoryException 523 { 524 return DublinCoreHelper.getDCDate(this, getLastValidationDate()); 525 526 } 527 528 @Override 529 public String getDCType() throws AmetysRepositoryException 530 { 531 return DublinCoreHelper.getDCType(this, DCMITypes.TEXT); 532 } 533 534 @Override 535 public String getDCFormat() throws AmetysRepositoryException 536 { 537 return DublinCoreHelper.getDCFormat(this, "text/html"); 538 } 539 540 @Override 541 public String getDCIdentifier() throws AmetysRepositoryException 542 { 543 return DublinCoreHelper.getDCIdentifier(this, getId()); 544 } 545 546 @Override 547 public String getDCSource() throws AmetysRepositoryException 548 { 549 return DublinCoreHelper.getDCSource(this); 550 } 551 552 @Override 553 public String getDCLanguage() throws AmetysRepositoryException 554 { 555 return DublinCoreHelper.getDCLanguage(this, getLanguage()); 556 } 557 558 @Override 559 public String getDCRelation() throws AmetysRepositoryException 560 { 561 return DublinCoreHelper.getDCRelation(this); 562 } 563 564 @Override 565 public String getDCCoverage() throws AmetysRepositoryException 566 { 567 return DublinCoreHelper.getDCCoverage(this, getDCLanguage()); 568 } 569 570 @Override 571 public String getDCRights() throws AmetysRepositoryException 572 { 573 return DublinCoreHelper.getDCRights(this); 574 } 575 576 @Override 577 public ResourceCollection getRootAttachments() throws AmetysRepositoryException 578 { 579 ResourceCollection attachments = null; 580 581 if (hasChild(ATTACHMENTS_NODE_NAME)) 582 { 583 attachments = getChild(ATTACHMENTS_NODE_NAME); 584 } 585 586 return attachments; 587 } 588 589 @Override 590 public boolean hasChild(String name) throws AmetysRepositoryException 591 { 592 return _getFactory().hasChild(this, name); 593 } 594 595 @SuppressWarnings("unchecked") 596 @Override 597 public <A extends AmetysObject> A createChild(String name, String type) throws AmetysRepositoryException, RepositoryIntegrityViolationException 598 { 599 return (A) _getFactory().createChild(this, name, type); 600 } 601 602 @SuppressWarnings("unchecked") 603 @Override 604 public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException 605 { 606 return (A) _getFactory().getChild(this, path); 607 } 608 609 @Override 610 public <A extends AmetysObject> AmetysObjectIterable<A> getChildren() throws AmetysRepositoryException 611 { 612 return _getFactory().getChildren(this); 613 } 614 615 public ModelAwareDataHolder getDataHolder() 616 { 617 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 618 return new DefaultModelAwareDataHolder(repositoryData, Optional.empty(), Optional.of(this), _getFactory().getContentHelper().getContentTypes(this)); 619 } 620 621 public ModifiableModelLessDataHolder getInternalDataHolder() 622 { 623 ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode(), RepositoryConstants.NAMESPACE_PREFIX_INTERNAL); 624 return new DefaultModifiableModelLessDataHolder(_getFactory().getInternalDataTypesExtensionPoint(), repositoryData); 625 } 626 627 @Override 628 public void addReaction(UserIdentity user, ReactionType reactionType) 629 { 630 ReactionableObjectHelper.addReaction(getUnversionedDataHolder(), user, reactionType); 631 } 632 633 @Override 634 public void removeReaction(UserIdentity user, ReactionType reactionType) 635 { 636 ReactionableObjectHelper.removeReaction(getUnversionedDataHolder(), user, reactionType); 637 } 638 639 @Override 640 public List<UserIdentity> getReactionUsers(ReactionType reactionType) 641 { 642 return ReactionableObjectHelper.getReactionUsers(getUnversionedDataHolder(), reactionType); 643 } 644 645 public void addReport() 646 { 647 ReportableObjectHelper.addReport(getUnversionedDataHolder()); 648 } 649 650 public void setReportsCount(long reportsCount) 651 { 652 ReportableObjectHelper.setReportsCount(getUnversionedDataHolder(), reportsCount); 653 } 654 655 public void clearReports() 656 { 657 ReportableObjectHelper.clearReports(getUnversionedDataHolder()); 658 } 659 660 public long getReportsCount() 661 { 662 return ReportableObjectHelper.getReportsCount(getUnversionedDataHolder()); 663 } 664 665 public void toSAX(ContentHandler contentHandler, Locale locale, View view, boolean saxWorkflowStep) throws SAXException 666 { 667 _getFactory().getContentSaxer().saxContent(this, contentHandler, locale, view, "content", saxWorkflowStep, false, false, "attributes"); 668 } 669}