001/* 002 * Copyright 2012 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.Collection; 020import java.util.Date; 021import java.util.List; 022import java.util.Locale; 023import java.util.Map; 024 025import javax.jcr.Node; 026import javax.jcr.Property; 027import javax.jcr.RepositoryException; 028 029import org.ametys.cms.content.references.OutgoingReferences; 030import org.ametys.cms.contenttype.ContentType; 031import org.ametys.cms.repository.comment.Comment; 032import org.ametys.cms.repository.comment.CommentableContent; 033import org.ametys.cms.repository.comment.contributor.ContributorCommentableContent; 034import org.ametys.cms.trash.ContentTrashElementType; 035import org.ametys.core.user.UserIdentity; 036import org.ametys.plugins.repository.AmetysRepositoryException; 037import org.ametys.plugins.repository.data.extractor.xml.XMLValuesExtractorAdditionalDataGetter; 038import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder; 039import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 040import org.ametys.plugins.repository.jcr.DublinCoreHelper; 041import org.ametys.plugins.repository.jcr.JCRTraversableAmetysObject; 042import org.ametys.plugins.repository.jcr.NameHelper; 043import org.ametys.plugins.repository.jcr.NameHelper.NameComputationMode; 044import org.ametys.plugins.repository.jcr.NodeHelper; 045import org.ametys.plugins.repository.lock.LockableAmetysObject; 046import org.ametys.plugins.repository.tag.TaggableAmetysObjectHelper; 047import org.ametys.plugins.repository.trash.TrashElement; 048import org.ametys.plugins.repository.trash.TrashableAmetysObject; 049import org.ametys.plugins.repository.trash.UnknownParentException; 050 051/** 052 * Default implementation of a {@link Content}, also versionable, lockable and workflow-aware. 053 * @param <F> the actual type of factory. 054 */ 055public class ModifiableDefaultContent<F extends ModifiableContentFactory> extends DefaultContent<F> implements CommentableContent, ContributorCommentableContent, LockableAmetysObject, ModifiableWorkflowAwareContent, TrashableAmetysObject/*, AnnotableContent*/ //, CopiableAmetysObject 056{ 057 private boolean _lockAlreadyChecked; 058 059 /** 060 * Creates a JCR-based Content. 061 * @param node the JCR Node backing this Content. 062 * @param parentPath the parent path in the Ametys hierarchy. 063 * @param factory the corresponding {@link ContentFactory}. 064 */ 065 public ModifiableDefaultContent(Node node, String parentPath, F factory) 066 { 067 super(node, parentPath, factory); 068 } 069 070 public void setTitle(String title, Locale locale) throws AmetysRepositoryException 071 { 072 _getFactory()._getModifiableContentHelper().setTitle(this, title, locale); 073 } 074 075 public void setTitle(String title) throws AmetysRepositoryException 076 { 077 _getFactory()._getModifiableContentHelper().setTitle(this, title); 078 } 079 080 public void setCreator(UserIdentity user) throws AmetysRepositoryException 081 { 082 _getFactory()._getModifiableContentHelper().setCreator(this, user); 083 } 084 085 public void setCreationDate(ZonedDateTime creationDate) throws AmetysRepositoryException 086 { 087 _getFactory()._getModifiableContentHelper().setCreationDate(this, creationDate); 088 } 089 090 public void setLastContributor(UserIdentity user) 091 { 092 _getFactory()._getModifiableContentHelper().setLastContributor(this, user); 093 } 094 095 public void setLastModified(ZonedDateTime lastModified) throws AmetysRepositoryException 096 { 097 _getFactory()._getModifiableContentHelper().setLastModified(this, lastModified); 098 } 099 100 public void setFirstValidator(UserIdentity user) throws AmetysRepositoryException 101 { 102 _getFactory()._getModifiableContentHelper().setFirstValidator(this, user); 103 } 104 105 @Override 106 public void setFirstValidationDate(ZonedDateTime validationDate) throws AmetysRepositoryException 107 { 108 _getFactory()._getModifiableContentHelper().setFirstValidationDate(this, validationDate); 109 } 110 111 public void setLastValidator(UserIdentity user) throws AmetysRepositoryException 112 { 113 _getFactory()._getModifiableContentHelper().setLastValidator(this, user); 114 } 115 116 @Override 117 public void setLastValidationDate(ZonedDateTime validationDate) throws AmetysRepositoryException 118 { 119 _getFactory()._getModifiableContentHelper().setLastValidationDate(this, validationDate); 120 } 121 122 public void setLastMajorValidator(UserIdentity user) throws AmetysRepositoryException 123 { 124 _getFactory()._getModifiableContentHelper().setLastMajorValidator(this, user); 125 } 126 127 @Override 128 public void setLastMajorValidationDate(ZonedDateTime validationDate) throws AmetysRepositoryException 129 { 130 _getFactory()._getModifiableContentHelper().setLastMajorValidationDate(this, validationDate); 131 } 132 133 @Override 134 public void setOutgoingReferences(Map<String, OutgoingReferences> references) throws AmetysRepositoryException 135 { 136 _getFactory()._getModifiableContentHelper().setOutgoingReferences(this, references); 137 _outgoingReferences = references; 138 } 139 140 // ------------------- Tags management -------------------- // 141 @Override 142 public void tag(String tag) throws AmetysRepositoryException 143 { 144 TaggableAmetysObjectHelper.tag(this, tag); 145 } 146 147 @Override 148 public void untag(String tag) throws AmetysRepositoryException 149 { 150 TaggableAmetysObjectHelper.untag(this, tag); 151 } 152 153 // ------------------- Workflow management -------------------- // 154 @Override 155 public long getWorkflowId() throws AmetysRepositoryException 156 { 157 return WorkflowAwareContentHelper.getWorkflowId(this); 158 } 159 160 @Override 161 public void setWorkflowId(long workflowId) throws AmetysRepositoryException 162 { 163 WorkflowAwareContentHelper.setWorkflowId(this, workflowId); 164 } 165 166 @Override 167 public long getCurrentStepId() throws AmetysRepositoryException 168 { 169 return WorkflowAwareContentHelper.getCurrentStepId(this); 170 } 171 172 @Override 173 public void setCurrentStepId (long stepId) throws AmetysRepositoryException 174 { 175 WorkflowAwareContentHelper.setCurrentStepId(this, stepId); 176 } 177 178 @Override 179 public ZonedDateTime getProposalDate() throws AmetysRepositoryException 180 { 181 return WorkflowAwareContentHelper.getProposalDate(this); 182 } 183 184 @Override 185 public void setProposalDate(ZonedDateTime proposalDate) throws AmetysRepositoryException 186 { 187 WorkflowAwareContentHelper.setProposalDate(this, proposalDate); 188 } 189 190 // --------------------- Lock management ----------------------- // 191 @Override 192 public void lock() throws AmetysRepositoryException 193 { 194 _getFactory().getLockComponent().lock(this); 195 196 // the lock component immediately detached the lock token, so we have to check it again at next usage 197 _lockAlreadyChecked = false; 198 } 199 200 @Override 201 public void unlock() throws AmetysRepositoryException 202 { 203 _getFactory().getLockComponent().unlock(this); 204 205 // the lock component removed the lock token on unlock 206 _lockAlreadyChecked = true; 207 } 208 209 @Override 210 public void setLockInfoOnCurrentContext() throws AmetysRepositoryException 211 { 212 if (!_lockAlreadyChecked) 213 { 214 _getFactory().getLockComponent().setLockTokenOnCurrentSession(this); 215 _lockAlreadyChecked = true; 216 } 217 } 218 219 @Override 220 public boolean isLocked() throws AmetysRepositoryException 221 { 222 return _getFactory().getLockComponent().isLocked(this); 223 } 224 225 @Override 226 public UserIdentity getLockOwner() throws AmetysRepositoryException 227 { 228 return _getFactory().getLockComponent().getLockOwner(this); 229 } 230 231 // -------------------------- DUBLIN CORE ---------------------------- // 232 @Override 233 public void setDCTitle(String title) throws AmetysRepositoryException 234 { 235 DublinCoreHelper.setDCTitle(this, title); 236 } 237 238 @Override 239 public void setDCCreator(String creator) throws AmetysRepositoryException 240 { 241 DublinCoreHelper.setDCCreator(this, creator); 242 } 243 244 @Override 245 public void setDCSubject(String[] subject) throws AmetysRepositoryException 246 { 247 DublinCoreHelper.setDCSubject(this, subject); 248 } 249 250 @Override 251 public void setDCDescription(String description) throws AmetysRepositoryException 252 { 253 DublinCoreHelper.setDCDescription(this, description); 254 } 255 256 @Override 257 public void setDCPublisher(String publisher) throws AmetysRepositoryException 258 { 259 DublinCoreHelper.setDCPublisher(this, publisher); 260 } 261 262 @Override 263 public void setDCContributor(String contributor) throws AmetysRepositoryException 264 { 265 DublinCoreHelper.setDCContributor(this, contributor); 266 } 267 268 @Override 269 public void setDCDate(Date date) throws AmetysRepositoryException 270 { 271 DublinCoreHelper.setDCDate(this, date); 272 } 273 274 @Override 275 public void setDCType(String type) throws AmetysRepositoryException 276 { 277 DublinCoreHelper.setDCType(this, type); 278 } 279 280 @Override 281 public void setDCFormat(String format) throws AmetysRepositoryException 282 { 283 DublinCoreHelper.setDCFormat(this, format); 284 } 285 286 @Override 287 public void setDCIdentifier(String identifier) throws AmetysRepositoryException 288 { 289 DublinCoreHelper.setDCIdentifier(this, identifier); 290 } 291 292 @Override 293 public void setDCSource(String source) throws AmetysRepositoryException 294 { 295 DublinCoreHelper.setDCSource(this, source); 296 } 297 298 @Override 299 public void setDCLanguage(String language) throws AmetysRepositoryException 300 { 301 DublinCoreHelper.setDCLanguage(this, language); 302 } 303 304 @Override 305 public void setDCRelation(String relation) throws AmetysRepositoryException 306 { 307 DublinCoreHelper.setDCRelation(this, relation); 308 } 309 310 @Override 311 public void setDCCoverage(String coverage) throws AmetysRepositoryException 312 { 313 DublinCoreHelper.setDCCoverage(this, coverage); 314 } 315 316 @Override 317 public void setDCRights(String rights) throws AmetysRepositoryException 318 { 319 DublinCoreHelper.setDCRights(this, rights); 320 } 321 322 // ---------------------------- COMMENTS ------------------------------ // 323 @Override 324 public Comment createComment() 325 { 326 return new Comment(_getFactory()._getModifiableContentHelper().getCommentsDataHolder(this, true)); 327 } 328 329 public Comment createComment(String commentId, ZonedDateTime creationDate) 330 { 331 return new Comment(_getFactory()._getModifiableContentHelper().getCommentsDataHolder(this, true), commentId, creationDate); 332 } 333 334 @Override 335 public Comment getComment(String commentId) throws AmetysRepositoryException 336 { 337 ModifiableModelLessDataHolder commentsDataHolder = _getFactory()._getModifiableContentHelper().getCommentsDataHolder(this, false); 338 return commentsDataHolder != null ? Comment.getComment(commentsDataHolder, commentId) : null; 339 } 340 341 @Override 342 public List<Comment> getComments(boolean includeNotValidatedComments, boolean includeValidatedComments) throws AmetysRepositoryException 343 { 344 ModifiableModelLessDataHolder commentsDataHolder = _getFactory()._getModifiableContentHelper().getCommentsDataHolder(this, false); 345 return commentsDataHolder != null ? Comment.getComments(commentsDataHolder, includeNotValidatedComments, includeValidatedComments) : List.of(); 346 } 347 348 // ---------------------------- CONTRIBUTOR COMMENTS ------------------------------ // 349 @Override 350 public Comment createContributorComment() 351 { 352 return new Comment(_getFactory()._getModifiableContentHelper().getContributorCommentsDataHolder(this, true)); 353 } 354 355 public Comment createContributorComment(String commentId, ZonedDateTime creationDate) 356 { 357 return new Comment(_getFactory()._getModifiableContentHelper().getContributorCommentsDataHolder(this, true), commentId, creationDate); 358 } 359 360 @Override 361 public Comment getContributorComment(String commentId) throws AmetysRepositoryException 362 { 363 ModifiableModelLessDataHolder commentsDataHolder = _getFactory()._getModifiableContentHelper().getContributorCommentsDataHolder(this, false); 364 return commentsDataHolder != null ? Comment.getComment(commentsDataHolder, commentId) : null; 365 } 366 367 @Override 368 public List<Comment> getContributorComments() throws AmetysRepositoryException 369 { 370 ModifiableModelLessDataHolder commentsDataHolder = _getFactory()._getModifiableContentHelper().getContributorCommentsDataHolder(this, false); 371 return commentsDataHolder != null ? Comment.getComments(commentsDataHolder, false, true) : List.of(); 372 } 373 374 // ---------------------------- DATA HOLDER --------------------------- // 375 @Override 376 public ModifiableContentDataHolder getDataHolder() 377 { 378 // It is necessary to instantiate the repository data because of locks: 379 // when the content is locked, the lock token is released to be taken by anyone 380 // in repository data there is a lock checked boolean that need to be reset in order to take the token 381 // this boolean is only reset at instantiation 382 Collection<ContentType> contentTypes = _getFactory().getContentHelper().getContentTypes(this); 383 return new ModifiableContentDataHolder(this, _getFactory().getContentDataHelper(), new JCRRepositoryData(getNode()), contentTypes); 384 } 385 386 public void fillContent(org.w3c.dom.Node node, XMLValuesExtractorAdditionalDataGetter additionalDataGetter) throws Exception 387 { 388 _getFactory().getContentExtractor().fillContent(this, node, additionalDataGetter); 389 } 390 391 public TrashElement moveToTrash() throws AmetysRepositoryException 392 { 393 TrashElement trashElement = _getFactory()._getTrashElementDAO().createTrashElement(this, getTitle()); 394 trashElement.setValue(ContentTrashElementType.CONTENT_TYPES, getTypes()); 395 396 // Clone the ametys object from the original session to trash session 397 Node storedNode = NodeHelper.cloneNode(getNode(), trashElement.getNode()); 398 399 try 400 { 401 storedNode.setProperty("ametys-internal:path", this.getParentPath()); 402 } 403 catch (RepositoryException e) 404 { 405 throw new AmetysRepositoryException("failed to store the content path", e); 406 } 407 408 // Remove the node from the original session 409 remove(); 410 411 return trashElement; 412 } 413 414 public TrashableAmetysObject restoreFromTrash() throws UnknownParentException 415 { 416 try 417 { 418 Property property = getNode().getProperty("ametys-internal:path"); 419 String parentPath = property.getValue().getString(); 420 property.remove(); 421 JCRTraversableAmetysObject parent = _getFactory()._getResolver().resolveByPath(parentPath); 422 // Get the node name, can be adjust if another content has already the same node name 423 String nodeName = NameHelper.getUniqueAmetysObjectName(parent, getName(), NameComputationMode.GENERATED_KEY, true); 424 425 // Create the hashed nodes 426 Node hashNode = NodeHelper.getOrCreateFinalHashNode(parent.getNode(), nodeName); 427 428 // Clone the ametys object from the trash session to default session 429 NodeHelper.cloneNode(getNode(), hashNode, nodeName); 430 431 // Remove the node from the trash session 432 remove(); 433 434 return parent.getChild(nodeName); 435 } 436 catch (RepositoryException e) 437 { 438 throw new AmetysRepositoryException("failed to store the content path", e); 439 } 440 } 441}