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; 035import javax.jcr.lock.Lock; 036import javax.jcr.lock.LockManager; 037 038import org.xml.sax.ContentHandler; 039import org.xml.sax.SAXException; 040 041import org.ametys.cms.content.references.OutgoingReferences; 042import org.ametys.cms.content.references.OutgoingReferencesHelper; 043import org.ametys.cms.contenttype.ContentType; 044import org.ametys.cms.data.holder.IndexableDataHolder; 045import org.ametys.cms.data.holder.impl.DefaultModelAwareDataHolder; 046import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 047import org.ametys.core.user.UserIdentity; 048import org.ametys.core.util.DateUtils; 049import org.ametys.plugins.explorer.resources.ResourceCollection; 050import org.ametys.plugins.repository.AmetysObject; 051import org.ametys.plugins.repository.AmetysObjectIterable; 052import org.ametys.plugins.repository.AmetysObjectResolver; 053import org.ametys.plugins.repository.AmetysRepositoryException; 054import org.ametys.plugins.repository.CopiableAmetysObject; 055import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 056import org.ametys.plugins.repository.RepositoryConstants; 057import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 058import org.ametys.plugins.repository.UnknownAmetysObjectException; 059import org.ametys.plugins.repository.data.UnknownDataException; 060import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder; 061import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder; 062import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 063import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 064import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 065import org.ametys.plugins.repository.dublincore.DCMITypes; 066import org.ametys.plugins.repository.jcr.DefaultAmetysObject; 067import org.ametys.plugins.repository.jcr.DublinCoreHelper; 068import org.ametys.plugins.repository.jcr.JCRTraversableAmetysObject; 069import org.ametys.plugins.repository.metadata.MultilingualString; 070import org.ametys.plugins.repository.tag.TaggableAmetysObjectHelper; 071import org.ametys.runtime.model.View; 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 private boolean _lockAlreadyChecked; 146 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 Map<String, OutgoingReferences> outgoingReferencesByPath = new HashMap<>(); 398 399 try 400 { 401 Node contentNode = getNode(); 402 if (contentNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES)) 403 { 404 Node rootOutgoingRefsNode = contentNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES); 405 406 // Loop on outgoing ref node by (metadata) path. 407 NodeIterator outgoingRefsNodeIterator = rootOutgoingRefsNode.getNodes(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES); 408 while (outgoingRefsNodeIterator.hasNext()) 409 { 410 Node outgoingRefsNode = outgoingRefsNodeIterator.nextNode(); 411 String path = outgoingRefsNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES_PATH_PROPERTY).getString(); 412 413 // Loop on each outgoing ref node values for each reference type and collecting outgoing references. 414 OutgoingReferences outgoingReferences = new OutgoingReferences(); 415 NodeIterator outgoingReferenceNodeIterator = outgoingRefsNode.getNodes(); 416 while (outgoingReferenceNodeIterator.hasNext()) 417 { 418 Node outgoingReferenceNode = outgoingReferenceNodeIterator.nextNode(); 419 Value[] referenceValues = outgoingReferenceNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_OUTGOING_REFERENCE_PROPERTY).getValues(); 420 421 List<String> referenceValuesAsList = new ArrayList<>(referenceValues.length); 422 for (Value value : referenceValues) 423 { 424 referenceValuesAsList.add(value.getString()); 425 } 426 427 outgoingReferences.put(outgoingReferenceNode.getName(), referenceValuesAsList); 428 } 429 430 // Updating the outgoing references map 431 if (!outgoingReferences.isEmpty()) 432 { 433 if (outgoingReferencesByPath.containsKey(path)) 434 { 435 outgoingReferencesByPath.get(path).merge(outgoingReferences); 436 } 437 else 438 { 439 outgoingReferencesByPath.put(path, outgoingReferences); 440 } 441 } 442 } 443 } 444 } 445 catch (RepositoryException e) 446 { 447 throw new AmetysRepositoryException(e); 448 } 449 450 return outgoingReferencesByPath; 451 } 452 453 @Override 454 public Collection<Content> getReferencingContents() throws AmetysRepositoryException 455 { 456 return _getReferencingContents(Long.MAX_VALUE).referencingContents(); 457 } 458 459 private ReferencingContentsSearch _getReferencingContents(long limit) throws AmetysRepositoryException 460 { 461 Set<Content> contents = new LinkedHashSet<>(); 462 long total = 0; 463 boolean hasOtherReferences = false; 464 465 try 466 { 467 NodeIterator results = OutgoingReferencesHelper.getContentOutgoingReferences(this); 468 total = results.getSize(); 469 470 AmetysObjectResolver resolver = _getFactory()._getAOResolver(); 471 while (results.hasNext() && contents.size() < limit) 472 { 473 Node node = results.nextNode(); 474 Node contentNode = node.getParent() // go up towards node 'ametys-internal:outgoing-references 475 .getParent() // go up towards node 'ametys-internal:root-outgoing-references 476 .getParent(); // go up towards node of the content 477 Content content = resolver.resolve(contentNode, false); 478 contents.add(content); 479 } 480 481 hasOtherReferences = results.hasNext(); 482 } 483 catch (RepositoryException e) 484 { 485 throw new AmetysRepositoryException("Unable to resolve references for content " + getId(), e); 486 } 487 488 return new ReferencingContentsSearch(total, contents, hasOtherReferences); 489 } 490 491 @Override 492 public ReferencingContentsSearch searchReferencingContents(int limit) throws AmetysRepositoryException 493 { 494 return _getReferencingContents(limit); 495 } 496 497 public boolean hasReferencingContents() throws AmetysRepositoryException 498 { 499 try 500 { 501 return OutgoingReferencesHelper.getContentOutgoingReferences(this).getSize() != 0; 502 } 503 catch (RepositoryException e) 504 { 505 throw new AmetysRepositoryException("A repository exception occured: ", e); 506 } 507 } 508 509 @Override 510 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException 511 { 512 return copyTo(parent, name); 513 } 514 515 @Override 516 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException 517 { 518 return copyTo(parent, name, 0, true, true); 519 } 520 521 /** 522 * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint) 523 * @param parent The parent of the new object. Can not be null. 524 * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object. 525 * @param initWorkflowActionId The initial workflow action id 526 * @param notifyObservers Set to false to do not fire observer events 527 * @param checkpoint true to check the content in if it is versionable 528 * @return the created object 529 * @throws AmetysRepositoryException if an error occurs. 530 */ 531 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, int initWorkflowActionId, boolean notifyObservers, boolean checkpoint) throws AmetysRepositoryException 532 { 533 return _getFactory()._getContentDAO().copy(this, parent, name, null, initWorkflowActionId, notifyObservers, checkpoint, false, false); 534 } 535 536 /** 537 * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint) 538 * @param parent The parent of the new object. Can not be null. 539 * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object. 540 * @param lang Language of the new object. Can be null. If null, the new language will be get from the copied object. 541 * @param initWorkflowActionId The initial workflow action id 542 * @param waitAsyncObservers if true, waits if necessary for the asynchronous observers to complete 543 * @param copyACL true to copy ACL of source content 544 * @return the created object 545 * @throws AmetysRepositoryException if an error occurs. 546 */ 547 public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, String lang, int initWorkflowActionId, boolean waitAsyncObservers, boolean copyACL) throws AmetysRepositoryException 548 { 549 return _getFactory()._getContentDAO().copy(this, parent, name, lang, initWorkflowActionId, true, true, waitAsyncObservers, copyACL); 550 } 551 552 void _checkLock() throws RepositoryException 553 { 554 Node node = getNode(); 555 if (!_lockAlreadyChecked && getNode().isLocked()) 556 { 557 LockManager lockManager = node.getSession().getWorkspace().getLockManager(); 558 559 Lock lock = lockManager.getLock(node.getPath()); 560 Node lockHolder = lock.getNode(); 561 562 lockManager.addLockToken(lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString()); 563 _lockAlreadyChecked = true; 564 } 565 } 566 567 // Dublin Core metadata. // 568 569 @Override 570 public String getDCTitle() throws AmetysRepositoryException 571 { 572 return DublinCoreHelper.getDCTitle(this, getTitle()); 573 } 574 575 @Override 576 public String getDCCreator() throws AmetysRepositoryException 577 { 578 return DublinCoreHelper.getDCCreator(this, UserIdentity.userIdentityToString(getCreator())); 579 } 580 581 @Override 582 public String[] getDCSubject() throws AmetysRepositoryException 583 { 584 return DublinCoreHelper.getDCSubject(this); 585 } 586 587 @Override 588 public String getDCDescription() throws AmetysRepositoryException 589 { 590 String seoDesc = null; 591 try 592 { 593 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 594 RepositoryData seoReposioryData = repositoryData.getRepositoryData("seo"); 595 seoDesc = seoReposioryData.getString("description"); 596 } 597 catch (UnknownDataException me) 598 { 599 seoDesc = null; 600 } 601 602 return DublinCoreHelper.getDCDescription(this, seoDesc); 603 } 604 605 @Override 606 public String getDCPublisher() throws AmetysRepositoryException 607 { 608 return DublinCoreHelper.getDCPublisher(this, getLastValidator().map(UserIdentity::userIdentityToString).orElse(null)); 609 } 610 611 @Override 612 public String getDCContributor() throws AmetysRepositoryException 613 { 614 return DublinCoreHelper.getDCContributor(this, UserIdentity.userIdentityToString(getLastContributor())); 615 } 616 617 @Override 618 public Date getDCDate() throws AmetysRepositoryException 619 { 620 Date defaultDCDate = Optional.ofNullable(getLastValidationDate()) 621 .map(DateUtils::asDate) 622 .orElse(null); 623 return DublinCoreHelper.getDCDate(this, defaultDCDate); 624 625 } 626 627 @Override 628 public String getDCType() throws AmetysRepositoryException 629 { 630 return DublinCoreHelper.getDCType(this, DCMITypes.TEXT); 631 } 632 633 @Override 634 public String getDCFormat() throws AmetysRepositoryException 635 { 636 return DublinCoreHelper.getDCFormat(this, "text/html"); 637 } 638 639 @Override 640 public String getDCIdentifier() throws AmetysRepositoryException 641 { 642 return DublinCoreHelper.getDCIdentifier(this, getId()); 643 } 644 645 @Override 646 public String getDCSource() throws AmetysRepositoryException 647 { 648 return DublinCoreHelper.getDCSource(this); 649 } 650 651 @Override 652 public String getDCLanguage() throws AmetysRepositoryException 653 { 654 return DublinCoreHelper.getDCLanguage(this, getLanguage()); 655 } 656 657 @Override 658 public String getDCRelation() throws AmetysRepositoryException 659 { 660 return DublinCoreHelper.getDCRelation(this); 661 } 662 663 @Override 664 public String getDCCoverage() throws AmetysRepositoryException 665 { 666 return DublinCoreHelper.getDCCoverage(this, getDCLanguage()); 667 } 668 669 @Override 670 public String getDCRights() throws AmetysRepositoryException 671 { 672 return DublinCoreHelper.getDCRights(this); 673 } 674 675 @Override 676 public ResourceCollection getRootAttachments() throws AmetysRepositoryException 677 { 678 ResourceCollection attachments = null; 679 680 if (hasChild(ATTACHMENTS_NODE_NAME)) 681 { 682 attachments = getChild(ATTACHMENTS_NODE_NAME); 683 } 684 685 return attachments; 686 } 687 688 @Override 689 public boolean hasChild(String name) throws AmetysRepositoryException 690 { 691 return _getFactory().hasChild(this, name); 692 } 693 694 @SuppressWarnings("unchecked") 695 @Override 696 public <A extends AmetysObject> A createChild(String name, String type) throws AmetysRepositoryException, RepositoryIntegrityViolationException 697 { 698 return (A) _getFactory().createChild(this, name, type); 699 } 700 701 @SuppressWarnings("unchecked") 702 @Override 703 public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException 704 { 705 return (A) _getFactory().getChild(this, path); 706 } 707 708 @Override 709 public <A extends AmetysObject> AmetysObjectIterable<A> getChildren() throws AmetysRepositoryException 710 { 711 return _getFactory().getChildren(this); 712 } 713 714 public IndexableDataHolder getDataHolder() 715 { 716 // It is necessary to instantiate the repository data because of locks: 717 // when the content is locked, the lock token is released to be taken by anyone 718 // in repository data there is a lock checked boolean that need to be reset in order to take the token 719 // this boolean is only reset at instantiation 720 RepositoryData repositoryData = new JCRRepositoryData(getNode()); 721 Collection<ContentType> contentTypes = _getFactory().getContentHelper().getContentTypes(this); 722 return new DefaultModelAwareDataHolder(repositoryData, Optional.empty(), Optional.of(this), contentTypes); 723 } 724 725 public ModifiableModelLessDataHolder getInternalDataHolder() 726 { 727 // It is necessary to instantiate the repository data because of locks: 728 // when the content is locked, the lock token is released to be taken by anyone 729 // in repository data there is a lock checked boolean that need to be reset in order to take the token 730 // this boolean is only reset at instantiation 731 ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode(), RepositoryConstants.NAMESPACE_PREFIX_INTERNAL); 732 return new DefaultModifiableModelLessDataHolder(_getFactory().getInternalDataTypesExtensionPoint(), repositoryData); 733 } 734 735 public Optional<SystemPropertyExtensionPoint> getSystemPropertyExtensionPoint() 736 { 737 return Optional.of(_getFactory().getSystemPropertyExtensionPoint()); 738 } 739 740 @Override 741 public void addReaction(UserIdentity user, ReactionType reactionType) 742 { 743 ReactionableObjectHelper.addReaction(getUnversionedDataHolder(), user, reactionType); 744 } 745 746 @Override 747 public void removeReaction(UserIdentity user, ReactionType reactionType) 748 { 749 ReactionableObjectHelper.removeReaction(getUnversionedDataHolder(), user, reactionType); 750 } 751 752 @Override 753 public List<UserIdentity> getReactionUsers(ReactionType reactionType) 754 { 755 return ReactionableObjectHelper.getReactionUsers(getUnversionedDataHolder(), reactionType); 756 } 757 758 public void addReport() 759 { 760 ReportableObjectHelper.addReport(getUnversionedDataHolder()); 761 } 762 763 public void setReportsCount(long reportsCount) 764 { 765 ReportableObjectHelper.setReportsCount(getUnversionedDataHolder(), reportsCount); 766 } 767 768 public void clearReports() 769 { 770 ReportableObjectHelper.clearReports(getUnversionedDataHolder()); 771 } 772 773 public long getReportsCount() 774 { 775 return ReportableObjectHelper.getReportsCount(getUnversionedDataHolder()); 776 } 777 778 public void toSAX(ContentHandler contentHandler, Locale locale, View view, boolean saxWorkflowStep) throws SAXException 779 { 780 _getFactory().getContentSaxer().saxContent(this, contentHandler, locale, view, "content", saxWorkflowStep, false, false, "attributes"); 781 } 782}