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