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