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}