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