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