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}