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