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}