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