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.time.ZonedDateTime; 019import java.util.ArrayList; 020import java.util.Collection; 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.Optional; 028import java.util.Set; 029 030import javax.jcr.Node; 031import javax.jcr.NodeIterator; 032import javax.jcr.RepositoryException; 033import javax.jcr.Value; 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.search.model.SystemPropertyExtensionPoint; 041import org.ametys.core.user.UserIdentity; 042import org.ametys.core.util.DateUtils; 043import org.ametys.plugins.explorer.resources.ResourceCollection; 044import org.ametys.plugins.repository.AmetysObject; 045import org.ametys.plugins.repository.AmetysObjectIterable; 046import org.ametys.plugins.repository.AmetysObjectResolver; 047import org.ametys.plugins.repository.AmetysRepositoryException; 048import org.ametys.plugins.repository.CopiableAmetysObject; 049import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 050import org.ametys.plugins.repository.RepositoryConstants; 051import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 052import org.ametys.plugins.repository.UnknownAmetysObjectException; 053import org.ametys.plugins.repository.data.UnknownDataException; 054import org.ametys.plugins.repository.data.holder.ModifiableDataHolder; 055import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableDataHolder; 056import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 057import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 058import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 059import org.ametys.plugins.repository.dublincore.DCMITypes; 060import org.ametys.plugins.repository.jcr.DublinCoreHelper; 061import org.ametys.plugins.repository.jcr.JCRTraversableAmetysObject; 062import org.ametys.plugins.repository.lock.LockableAmetysObject; 063import org.ametys.plugins.repository.metadata.MultilingualString; 064import org.ametys.plugins.repository.tag.TaggableAmetysObjectHelper; 065import org.ametys.runtime.model.View; 066import org.ametys.runtime.model.type.DataContext; 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 DefaultIndexableAmetysObject<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 the first validator metadata */ 100 public static final String METADATA_FIRST_VALIDATOR = "firstValidator"; 101 102 /** Constants for firstValidationDate Metadata* */ 103 public static final String METADATA_FIRST_VALIDATION = "firstValidationDate"; 104 105 /** Constants for last validator Metadata* */ 106 public static final String METADATA_LAST_VALIDATOR = "lastValidator"; 107 108 /** Constants for lastValidationDate Metadata* */ 109 public static final String METADATA_LAST_VALIDATION = "lastValidationDate"; 110 111 /** Constants for last major validator Metadata* */ 112 public static final String METADATA_LAST_MAJOR_VALIDATOR = "lastMajorValidator"; 113 114 /** Constants for lastMajorValidationDate Metadata* */ 115 public static final String METADATA_LAST_MAJORVALIDATION = "lastMajorValidationDate"; 116 117 /** Constants for last contributor Metadata* */ 118 public static final String METADATA_CONTRIBUTOR = "contributor"; 119 120 /** Constants for lastModified Metadata* */ 121 public static final String METADATA_MODIFIED = "lastModified"; 122 123 /** Constants for contentType Metadata* */ 124 public static final String METADATA_CONTENTTYPE = "contentType"; 125 126 /** Constants for contentType Metadata* */ 127 public static final String METADATA_MIXINCONTENTTYPES = "mixins"; 128 129 /** Constant for the attachment node name. */ 130 public static final String ATTACHMENTS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":attachments"; 131 132 /** The default locale for content */ 133 public static final Locale DEFAULT_CONTENT_LOCALE = Locale.ENGLISH; 134 135 /** The type identifiers of this content */ 136 protected String[] _types; 137 /** The mixin type identifiers of this content */ 138 protected String[] _mixins; 139 140 /** Store already retrieved outgoing references, if any */ 141 protected Map<String, OutgoingReferences> _outgoingReferences; 142 143 /** 144 * Creates a JCR-based Content. 145 * @param node the JCR Node backing this Content. 146 * @param parentPath the parent path in the Ametys hierarchy. 147 * @param factory the corresponding {@link ContentFactory}. 148 */ 149 public DefaultContent(Node node, String parentPath, F factory) 150 { 151 super(node, parentPath, factory); 152 } 153 154 @Override 155 public String[] getTypes() 156 { 157 if (_types == null) 158 { 159 _types = getInternalDataHolder().getValueOfTypeOrDefault(METADATA_CONTENTTYPE, ModelItemTypeConstants.STRING_TYPE_ID, new String[0]); 160 } 161 162 return _types; 163 } 164 165 @Override 166 public void setType(String type) 167 { 168 setTypes(new String[] {type}); 169 } 170 171 @Override 172 public void setTypes(String[] types) 173 { 174 getInternalDataHolder().setValue(DefaultContent.METADATA_CONTENTTYPE, types, ModelItemTypeConstants.STRING_TYPE_ID); 175 _types = types; 176 } 177 178 @Override 179 public String[] getMixinTypes() 180 { 181 if (_mixins == null) 182 { 183 _mixins = getInternalDataHolder().getValueOfTypeOrDefault(METADATA_MIXINCONTENTTYPES, ModelItemTypeConstants.STRING_TYPE_ID, new String[0]); 184 } 185 return _mixins; 186 } 187 188 @Override 189 public void setMixinTypes(String[] mixins) 190 { 191 getInternalDataHolder().setValue(DefaultContent.METADATA_MIXINCONTENTTYPES, mixins, ModelItemTypeConstants.STRING_TYPE_ID); 192 _mixins = mixins; 193 } 194 195 @Override 196 public String getLanguage() 197 { 198 return getInternalDataHolder().getValueOfType(METADATA_LANGUAGE, ModelItemTypeConstants.STRING_TYPE_ID); 199 } 200 201 @Override 202 public void setLanguage(String language) 203 { 204 getInternalDataHolder().setValue(DefaultContent.METADATA_LANGUAGE, language, ModelItemTypeConstants.STRING_TYPE_ID); 205 } 206 207 public String getTitle(Locale locale) throws UnknownDataException 208 { 209 Object value = getValue(ATTRIBUTE_TITLE); 210 if (value == null) 211 { 212 throw new UnknownDataException("Unknown attribute " + ATTRIBUTE_TITLE + " for content " + getId()); 213 } 214 else if (value instanceof MultilingualString) 215 { 216 MultilingualString multilingual = (MultilingualString) value; 217 218 if (locale != null && multilingual.hasLocale(locale)) 219 { 220 return multilingual.getValue(locale); 221 } 222 else if (multilingual.hasLocale(DEFAULT_CONTENT_LOCALE)) 223 { 224 return multilingual.getValue(DEFAULT_CONTENT_LOCALE); 225 } 226 else if (multilingual.getValues().isEmpty()) 227 { 228 throw new UnknownDataException("Unknown attribute " + ATTRIBUTE_TITLE + " for content " + getId()); 229 } 230 else 231 { 232 return multilingual.getValues().get(0); 233 } 234 } 235 else 236 { 237 return (String) value; 238 } 239 } 240 241 public String getTitle() throws UnknownDataException 242 { 243 return getTitle(null); 244 } 245 246 @Override 247 public UserIdentity getCreator() throws UnknownDataException 248 { 249 UserIdentity creator = getInternalDataHolder().getValueOfType(METADATA_CREATOR, ModelItemTypeConstants.USER_ELEMENT_TYPE_ID); 250 if (creator == null) 251 { 252 throw new UnknownDataException("Unknown metadata " + METADATA_CREATOR + " for content " + getId()); 253 } 254 255 return creator; 256 } 257 258 @Override 259 public ZonedDateTime getCreationDate() throws UnknownDataException 260 { 261 ZonedDateTime creationDate = getInternalDataHolder().getValueOfType(METADATA_CREATION, ModelItemTypeConstants.DATETIME_TYPE_ID); 262 if (creationDate == null) 263 { 264 throw new UnknownDataException("Unknown metadata " + METADATA_CREATION + " for content " + getId()); 265 } 266 267 return creationDate; 268 } 269 270 @Override 271 public UserIdentity getLastContributor() throws UnknownDataException 272 { 273 UserIdentity lastContributor = getInternalDataHolder().getValueOfType(METADATA_CONTRIBUTOR, ModelItemTypeConstants.USER_ELEMENT_TYPE_ID); 274 if (lastContributor == null) 275 { 276 throw new UnknownDataException("Unknown metadata " + METADATA_CONTRIBUTOR + " for content " + getId()); 277 } 278 279 return lastContributor; 280 } 281 282 @Override 283 public ZonedDateTime getLastModified() throws UnknownDataException 284 { 285 ZonedDateTime lastModified = getInternalDataHolder().getValueOfType(METADATA_MODIFIED, ModelItemTypeConstants.DATETIME_TYPE_ID); 286 if (lastModified == null) 287 { 288 throw new UnknownDataException("Unknown metadata " + METADATA_MODIFIED + " for content " + getId()); 289 } 290 291 return lastModified; 292 } 293 294 public Optional<UserIdentity> getFirstValidator() 295 { 296 return Optional.ofNullable(getInternalDataHolder().getValueOfType(METADATA_FIRST_VALIDATOR, ModelItemTypeConstants.USER_ELEMENT_TYPE_ID)); 297 } 298 299 @Override 300 public ZonedDateTime getFirstValidationDate() 301 { 302 return getInternalDataHolder().getValueOfType(METADATA_FIRST_VALIDATION, ModelItemTypeConstants.DATETIME_TYPE_ID); 303 } 304 305 public Optional<UserIdentity> getLastValidator() 306 { 307 return Optional.ofNullable(getInternalDataHolder().getValueOfType(METADATA_LAST_VALIDATOR, ModelItemTypeConstants.USER_ELEMENT_TYPE_ID)); 308 } 309 310 @Override 311 public ZonedDateTime getLastValidationDate() 312 { 313 return getInternalDataHolder().getValueOfType(METADATA_LAST_VALIDATION, ModelItemTypeConstants.DATETIME_TYPE_ID); 314 } 315 316 public Optional<UserIdentity> getLastMajorValidator() 317 { 318 return Optional.ofNullable(getInternalDataHolder().getValueOfType(METADATA_LAST_MAJOR_VALIDATOR, ModelItemTypeConstants.USER_ELEMENT_TYPE_ID)); 319 } 320 321 @Override 322 public ZonedDateTime getLastMajorValidationDate() 323 { 324 return getInternalDataHolder().getValueOfType(METADATA_LAST_MAJORVALIDATION, ModelItemTypeConstants.DATETIME_TYPE_ID); 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 // small optimization to avoid many JCR operations for outgoing references retrieval if already retrieved for the same Content instance 338 if (_outgoingReferences != null) 339 { 340 return _outgoingReferences; 341 } 342 343 Map<String, OutgoingReferences> outgoingReferencesByPath = new HashMap<>(); 344 345 try 346 { 347 Node contentNode = getNode(); 348 if (contentNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES)) 349 { 350 Node rootOutgoingRefsNode = contentNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES); 351 352 // Loop on outgoing ref node by (metadata) path. 353 NodeIterator outgoingRefsNodeIterator = rootOutgoingRefsNode.getNodes(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES); 354 while (outgoingRefsNodeIterator.hasNext()) 355 { 356 Node outgoingRefsNode = outgoingRefsNodeIterator.nextNode(); 357 String path = outgoingRefsNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES_PATH_PROPERTY).getString(); 358 359 // Loop on each outgoing ref node values for each reference type and collecting outgoing references. 360 OutgoingReferences outgoingReferences = new OutgoingReferences(); 361 NodeIterator outgoingReferenceNodeIterator = outgoingRefsNode.getNodes(); 362 while (outgoingReferenceNodeIterator.hasNext()) 363 { 364 Node outgoingReferenceNode = outgoingReferenceNodeIterator.nextNode(); 365 Value[] referenceValues = outgoingReferenceNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_OUTGOING_REFERENCE_PROPERTY).getValues(); 366 367 List<String> referenceValuesAsList = new ArrayList<>(referenceValues.length); 368 for (Value value : referenceValues) 369 { 370 referenceValuesAsList.add(value.getString()); 371 } 372 373 outgoingReferences.put(outgoingReferenceNode.getName(), referenceValuesAsList); 374 } 375 376 // Updating the outgoing references map 377 if (!outgoingReferences.isEmpty()) 378 { 379 if (outgoingReferencesByPath.containsKey(path)) 380 { 381 outgoingReferencesByPath.get(path).merge(outgoingReferences); 382 } 383 else 384 { 385 outgoingReferencesByPath.put(path, outgoingReferences); 386 } 387 } 388 } 389 } 390 } 391 catch (RepositoryException e) 392 { 393 throw new AmetysRepositoryException(e); 394 } 395 396 _outgoingReferences = outgoingReferencesByPath; 397 return outgoingReferencesByPath; 398 } 399 400 @Override 401 public Collection<Content> getReferencingContents() throws AmetysRepositoryException 402 { 403 return _getReferencingContents(Long.MAX_VALUE).referencingContents(); 404 } 405 406 private ReferencingContentsSearch _getReferencingContents(long limit) throws AmetysRepositoryException 407 { 408 Set<Content> contents = new LinkedHashSet<>(); 409 long total = 0; 410 boolean hasOtherReferences = false; 411 412 try 413 { 414 NodeIterator results = OutgoingReferencesHelper.getContentOutgoingReferences(this); 415 total = results.getSize(); 416 417 AmetysObjectResolver resolver = _getFactory()._getAOResolver(); 418 while (results.hasNext() && contents.size() < limit) 419 { 420 Node node = results.nextNode(); 421 Node contentNode = node.getParent() // go up towards node 'ametys-internal:outgoing-references 422 .getParent() // go up towards node 'ametys-internal:root-outgoing-references 423 .getParent(); // go up towards node of the content 424 Content content = resolver.resolve(contentNode, false); 425 contents.add(content); 426 } 427 428 hasOtherReferences = results.hasNext(); 429 } 430 catch (RepositoryException e) 431 { 432 throw new AmetysRepositoryException("Unable to resolve references for content " + getId(), e); 433 } 434 435 return new ReferencingContentsSearch(total, contents, hasOtherReferences); 436 } 437 438 @Override 439 public ReferencingContentsSearch searchReferencingContents(int limit) throws AmetysRepositoryException 440 { 441 return _getReferencingContents(limit); 442 } 443 444 public boolean hasReferencingContents() throws AmetysRepositoryException 445 { 446 try 447 { 448 return OutgoingReferencesHelper.getContentOutgoingReferences(this).getSize() != 0; 449 } 450 catch (RepositoryException e) 451 { 452 throw new AmetysRepositoryException("A repository exception occured: ", e); 453 } 454 } 455 456 @Override 457 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException 458 { 459 return copyTo(parent, name); 460 } 461 462 @Override 463 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException 464 { 465 return copyTo(parent, name, 0, true, true); 466 } 467 468 /** 469 * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint) 470 * @param parent The parent of the new object. Can not be null. 471 * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object. 472 * @param initWorkflowActionId The initial workflow action id 473 * @param notifyObservers Set to false to do not fire observer events 474 * @param checkpoint true to check the content in if it is versionable 475 * @return the created object 476 * @throws AmetysRepositoryException if an error occurs. 477 */ 478 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, int initWorkflowActionId, boolean notifyObservers, boolean checkpoint) throws AmetysRepositoryException 479 { 480 return _getFactory()._getContentDAO().copy(this, parent, name, null, initWorkflowActionId, notifyObservers, checkpoint, false, false, DataContext.newInstance()); 481 } 482 483 /** 484 * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint) 485 * @param parent The parent of the new object. Can not be null. 486 * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object. 487 * @param lang Language of the new object. Can be null. If null, the new language will be get from the copied object. 488 * @param initWorkflowActionId The initial workflow action id 489 * @param notifyObservers Set to false to do not fire observer events 490 * @param checkpoint true to check the content in if it is versionable 491 * @param waitAsyncObservers if true, waits if necessary for the asynchronous observers to complete 492 * @param copyACL true to copy ACL of source content 493 * @param context The context of the data to copy 494 * @return the created object 495 * @throws AmetysRepositoryException if an error occurs. 496 */ 497 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, String lang, int initWorkflowActionId, boolean notifyObservers, boolean checkpoint, boolean waitAsyncObservers, boolean copyACL, DataContext context) throws AmetysRepositoryException 498 { 499 return _getFactory()._getContentDAO().copy(this, parent, name, lang, initWorkflowActionId, notifyObservers, checkpoint, waitAsyncObservers, copyACL, context); 500 } 501 502 // Dublin Core metadata. // 503 504 @Override 505 public String getDCTitle() throws AmetysRepositoryException 506 { 507 return DublinCoreHelper.getDCTitle(this, getTitle()); 508 } 509 510 @Override 511 public String getDCCreator() throws AmetysRepositoryException 512 { 513 return DublinCoreHelper.getDCCreator(this, UserIdentity.userIdentityToString(getCreator())); 514 } 515 516 @Override 517 public String[] getDCSubject() throws AmetysRepositoryException 518 { 519 return DublinCoreHelper.getDCSubject(this); 520 } 521 522 @Override 523 public String getDCDescription() throws AmetysRepositoryException 524 { 525 String seoDesc = null; 526 try 527 { 528 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 529 RepositoryData seoReposioryData = repositoryData.getRepositoryData("seo"); 530 seoDesc = seoReposioryData.getString("description"); 531 } 532 catch (UnknownDataException me) 533 { 534 seoDesc = null; 535 } 536 537 return DublinCoreHelper.getDCDescription(this, seoDesc); 538 } 539 540 @Override 541 public String getDCPublisher() throws AmetysRepositoryException 542 { 543 return DublinCoreHelper.getDCPublisher(this, getLastValidator().map(UserIdentity::userIdentityToString).orElse(null)); 544 } 545 546 @Override 547 public String getDCContributor() throws AmetysRepositoryException 548 { 549 return DublinCoreHelper.getDCContributor(this, UserIdentity.userIdentityToString(getLastContributor())); 550 } 551 552 @Override 553 public Date getDCDate() throws AmetysRepositoryException 554 { 555 Date defaultDCDate = Optional.ofNullable(getLastValidationDate()) 556 .map(DateUtils::asDate) 557 .orElse(null); 558 return DublinCoreHelper.getDCDate(this, defaultDCDate); 559 560 } 561 562 @Override 563 public String getDCType() throws AmetysRepositoryException 564 { 565 return DublinCoreHelper.getDCType(this, DCMITypes.TEXT); 566 } 567 568 @Override 569 public String getDCFormat() throws AmetysRepositoryException 570 { 571 return DublinCoreHelper.getDCFormat(this, "text/html"); 572 } 573 574 @Override 575 public String getDCIdentifier() throws AmetysRepositoryException 576 { 577 return DublinCoreHelper.getDCIdentifier(this, getId()); 578 } 579 580 @Override 581 public String getDCSource() throws AmetysRepositoryException 582 { 583 return DublinCoreHelper.getDCSource(this); 584 } 585 586 @Override 587 public String getDCLanguage() throws AmetysRepositoryException 588 { 589 return DublinCoreHelper.getDCLanguage(this, getLanguage()); 590 } 591 592 @Override 593 public String getDCRelation() throws AmetysRepositoryException 594 { 595 return DublinCoreHelper.getDCRelation(this); 596 } 597 598 @Override 599 public String getDCCoverage() throws AmetysRepositoryException 600 { 601 return DublinCoreHelper.getDCCoverage(this, getDCLanguage()); 602 } 603 604 @Override 605 public String getDCRights() throws AmetysRepositoryException 606 { 607 return DublinCoreHelper.getDCRights(this); 608 } 609 610 @Override 611 public ResourceCollection getRootAttachments() throws AmetysRepositoryException 612 { 613 ResourceCollection attachments = null; 614 615 if (hasChild(ATTACHMENTS_NODE_NAME)) 616 { 617 attachments = getChild(ATTACHMENTS_NODE_NAME); 618 } 619 620 return attachments; 621 } 622 623 @Override 624 public boolean hasChild(String name) throws AmetysRepositoryException 625 { 626 return _getFactory().hasChild(this, name); 627 } 628 629 @SuppressWarnings("unchecked") 630 @Override 631 public <A extends AmetysObject> A createChild(String name, String type) throws AmetysRepositoryException, RepositoryIntegrityViolationException 632 { 633 return (A) _getFactory().createChild(this, name, type); 634 } 635 636 @SuppressWarnings("unchecked") 637 @Override 638 public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException 639 { 640 return (A) _getFactory().getChild(this, path); 641 } 642 643 @Override 644 public <A extends AmetysObject> AmetysObjectIterable<A> getChildren() throws AmetysRepositoryException 645 { 646 return _getFactory().getChildren(this); 647 } 648 649 public ModifiableDataHolder getInternalDataHolder() 650 { 651 // It is necessary to instantiate the repository data because of locks: 652 // when the content is locked, the lock token is released to be taken by anyone 653 // in repository data there is a lock checked boolean that need to be reset in order to take the token 654 // this boolean is only reset at instantiation 655 ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode(), RepositoryConstants.NAMESPACE_PREFIX_INTERNAL); 656 Optional<LockableAmetysObject> lockableAmetysObject = Optional.of(this) 657 .filter(LockableAmetysObject.class::isInstance) 658 .map(LockableAmetysObject.class:: cast); 659 return new DefaultModifiableDataHolder(_getFactory().getInternalDataTypesExtensionPoint(), repositoryData, lockableAmetysObject); 660 } 661 662 public Optional<SystemPropertyExtensionPoint> getSystemPropertyExtensionPoint() 663 { 664 return Optional.of(_getFactory().getSystemPropertyExtensionPoint()); 665 } 666 667 @Override 668 public void addReaction(UserIdentity user, ReactionType reactionType) 669 { 670 ReactionableObjectHelper.addReaction(getUnversionedDataHolder(), user, reactionType); 671 } 672 673 @Override 674 public void removeReaction(UserIdentity user, ReactionType reactionType) 675 { 676 ReactionableObjectHelper.removeReaction(getUnversionedDataHolder(), user, reactionType); 677 } 678 679 @Override 680 public List<UserIdentity> getReactionUsers(ReactionType reactionType) 681 { 682 return ReactionableObjectHelper.getReactionUsers(getUnversionedDataHolder(), reactionType); 683 } 684 685 public void addReport() 686 { 687 ReportableObjectHelper.addReport(getUnversionedDataHolder()); 688 } 689 690 public void setReportsCount(long reportsCount) 691 { 692 ReportableObjectHelper.setReportsCount(getUnversionedDataHolder(), reportsCount); 693 } 694 695 public void clearReports() 696 { 697 ReportableObjectHelper.clearReports(getUnversionedDataHolder()); 698 } 699 700 public long getReportsCount() 701 { 702 return ReportableObjectHelper.getReportsCount(getUnversionedDataHolder()); 703 } 704 705 public void toSAX(ContentHandler contentHandler, Locale locale, View view, boolean saxWorkflowStep) throws SAXException 706 { 707 _getFactory().getContentSaxer().saxContent(this, contentHandler, locale, view, "content", saxWorkflowStep, false, false, "attributes"); 708 } 709}