001/*
002 *  Copyright 2010 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.plugins.explorer.resources.jcr;
017
018import java.io.InputStream;
019import java.util.Calendar;
020import java.util.Date;
021import java.util.GregorianCalendar;
022import java.util.List;
023import java.util.Set;
024
025import javax.jcr.Binary;
026import javax.jcr.Node;
027import javax.jcr.NodeIterator;
028import javax.jcr.Property;
029import javax.jcr.RepositoryException;
030import javax.jcr.Value;
031import javax.jcr.version.VersionHistory;
032
033import org.apache.commons.lang3.StringUtils;
034import org.apache.jackrabbit.JcrConstants;
035
036import org.ametys.core.user.UserIdentity;
037import org.ametys.plugins.explorer.ExplorerNode;
038import org.ametys.plugins.explorer.resources.ModifiableResource;
039import org.ametys.plugins.explorer.resources.Resource;
040import org.ametys.plugins.repository.AmetysObject;
041import org.ametys.plugins.repository.AmetysRepositoryException;
042import org.ametys.plugins.repository.CopiableAmetysObject;
043import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
044import org.ametys.plugins.repository.RepositoryConstants;
045import org.ametys.plugins.repository.UnknownAmetysObjectException;
046import org.ametys.plugins.repository.dublincore.DCMITypes;
047import org.ametys.plugins.repository.jcr.DefaultLockableAmetysObject;
048import org.ametys.plugins.repository.jcr.DublinCoreHelper;
049import org.ametys.plugins.repository.jcr.JCRTraversableAmetysObject;
050import org.ametys.plugins.repository.jcr.NameHelper;
051import org.ametys.plugins.repository.jcr.NameHelper.NameComputationMode;
052import org.ametys.plugins.repository.jcr.NodeHelper;
053import org.ametys.plugins.repository.jcr.NodeTypeHelper;
054import org.ametys.plugins.repository.tag.TaggableAmetysObjectHelper;
055import org.ametys.plugins.repository.trash.TrashElement;
056import org.ametys.plugins.repository.trash.TrashableAmetysObject;
057import org.ametys.plugins.repository.trash.UnknownParentException;
058
059/**
060 * Default implementation of an {@link Resource}, backed by a JCR node.<br>
061 * @param <F> the actual type of factory.
062 */
063public class JCRResource<F extends JCRResourceFactory> extends DefaultLockableAmetysObject<F> implements ModifiableResource, CopiableAmetysObject, TrashableAmetysObject
064{
065    /** The name of node holding the creator */
066    public static final String CREATOR_NODE_NAME = "creator";
067    /** Constants for lastModified Metadata */
068    public static final String CREATION_DATE = "creationDate";
069    /** The name of node holding the last contributor */
070    public static final String CONTRIBUTOR_NODE_NAME = "contributor";
071    
072    /**
073     * Creates an {@link JCRResource}.
074     * @param node the node backing this {@link AmetysObject}
075     * @param parentPath the parentPath in the Ametys hierarchy
076     * @param factory the DefaultAmetysObjectFactory which created the AmetysObject
077     */
078    public JCRResource(Node node, String parentPath, F factory)
079    {
080        super(node, parentPath, factory);
081    }
082    
083    @Override
084    public void setData(InputStream stream, String mimeType, Date lastModified, UserIdentity author)
085    {
086        Node fileNode = getNode();
087        
088        try
089        {
090            setLastContributor(author);
091            
092            Node resourceNode = null;
093            
094            if (fileNode.hasNode(JcrConstants.JCR_CONTENT))
095            {
096                // Already exists
097                resourceNode = fileNode.getNode(JcrConstants.JCR_CONTENT);
098            }
099            else
100            {
101                resourceNode = fileNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE);
102                setCreator(author);
103                setCreationDate(new Date());
104            }
105            
106            GregorianCalendar gc = new GregorianCalendar();
107            gc.setTime(lastModified);
108            resourceNode.setProperty(JcrConstants.JCR_LASTMODIFIED, gc);
109            
110            resourceNode.setProperty(JcrConstants.JCR_MIMETYPE, mimeType);
111            
112            Binary binary = resourceNode.getSession().getValueFactory().createBinary(stream);
113            resourceNode.setProperty(JcrConstants.JCR_DATA, binary);
114        }
115        catch (RepositoryException e)
116        {
117            throw new AmetysRepositoryException("Cannot set data for resource " + this.getName() + " (" + this.getId() + ")", e);
118        }
119    }
120    
121    @Override
122    public void setLastModified(Date lastModified)
123    {
124        Node fileNode = getNode();
125        
126        try
127        {
128            Node resourceNode = fileNode.getNode(JcrConstants.JCR_CONTENT);
129            
130            GregorianCalendar gc = new GregorianCalendar();
131            gc.setTime(lastModified);
132            resourceNode.setProperty(JcrConstants.JCR_LASTMODIFIED, gc);
133        }
134        catch (RepositoryException e)
135        {
136            throw new AmetysRepositoryException("Cannot set lastmodified for resource " + this.getName() + " (" + this.getId() + ")", e);
137        }
138    }
139    
140    @Override
141    public void setKeywords(String keywords)
142    {
143        String[] words = StringUtils.stripAll(StringUtils.split(keywords, ','));
144        
145        String[] trimWords = new String[words.length];
146        for (int i = 0; i < words.length; i++)
147        {
148            trimWords[i] = words[i].trim();
149        }
150        
151        Node fileNode = getNode();
152        try
153        {
154            fileNode.setProperty("ametys:keywords", trimWords);
155        }
156        catch (RepositoryException e)
157        {
158            throw new AmetysRepositoryException("Cannot set keywords for resource " + this.getName() + " (" + this.getId() + ")", e);
159        }
160    }
161    
162    @Override
163    public void setKeywords(String[] keywords)
164    {
165        Node fileNode = getNode();
166        try
167        {
168            fileNode.setProperty("ametys:keywords", keywords);
169        }
170        catch (RepositoryException e)
171        {
172            throw new AmetysRepositoryException("Cannot set keywords for resource " + this.getName() + " (" + this.getId() + ")", e);
173        }
174    }
175    
176    @Override
177    public void setMimeType(String mimeType)
178    {
179        Node fileNode = getNode();
180        
181        try
182        {
183            Node resourceNode = fileNode.getNode(JcrConstants.JCR_CONTENT);
184            
185            resourceNode.setProperty(JcrConstants.JCR_MIMETYPE, mimeType);
186        }
187        catch (RepositoryException e)
188        {
189            throw new AmetysRepositoryException("Cannot set mimetype for resource " + this.getName() + " (" + this.getId() + ")", e);
190        }
191    }
192    
193    @Override
194    public void setCreator(UserIdentity author)
195    {
196        try
197        {
198            Node authorNode = null;
199            if (getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME))
200            {
201                authorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME);
202            }
203            else
204            {
205                authorNode = getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME, RepositoryConstants.USER_NODETYPE);
206            }
207            authorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", author.getLogin());
208            authorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", author.getPopulationId());
209        }
210        catch (RepositoryException e)
211        {
212            throw new AmetysRepositoryException("Cannot set creator for resource " + this.getName() + " (" + this.getId() + ")", e);
213        }
214    }
215    
216    @Override
217    public InputStream getInputStream () throws AmetysRepositoryException
218    {
219        Node fileNode = getNode();
220        try
221        {
222            Node resourceNode = null;
223            
224            if (fileNode.hasNode(JcrConstants.JCR_CONTENT))
225            {
226                // Already exists
227                resourceNode = fileNode.getNode(JcrConstants.JCR_CONTENT);
228                return resourceNode.getProperty(JcrConstants.JCR_DATA).getBinary().getStream();
229            }
230            return null;
231        }
232        catch (RepositoryException e)
233        {
234            throw new AmetysRepositoryException("Cannot get inputstream for resource " + this.getName() + " (" + this.getId() + ")", e);
235        }
236    }
237    
238    @Override
239    public String getMimeType ()  throws AmetysRepositoryException
240    {
241        Node fileNode = getNode();
242        try
243        {
244            Node resourceNode = null;
245            
246            if (fileNode.hasNode(JcrConstants.JCR_CONTENT))
247            {
248                // Already exists
249                resourceNode = fileNode.getNode(JcrConstants.JCR_CONTENT);
250                return resourceNode.getProperty(JcrConstants.JCR_MIMETYPE).getString();
251            }
252            
253            return null;
254        }
255        catch (RepositoryException e)
256        {
257            throw new AmetysRepositoryException("Cannot get mimetype for resource " + this.getName() + " (" + this.getId() + ")", e);
258        }
259    }
260    
261    @Override
262    public Date getLastModified () throws AmetysRepositoryException
263    {
264        Node fileNode = getNode();
265        try
266        {
267            Node resourceNode = null;
268            
269            if (fileNode.hasNode(JcrConstants.JCR_CONTENT))
270            {
271                resourceNode = fileNode.getNode(JcrConstants.JCR_CONTENT);
272                return resourceNode.getProperty(JcrConstants.JCR_LASTMODIFIED).getDate().getTime();
273            }
274            
275            return null;
276        }
277        catch (RepositoryException e)
278        {
279            throw new AmetysRepositoryException("Cannot get lastmodified for resource " + this.getName() + " (" + this.getId() + ")", e);
280        }
281    }
282    
283    @Override
284    public long getLength() throws AmetysRepositoryException
285    {
286        Node fileNode = getNode();
287        try
288        {
289            Node resourceNode = null;
290            
291            if (fileNode.hasNode(JcrConstants.JCR_CONTENT))
292            {
293                resourceNode = fileNode.getNode(JcrConstants.JCR_CONTENT);
294                return resourceNode.getProperty(JcrConstants.JCR_DATA).getLength();
295            }
296            
297            return 0;
298        }
299        catch (RepositoryException e)
300        {
301            throw new AmetysRepositoryException("Cannot get length for resource " + this.getName() + " (" + this.getId() + ")", e);
302        }
303    }
304    
305    @Override
306    public UserIdentity getCreator() throws AmetysRepositoryException
307    {
308        try
309        {
310            Node authorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME);
311            return new UserIdentity(authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login").getString(), authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population").getString());
312        }
313        catch (RepositoryException e)
314        {
315            throw new AmetysRepositoryException("Cannot get creator for resource " + this.getName() + " (" + this.getId() + ")", e);
316        }
317    }
318    
319    /**
320     * Get the author from old revision
321     * @param revision The revision
322     * @return The user identity of the author or <code>null</code> if not found
323     * @throws RepositoryException If an error occurred
324     */
325    public UserIdentity getAuthorFromRevision (String revision) throws RepositoryException
326    {
327        try
328        {
329            switchToRevision(revision);
330            
331            VersionHistory history = getVersionHistory();
332            Node versionNode = history.getVersion(revision);
333            Node frozenNode = versionNode.getNode(JcrConstants.JCR_FROZENNODE);
334            
335            if (frozenNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME))
336            {
337                Node authorNode = frozenNode.getNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATOR_NODE_NAME);
338                return new UserIdentity(authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login").getString(), authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population").getString());
339            }
340            return null;
341        }
342        catch (RepositoryException e)
343        {
344            throw new AmetysRepositoryException("Unable to get author from revision: " + revision + " of resource " + this.getName() + " (" + this.getId() + ")", e);
345        }
346    }
347    
348    @Override
349    public String[] getKeywords() throws AmetysRepositoryException
350    {
351        Node fileNode = getNode();
352        try
353        {
354            if (!fileNode.hasProperty("ametys:keywords"))
355            {
356                return new String[0];
357            }
358            
359            Value[] values = fileNode.getProperty("ametys:keywords").getValues();
360            String[] result = new String[values.length];
361            
362            for (int i = 0; i < values.length; i++)
363            {
364                result[i] = values[i].getString();
365            }
366            
367            return result;
368        }
369        catch (RepositoryException e)
370        {
371            throw new AmetysRepositoryException("Cannot get keywords for resource " + this.getName() + " (" + this.getId() + ")", e);
372        }
373    }
374    
375    @Override
376    public String getKeywordsAsString() throws AmetysRepositoryException
377    {
378        Node fileNode = getNode();
379        try
380        {
381            if (!fileNode.hasProperty("ametys:keywords"))
382            {
383                return StringUtils.EMPTY;
384            }
385            
386            StringBuilder sb = new StringBuilder();
387            Value[] values = fileNode.getProperty("ametys:keywords").getValues();
388            
389            for (Value value : values)
390            {
391                if (sb.length() > 0)
392                {
393                    sb.append(", ");
394                }
395                
396                sb.append(value.getString());
397            }
398            
399            return sb.toString();
400        }
401        catch (RepositoryException e)
402        {
403            throw new AmetysRepositoryException("Cannot get keywords for resource " + this.getName() + " (" + this.getId() + ")", e);
404        }
405    }
406    
407    @Override
408    protected void restoreFromNode(Node node) throws RepositoryException
409    {
410        super.restoreFromNode(node);
411        
412        // First remove node
413        NodeIterator nit = getBaseNode().getNodes(JcrConstants.JCR_CONTENT);
414        while (nit.hasNext())
415        {
416            nit.nextNode().remove();
417        }
418        
419        NodeIterator new_nit = node.getNodes(JcrConstants.JCR_CONTENT);
420        while (new_nit.hasNext())
421        {
422            copyNode(getBaseNode(), new_nit.nextNode());
423        }
424    }
425    
426    @Override
427    public String getResourcePath() throws AmetysRepositoryException
428    {
429        return ((ExplorerNode) getParent()).getExplorerPath() + "/" + getName();
430    }
431    
432    @Override
433    public AmetysObject copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException
434    {
435        try
436        {
437            String nodeTypeName = NodeTypeHelper.getNodeTypeName(getNode());
438            
439            JCRResource copiedResource = parent.createChild(name, nodeTypeName);
440            
441            copiedResource.setKeywords(getKeywords());
442            copiedResource.setData(getInputStream(), getMimeType(), getLastModified(), getCreator());
443            
444            parent.saveChanges();
445            
446            return copiedResource;
447        }
448        catch (RepositoryException e)
449        {
450            throw new AmetysRepositoryException("Error copying the collection " + getId() + " into " + parent.getId(), e);
451        }
452    }
453    
454    @Override
455    public AmetysObject copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException
456    {
457        return copyTo(parent, name);
458    }
459    
460    // Dublin Core metadata. //
461    
462    @Override
463    public String getDCTitle() throws AmetysRepositoryException
464    {
465        return DublinCoreHelper.getDCTitle(this, getName());
466    }
467    
468    @Override
469    public void setDCTitle(String title) throws AmetysRepositoryException
470    {
471        DublinCoreHelper.setDCTitle(this, title);
472    }
473    
474    @Override
475    public String getDCCreator() throws AmetysRepositoryException
476    {
477        return DublinCoreHelper.getDCCreator(this);
478    }
479    
480    @Override
481    public void setDCCreator(String creator) throws AmetysRepositoryException
482    {
483        DublinCoreHelper.setDCCreator(this, creator);
484    }
485    
486    @Override
487    public String[] getDCSubject() throws AmetysRepositoryException
488    {
489        return DublinCoreHelper.getDCSubject(this, getKeywords());
490    }
491    
492    @Override
493    public void setDCSubject(String[] subject) throws AmetysRepositoryException
494    {
495        DublinCoreHelper.setDCSubject(this, subject);
496    }
497    
498    @Override
499    public String getDCDescription() throws AmetysRepositoryException
500    {
501        return DublinCoreHelper.getDCDescription(this);
502    }
503    
504    @Override
505    public void setDCDescription(String description) throws AmetysRepositoryException
506    {
507        DublinCoreHelper.setDCDescription(this, description);
508    }
509    
510    @Override
511    public String getDCPublisher() throws AmetysRepositoryException
512    {
513        return DublinCoreHelper.getDCPublisher(this);
514    }
515    
516    @Override
517    public void setDCPublisher(String publisher) throws AmetysRepositoryException
518    {
519        DublinCoreHelper.setDCPublisher(this, publisher);
520    }
521    
522    @Override
523    public String getDCContributor() throws AmetysRepositoryException
524    {
525        return DublinCoreHelper.getDCContributor(this, UserIdentity.userIdentityToString(getCreator()));
526    }
527    
528    @Override
529    public void setDCContributor(String contributor) throws AmetysRepositoryException
530    {
531        DublinCoreHelper.setDCContributor(this, contributor);
532    }
533    
534    @Override
535    public Date getDCDate() throws AmetysRepositoryException
536    {
537        return DublinCoreHelper.getDCDate(this, getLastModified());
538    }
539    
540    @Override
541    public void setDCDate(Date date) throws AmetysRepositoryException
542    {
543        DublinCoreHelper.setDCDate(this, date);
544    }
545    
546    @Override
547    public String getDCType() throws AmetysRepositoryException
548    {
549        return DublinCoreHelper.getDCType(this, _getDefaultDCType());
550    }
551    
552    private String _getDefaultDCType ()
553    {
554        String mimetype = getMimeType();
555        
556        if (mimetype == null)
557        {
558            return DCMITypes.TEXT;
559        }
560        else if (mimetype.startsWith("image"))
561        {
562            return DCMITypes.IMAGE;
563        }
564        else if (mimetype.startsWith("video") || "application/x-shockwave-flash".equals(mimetype))
565        {
566            return DCMITypes.INTERACTIVERESOURCE;
567        }
568        else if (mimetype.startsWith("audio"))
569        {
570            return DCMITypes.SOUND;
571        }
572        
573        return DCMITypes.TEXT;
574    }
575    
576    
577    @Override
578    public void setDCType(String type) throws AmetysRepositoryException
579    {
580        DublinCoreHelper.setDCType(this, type);
581    }
582    
583    @Override
584    public String getDCFormat() throws AmetysRepositoryException
585    {
586        return DublinCoreHelper.getDCFormat(this, getMimeType());
587    }
588    
589    @Override
590    public void setDCFormat(String format) throws AmetysRepositoryException
591    {
592        DublinCoreHelper.setDCFormat(this, format);
593    }
594    
595    @Override
596    public String getDCIdentifier() throws AmetysRepositoryException
597    {
598        return DublinCoreHelper.getDCIdentifier(this, getId());
599    }
600    
601    @Override
602    public void setDCIdentifier(String identifier) throws AmetysRepositoryException
603    {
604        DublinCoreHelper.setDCIdentifier(this, identifier);
605    }
606    
607    @Override
608    public String getDCSource() throws AmetysRepositoryException
609    {
610        return DublinCoreHelper.getDCSource(this);
611    }
612    
613    @Override
614    public void setDCSource(String source) throws AmetysRepositoryException
615    {
616        DublinCoreHelper.setDCSource(this, source);
617    }
618    
619    @Override
620    public String getDCLanguage() throws AmetysRepositoryException
621    {
622        return DublinCoreHelper.getDCLanguage(this);
623    }
624    
625    @Override
626    public void setDCLanguage(String language) throws AmetysRepositoryException
627    {
628        DublinCoreHelper.setDCLanguage(this, language);
629    }
630    
631    @Override
632    public String getDCRelation() throws AmetysRepositoryException
633    {
634        return DublinCoreHelper.getDCRelation(this);
635    }
636    
637    @Override
638    public void setDCRelation(String relation) throws AmetysRepositoryException
639    {
640        DublinCoreHelper.setDCRelation(this, relation);
641    }
642    
643    @Override
644    public String getDCCoverage() throws AmetysRepositoryException
645    {
646        return DublinCoreHelper.getDCCoverage(this, getDCLanguage());
647    }
648    
649    @Override
650    public void setDCCoverage(String coverage) throws AmetysRepositoryException
651    {
652        DublinCoreHelper.setDCCoverage(this, coverage);
653    }
654    
655    @Override
656    public String getDCRights() throws AmetysRepositoryException
657    {
658        return DublinCoreHelper.getDCRights(this);
659    }
660    
661    @Override
662    public void setDCRights(String rights) throws AmetysRepositoryException
663    {
664        DublinCoreHelper.setDCRights(this, rights);
665    }
666
667    public Date getCreationDate() throws AmetysRepositoryException
668    {
669        Node fileNode = getNode();
670        try
671        {
672            if (!fileNode.hasProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATION_DATE))
673            {
674                return null;
675            }
676            
677            return fileNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATION_DATE).getDate().getTime();
678        }
679        catch (RepositoryException e)
680        {
681            throw new AmetysRepositoryException("Cannot get creation date for resource " + this.getName() + " (" + this.getId() + ")", e);
682        }
683    }
684
685    public void setCreationDate(Date creationDate)
686    {
687        Node fileNode = getNode();
688        try
689        {
690            Calendar calendar = new GregorianCalendar();
691            calendar.setTime(creationDate);
692            fileNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":" + CREATION_DATE, calendar);
693        }
694        catch (RepositoryException e)
695        {
696            throw new AmetysRepositoryException("Cannot set create date for resource " + this.getName() + " (" + this.getId() + ")", e);
697        }
698    }
699
700    public UserIdentity getLastContributor() throws AmetysRepositoryException
701    {
702        try
703        {
704            Node authorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CONTRIBUTOR_NODE_NAME);
705            return new UserIdentity(authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login").getString(), authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population").getString());
706        }
707        catch (RepositoryException e)
708        {
709            throw new AmetysRepositoryException("Cannot get last contributor for resource " + this.getName() + " (" + this.getId() + ")", e);
710        }
711    }
712
713    public void setLastContributor(UserIdentity lastContributor)
714    {
715        try
716        {
717            Node lastContributorNode = null;
718            if (getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CONTRIBUTOR_NODE_NAME))
719            {
720                lastContributorNode = getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CONTRIBUTOR_NODE_NAME);
721            }
722            else
723            {
724                lastContributorNode = getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ":" + CONTRIBUTOR_NODE_NAME, RepositoryConstants.USER_NODETYPE);
725            }
726            lastContributorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", lastContributor.getLogin());
727            lastContributorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", lastContributor.getPopulationId());
728        }
729        catch (RepositoryException e)
730        {
731            throw new AmetysRepositoryException("Cannot set last contributor for resource " + this.getName() + " (" + this.getId() + ")", e);
732        }
733    }
734    
735    public void tag(String tag) throws AmetysRepositoryException
736    {
737        TaggableAmetysObjectHelper.tag(this, tag);
738    }
739
740    public void untag(String tag) throws AmetysRepositoryException
741    {
742        TaggableAmetysObjectHelper.untag(this, tag);
743    }
744
745    public Set<String> getTags() throws AmetysRepositoryException
746    {
747        return TaggableAmetysObjectHelper.getTags(this);
748    }
749    
750    public TrashElement moveToTrash()
751    {
752        TrashElement trashElement = _getFactory()._getTrashElementDAO().createTrashElement(this, getName());
753        
754        // Clone the ametys object from the original session to trash session
755        Node storedNode = NodeHelper.cloneNode(getNode(), trashElement.getNode());
756        
757        try
758        {
759            storedNode.setProperty("ametys-internal:path", this.getParentPath());
760        }
761        catch (RepositoryException e)
762        {
763            throw new AmetysRepositoryException("failed to store the content path", e);
764        }
765
766        // Remove the node from the original session
767        remove();
768        
769        return trashElement;
770    }
771    
772    public JCRResource restoreFromTrash() throws UnknownParentException
773    {
774        try
775        {
776            Property property = getNode().getProperty("ametys-internal:path");
777            String parentPath = property.getValue().getString();
778            property.remove();
779            JCRTraversableAmetysObject parent;
780            try
781            {
782                parent = _getFactory()._getResolver().resolveByPath(parentPath);
783            }
784            catch (UnknownAmetysObjectException e)
785            {
786                throw new UnknownParentException("No parent available at path " + parentPath, e);
787            }
788            
789            // Get the node name, can be adjust if another content has already the same node name
790            String nodeName = NameHelper.getUniqueAmetysObjectName(parent, getName(), NameComputationMode.USER_FRIENDLY, true);
791            // Clone the ametys object from the trash session to default session
792            NodeHelper.cloneNode(getNode(), parent.getNode(), nodeName);
793            
794            // Remove the node from the trash session
795            remove();
796            
797            return parent.getChild(nodeName);
798        }
799        catch (RepositoryException e)
800        {
801            throw new AmetysRepositoryException("failed to store the content path", e);
802        }
803    }
804}