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.repository.metadata.jcr;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Date;
021import java.util.GregorianCalendar;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Locale;
025import java.util.Set;
026import java.util.regex.Pattern;
027
028import javax.jcr.Node;
029import javax.jcr.NodeIterator;
030import javax.jcr.PathNotFoundException;
031import javax.jcr.Property;
032import javax.jcr.PropertyIterator;
033import javax.jcr.PropertyType;
034import javax.jcr.RepositoryException;
035import javax.jcr.Value;
036import javax.jcr.lock.Lock;
037import javax.jcr.lock.LockManager;
038
039import org.apache.commons.lang3.LocaleUtils;
040
041import org.ametys.core.user.UserIdentity;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.plugins.repository.AmetysRepositoryException;
044import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
045import org.ametys.plugins.repository.RepositoryConstants;
046import org.ametys.plugins.repository.jcr.NodeTypeHelper;
047import org.ametys.plugins.repository.metadata.CommentableCompositeMetadata;
048import org.ametys.plugins.repository.metadata.MetadataComment;
049import org.ametys.plugins.repository.metadata.ModifiableBinaryMetadata;
050import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
051import org.ametys.plugins.repository.metadata.ModifiableFile;
052import org.ametys.plugins.repository.metadata.ModifiableFolder;
053import org.ametys.plugins.repository.metadata.ModifiableResource;
054import org.ametys.plugins.repository.metadata.ModifiableRichText;
055import org.ametys.plugins.repository.metadata.MultilingualString;
056import org.ametys.plugins.repository.metadata.UnknownMetadataException;
057
058/**
059 * JCR implementation of a CompositeMetadata.<br>
060 * This implementation is based on a child node of the Node supporting the actual content.
061 */
062public class JCRCompositeMetadata implements ModifiableCompositeMetadata, CommentableCompositeMetadata
063{
064    /** Prefix for jcr properties and nodes names */
065    public static final String METADATA_PREFIX = RepositoryConstants.NAMESPACE_PREFIX + ':';
066    
067    /** Comments metadata suffix */
068    public static final String METADATA_COMMENTS_SUFFIX = "_comments";
069    
070    /** The JCR nodetype of object collection metadata. */
071    public static final String OBJECT_COLLECTION_METADATA_NODETYPE = METADATA_PREFIX + "objectCollectionMetadata";
072    
073    /** The JCR nodetype of multilingual metadata. */
074    public static final String MULTILINGUAL_STRING_METADATA_NODETYPE = METADATA_PREFIX + "multilingualString";
075    
076    private static Pattern __metadataNamePattern;
077
078    private Node _node;
079    private boolean _lockAlreadyChecked;
080    private AmetysObjectResolver _resolver;
081
082    /**
083     * Constructor
084     * @param metadataNode the Node supporting this composite metadata
085     * @param resolver The resolver, used to resolve object collections.
086     */
087    public JCRCompositeMetadata(Node metadataNode, AmetysObjectResolver resolver)
088    {
089        _node = metadataNode;
090        _resolver = resolver;
091    }
092    
093    /**
094     * Retrieves the underlying node.
095     * @return the underlying node.
096     */
097    public Node getNode()
098    {
099        return _node;
100    }
101    
102    public boolean hasMetadata(String metadataName) throws AmetysRepositoryException
103    {
104        try
105        {
106            // TODO filter sub-nodes by type (like in getMetadataNames).
107            return _node.hasProperty(METADATA_PREFIX + metadataName) || _node.hasNode(METADATA_PREFIX + metadataName);
108        }
109        catch (RepositoryException e)
110        {
111            throw new AmetysRepositoryException(e);
112        }
113    }
114    
115    @Override
116    public void rename(String newName) throws AmetysRepositoryException
117    {
118        try
119        {
120            getNode().getSession().move(getNode().getPath(), getNode().getParent().getPath() + "/" + newName);
121        }
122        catch (RepositoryException e)
123        {
124            throw new AmetysRepositoryException(e);
125        }        
126    }
127
128    public void removeMetadata(String metadataName) throws AmetysRepositoryException
129    {
130        try
131        {
132            _checkLock();
133            String realMetadataName = METADATA_PREFIX + metadataName;
134            
135            if (_node.hasProperty(realMetadataName))
136            {
137                _node.getProperty(realMetadataName).remove();
138            }
139            else if (_node.hasNode(realMetadataName))
140            {
141                // Remove all existing nodes
142                NodeIterator it = _node.getNodes(realMetadataName);
143                while (it.hasNext())
144                {
145                    Node next = it.nextNode();
146                    next.remove();
147                }
148            }
149            else
150            {
151                throw new UnknownMetadataException("No metadata for name: " + metadataName);
152            }
153        }
154        catch (RepositoryException e)
155        {
156            throw new AmetysRepositoryException("Unable to remove metadata " + metadataName, e);
157        }
158    }
159    
160    @Override
161    public void copyTo(ModifiableCompositeMetadata metadata) throws AmetysRepositoryException
162    {
163        String[] metadataNames = getMetadataNames();
164        for (String metadataName : metadataNames)
165        {
166            if (hasMetadata(metadataName))
167            {
168                MetadataType type = getType(metadataName);
169                
170                boolean multiple = isMultiple(metadataName);
171                switch (type)
172                {
173                    case COMPOSITE:
174                        ModifiableCompositeMetadata compositeMetadata = metadata.getCompositeMetadata(metadataName, true);
175                        getCompositeMetadata(metadataName).copyTo(compositeMetadata);
176                        break;
177                    case USER:
178                        if (multiple)
179                        {
180                            metadata.setMetadata(metadataName, getUserArray(metadataName));
181                        }
182                        else
183                        {
184                            metadata.setMetadata(metadataName, getUser(metadataName));
185                        }
186                        break;
187                    case BOOLEAN:
188                        if (multiple)
189                        {
190                            metadata.setMetadata(metadataName, getBooleanArray(metadataName));
191                        }
192                        else
193                        {
194                            metadata.setMetadata(metadataName, getBoolean(metadataName));
195                        }
196                        break;
197                    case DATE:
198                        if (multiple)
199                        {
200                            metadata.setMetadata(metadataName, getDateArray(metadataName));
201                        }
202                        else
203                        {
204                            metadata.setMetadata(metadataName, getDate(metadataName));
205                        }
206                        break;
207                    case DOUBLE:
208                        if (multiple)
209                        {
210                            metadata.setMetadata(metadataName, getDoubleArray(metadataName));
211                        }
212                        else
213                        {
214                            metadata.setMetadata(metadataName, getDouble(metadataName));
215                        }
216                        break;
217                    case LONG:
218                        if (multiple)
219                        {
220                            metadata.setMetadata(metadataName, getLongArray(metadataName));
221                        }
222                        else
223                        {
224                            metadata.setMetadata(metadataName, getLong(metadataName));
225                        }
226                        break;
227                    case BINARY:
228                        ModifiableBinaryMetadata binaryMetadata = metadata.getBinaryMetadata(metadataName, true);
229                        ModifiableBinaryMetadata srcBinary = getBinaryMetadata(metadataName);
230                        
231                        binaryMetadata.setFilename(srcBinary.getFilename());
232                        binaryMetadata.setMimeType(srcBinary.getMimeType());
233                        binaryMetadata.setLastModified(srcBinary.getLastModified());
234                        binaryMetadata.setEncoding(srcBinary.getEncoding());
235                        binaryMetadata.setInputStream(srcBinary.getInputStream());
236                        break;
237                    case RICHTEXT:
238                        ModifiableRichText richText = metadata.getRichText(metadataName, true);
239                        ModifiableRichText srcRichText = getRichText(metadataName);
240                        
241                        richText.setInputStream(srcRichText.getInputStream());
242                        richText.setEncoding(srcRichText.getEncoding());
243                        richText.setLastModified(srcRichText.getLastModified());
244                        richText.setMimeType(srcRichText.getMimeType());
245                        
246                        // Copy additional data
247                        _copyDataFolder (srcRichText.getAdditionalDataFolder(), richText.getAdditionalDataFolder());
248                        break;
249                    case MULTILINGUAL_STRING:
250                        MultilingualString srcMultilingualString = getMultilingualString(metadataName);
251                        metadata.setMetadata(metadataName, srcMultilingualString);
252                        break;
253                    default:
254                        if (multiple)
255                        {
256                            metadata.setMetadata(metadataName, getStringArray(metadataName));
257                        }
258                        else
259                        {
260                            metadata.setMetadata(metadataName, getString(metadataName));
261                        }
262                        break;
263                }
264            }
265        }
266    }
267    
268    private void _copyDataFolder (ModifiableFolder srcDataFolder, ModifiableFolder targetDataFolder)
269    {
270        // Copy folders
271        Collection<ModifiableFolder> folders = srcDataFolder.getFolders();
272        for (ModifiableFolder srcSubfolder : folders)
273        {
274            ModifiableFolder targetSubFolder = targetDataFolder.addFolder(srcSubfolder.getName());
275            _copyDataFolder (srcSubfolder, targetSubFolder);
276        }
277        
278        // Copy files
279        Collection<ModifiableFile> files = srcDataFolder.getFiles();
280        for (ModifiableFile srcFile : files)
281        {
282            _copyFile (srcFile, targetDataFolder);
283        }
284    }
285    
286    private void _copyFile (ModifiableFile srcFile, ModifiableFolder targetDataFolder)
287    {
288        ModifiableResource srcResource = srcFile.getResource();
289        
290        ModifiableFile targetFile = targetDataFolder.addFile(srcFile.getName());
291        ModifiableResource targetResource = targetFile.getResource();
292        
293        targetResource.setLastModified(srcResource.getLastModified());
294        targetResource.setMimeType(srcResource.getMimeType());
295        targetResource.setEncoding(srcResource.getEncoding());
296        targetResource.setInputStream(srcResource.getInputStream());
297    }
298
299    public MetadataType getType(String metadataName) throws AmetysRepositoryException
300    {
301        try
302        {
303            String realMetadataName = METADATA_PREFIX + metadataName;
304            
305            if (_node.hasNode(realMetadataName))
306            {
307                Node node = _node.getNode(realMetadataName);
308                String type = node.getPrimaryNodeType().getName();
309                if ("nt:frozenNode".equals(_node.getPrimaryNodeType().getName()))
310                {
311                    // on version history
312                    type = node.getProperty("jcr:frozenPrimaryType").getString();
313                }
314                
315                if (type.equals(OBJECT_COLLECTION_METADATA_NODETYPE))
316                {
317                    return MetadataType.OBJECT_COLLECTION;
318                }
319                else if (type.equals(METADATA_PREFIX + "binaryMetadata"))
320                {
321                    return MetadataType.BINARY;
322                }
323                else if (type.equals(METADATA_PREFIX + "richText"))
324                {
325                    return MetadataType.RICHTEXT;
326                }
327                else if (type.equals(METADATA_PREFIX + "user"))
328                {
329                    return MetadataType.USER;
330                }
331                else if (type.equals(MULTILINGUAL_STRING_METADATA_NODETYPE))
332                {
333                    return MetadataType.MULTILINGUAL_STRING;
334                }
335                else
336                {
337                    return MetadataType.COMPOSITE;
338                }
339            }
340
341            if (_node.hasProperty(realMetadataName))
342            {
343                Property property = _node.getProperty(realMetadataName);
344                int type = property.getType();
345    
346                switch (type)
347                {
348                    case PropertyType.STRING:
349                        return MetadataType.STRING;
350                    case PropertyType.BOOLEAN:
351                        return MetadataType.BOOLEAN;
352                    case PropertyType.DATE:
353                        return MetadataType.DATE;
354                    case PropertyType.DOUBLE:
355                        return MetadataType.DOUBLE;
356                    case PropertyType.LONG:
357                        return MetadataType.LONG;
358                    default:
359                        throw new AmetysRepositoryException("Unhandled JCR property type : " + PropertyType.nameFromValue(type));
360                }
361            }
362            
363            throw new UnknownMetadataException("No metadata for name: " + metadataName);
364        }
365        catch (RepositoryException e)
366        {
367            throw new AmetysRepositoryException("Unable to get property type", e);
368        }
369    }
370
371    public String getString(String metadataName)
372    {
373        return _getString(metadataName, null, true);
374    }
375
376    @Override
377    public String getString(String metadataName, String defaultValue)
378    {
379        return _getString(metadataName, defaultValue, false);
380    }
381    
382    private String _getString(String metadataName, String defaultValue, boolean failIfNotExist)
383    {
384        try
385        {
386            Property property = _node.getProperty(METADATA_PREFIX + metadataName);
387
388            if (property.getDefinition().isMultiple())
389            {
390                Value[] values = property.getValues();
391
392                if (values.length > 0)
393                {
394                    return values[0].getString();
395                }
396                else
397                {
398                    throw new AmetysRepositoryException("Cannot get a String from an empty array");
399                }
400            }
401
402            return property.getString();
403        }
404        catch (PathNotFoundException e)
405        {
406            if (failIfNotExist)
407            {
408                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
409            }
410            else
411            {
412                return defaultValue;
413            }
414        }
415        catch (RepositoryException e)
416        {
417            throw new AmetysRepositoryException("Unable to get String property", e);
418        }
419    }
420
421    public String[] getStringArray(String metadataName) throws AmetysRepositoryException
422    {
423        return _getStringArray(metadataName, null, true);
424    }
425
426    public String[] getStringArray(String metadataName, String[] defaultValues)
427    {
428        return _getStringArray(metadataName, defaultValues, false);
429    }
430    
431    private String[] _getStringArray(String metadataName, String[] defaultValues, boolean failIfNotExist)
432    {
433        try
434        {
435            Property property = _node.getProperty(METADATA_PREFIX + metadataName);
436
437            if (property.getDefinition().isMultiple())
438            {
439                Value[] values = property.getValues();
440
441                String[] results = new String[values.length];
442                for (int i = 0; i < values.length; i++)
443                {
444                    Value value = values[i];
445                    results[i] = value.getString();
446                }
447
448                return results;
449            }
450
451            Value value = property.getValue();
452            return new String[] {value.getString()};
453        }
454        catch (PathNotFoundException e)
455        {
456            if (failIfNotExist)
457            {
458                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
459            }
460            else
461            {
462                return defaultValues;
463            }
464        }
465        catch (RepositoryException e)
466        {
467            throw new AmetysRepositoryException("Unable to get String array property", e);
468        }
469    }
470    
471    @Override
472    public String getLocalizedString(String metadataName, Locale locale) throws UnknownMetadataException, AmetysRepositoryException
473    {
474        return _getString(metadataName, locale, null, true);
475    }
476    
477    @Override
478    public String getLocalizedString(String metadataName, Locale locale, String defaultValue) throws AmetysRepositoryException
479    {
480        return _getString(metadataName, locale, defaultValue, false);
481    }
482    
483    @Override
484    public MultilingualString getMultilingualString(String metadataName) throws AmetysRepositoryException
485    {
486        try
487        {
488            if (_node.hasNode(METADATA_PREFIX + metadataName))
489            {
490                Node node = _node.getNode(METADATA_PREFIX + metadataName);
491                
492                MultilingualString values = new MultilingualString();
493                
494                PropertyIterator properties = node.getProperties();
495                while (properties.hasNext())
496                {
497                    Property property = properties.nextProperty();
498                    String name = property.getName();
499                    
500                    try
501                    {
502                        Locale locale = LocaleUtils.toLocale(name);
503                        values.add(locale, property.getString());
504                    }
505                    catch (IllegalArgumentException e)
506                    {
507                        // Property is not a valid locale, ignore it.
508                    }
509                }
510                
511                return values;
512            }
513            else
514            {
515                throw new UnknownMetadataException("No multilingual String named " + metadataName);
516            }
517        }
518        catch (RepositoryException e)
519        {
520            throw new AmetysRepositoryException("Unable to get multilingual String property", e);
521        }
522    }
523    
524    private String _getString(String metadataName, Locale locale, String defaultValue, boolean failIfNotExist)
525    {
526        try
527        {
528            Node node = _node.getNode(METADATA_PREFIX + metadataName);
529            
530            Property property = node.getProperty(locale.toString());
531            return property.getString();
532        }
533        catch (PathNotFoundException e)
534        {
535            if (failIfNotExist)
536            {
537                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
538            }
539            else
540            {
541                return defaultValue;
542            }
543        }
544        catch (RepositoryException e)
545        {
546            throw new AmetysRepositoryException("Unable to get String property", e);
547        }
548    }
549
550    public boolean getBoolean(String metadataName) throws UnknownMetadataException, AmetysRepositoryException
551    {
552        return _getBoolean(metadataName, null, true);
553    }
554
555    public boolean getBoolean(String metadataName, boolean defaultValue) throws AmetysRepositoryException
556    {
557        return _getBoolean(metadataName, defaultValue, false);
558    }
559    
560    private boolean _getBoolean(String metadataName, Boolean defaultValue, boolean failIfNotExist)
561    {
562        try
563        {
564            Property property = _node.getProperty(METADATA_PREFIX + metadataName);
565
566            if (property.getDefinition().isMultiple())
567            {
568                Value[] values = property.getValues();
569
570                if (values.length > 0)
571                {
572                    return values[0].getBoolean();
573                }
574                else
575                {
576                    throw new AmetysRepositoryException("Cannot get a boolean from an empty array");
577                }
578            }
579
580            return property.getBoolean();
581        }
582        catch (PathNotFoundException e)
583        {
584            if (failIfNotExist)
585            {
586                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
587            }
588            else
589            {
590                return defaultValue;
591            }
592        }
593        catch (RepositoryException e)
594        {
595            throw new AmetysRepositoryException("Unable to get boolean property", e);
596        }
597    }
598
599    public boolean[] getBooleanArray(String metadataName) throws AmetysRepositoryException
600    {
601        return _getBooleanArray(metadataName, null, true);
602    }
603
604    public boolean[] getBooleanArray(String metadataName, boolean[] defaultValues)
605    {
606        return _getBooleanArray(metadataName, defaultValues, false);
607    }
608
609    private boolean[] _getBooleanArray(String metadataName, boolean[] defaultValues, boolean failIfNotExist)
610    {
611        try
612        {
613            Property property = _node.getProperty(METADATA_PREFIX + metadataName);
614
615            if (property.getDefinition().isMultiple())
616            {
617                Value[] values = property.getValues();
618
619                boolean[] results = new boolean[values.length];
620                for (int i = 0; i < values.length; i++)
621                {
622                    Value value = values[i];
623                    results[i] = value.getBoolean();
624                }
625
626                return results;
627            }
628
629            Value value = property.getValue();
630            return new boolean[] {value.getBoolean()};
631        }
632        catch (PathNotFoundException e)
633        {
634            if (failIfNotExist)
635            {
636                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
637            }
638            else
639            {
640                return defaultValues;
641            }
642        }
643        catch (RepositoryException e)
644        {
645            throw new AmetysRepositoryException("Unable to get boolean array property", e);
646        }
647    }
648
649    public Date getDate(String metadataName) throws AmetysRepositoryException
650    {
651        return _getDate(metadataName, null, true);
652    }
653
654    public Date getDate(String metadataName, Date defaultValue) throws AmetysRepositoryException
655    {
656        return _getDate(metadataName, defaultValue, false);
657    }
658
659    private Date _getDate(String metadataName, Date defaultValue, boolean failIfNotExist)
660    {
661        try
662        {
663            Property property = _node.getProperty(METADATA_PREFIX + metadataName);
664
665            if (property.getDefinition().isMultiple())
666            {
667                Value[] values = property.getValues();
668
669                if (values.length > 0)
670                {
671                    return values[0].getDate().getTime();
672                }
673                else
674                {
675                    throw new AmetysRepositoryException("Cannot get a date from an empty array");
676                }
677            }
678
679            return property.getDate().getTime();
680        }
681        catch (PathNotFoundException e)
682        {
683            if (failIfNotExist)
684            {
685                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
686            }
687            else
688            {
689                return defaultValue;
690            }
691        }
692        catch (RepositoryException e)
693        {
694            throw new AmetysRepositoryException("Unable to get date property", e);
695        }
696    }
697
698    public Date[] getDateArray(String metadataName) throws AmetysRepositoryException
699    {
700        return _getDateArray(metadataName, null, true);
701    }
702
703    public Date[] getDateArray(String metadataName, Date[] defaultValues)
704    {
705        return _getDateArray(metadataName, defaultValues, false);
706    }
707
708    private Date[] _getDateArray(String metadataName, Date[] defaultValues, boolean failIfNotExist)
709    {
710        try
711        {
712            Property property = _node.getProperty(METADATA_PREFIX + metadataName);
713
714            if (property.getDefinition().isMultiple())
715            {
716                Value[] values = property.getValues();
717
718                Date[] results = new Date[values.length];
719                for (int i = 0; i < values.length; i++)
720                {
721                    Value value = values[i];
722                    results[i] = value.getDate().getTime();
723                }
724
725                return results;
726            }
727
728            Value value = property.getValue();
729            return new Date[] {value.getDate().getTime()};
730        }
731        catch (PathNotFoundException e)
732        {
733            if (failIfNotExist)
734            {
735                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
736            }
737            else
738            {
739                return defaultValues;
740            }
741        }
742        catch (RepositoryException e)
743        {
744            throw new AmetysRepositoryException("Unable to get date array property", e);
745        }
746    }
747
748    public long getLong(String metadataName) throws AmetysRepositoryException
749    {
750        return _getLong(metadataName, null, true);
751    }
752
753    public long getLong(String metadataName, long defaultValue) throws AmetysRepositoryException
754    {
755        return _getLong(metadataName, defaultValue, false);
756    }
757
758    private long _getLong(String metadataName, Long defaultValue, boolean failIfNotExist)
759    {
760        try
761        {
762            Property property = _node.getProperty(METADATA_PREFIX + metadataName);
763
764            if (property.getDefinition().isMultiple())
765            {
766                Value[] values = property.getValues();
767
768                if (values.length > 0)
769                {
770                    return values[0].getLong();
771                }
772                else
773                {
774                    throw new AmetysRepositoryException("Cannot get a long from an empty array");
775                }
776            }
777
778            return property.getLong();
779        }
780        catch (PathNotFoundException e)
781        {
782            if (failIfNotExist)
783            {
784                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
785            }
786            else
787            {
788                return defaultValue;
789            }
790        }
791        catch (RepositoryException e)
792        {
793            throw new AmetysRepositoryException("Unable to get long property", e);
794        }
795    }
796
797    public long[] getLongArray(String metadataName) throws AmetysRepositoryException
798    {
799        return _getLongArray(metadataName, null, true);
800    }
801
802    public long[] getLongArray(String metadataName, long[] defaultValues)
803    {
804        return _getLongArray(metadataName, defaultValues, false);
805    }
806
807    private long[] _getLongArray(String metadataName, long[] defaultValues, boolean failIfNotExist)
808    {
809        try
810        {
811            Property property = _node.getProperty(METADATA_PREFIX + metadataName);
812
813            if (property.getDefinition().isMultiple())
814            {
815                Value[] values = property.getValues();
816
817                long[] results = new long[values.length];
818                for (int i = 0; i < values.length; i++)
819                {
820                    Value value = values[i];
821                    results[i] = value.getLong();
822                }
823
824                return results;
825            }
826
827            Value value = property.getValue();
828            return new long[] {value.getLong()};
829        }
830        catch (PathNotFoundException e)
831        {
832            if (failIfNotExist)
833            {
834                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
835            }
836            else
837            {
838                return defaultValues;
839            }
840        }
841        catch (RepositoryException e)
842        {
843            throw new AmetysRepositoryException("Unable to get long array property", e);
844        }
845    }
846
847    public double getDouble(String metadataName) throws AmetysRepositoryException
848    {
849        return _getDouble(metadataName, null, true);
850    }
851
852    public double getDouble(String metadataName, double defaultValue) throws AmetysRepositoryException
853    {
854        return _getDouble(metadataName, defaultValue, false);
855    }
856
857    private double _getDouble(String metadataName, Double defaultValue, boolean failIfNotExist)
858    {
859        try
860        {
861            Property property = _node.getProperty(METADATA_PREFIX + metadataName);
862
863            if (property.getDefinition().isMultiple())
864            {
865                Value[] values = property.getValues();
866
867                if (values.length > 0)
868                {
869                    return values[0].getDouble();
870                }
871                else
872                {
873                    throw new AmetysRepositoryException("Cannot get a double from an empty array");
874                }
875            }
876
877            return property.getDouble();
878        }
879        catch (PathNotFoundException e)
880        {
881            if (failIfNotExist)
882            {
883                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
884            }
885            else
886            {
887                return defaultValue;
888            }
889        }
890        catch (RepositoryException e)
891        {
892            throw new AmetysRepositoryException("Unable to get double property", e);
893        }
894    }
895
896    public double[] getDoubleArray(String metadataName) throws AmetysRepositoryException
897    {
898        return _getDoubleArray(metadataName, null, true);
899    }
900
901    public double[] getDoubleArray(String metadataName, double[] defaultValues)
902    {
903        return _getDoubleArray(metadataName, defaultValues, false);
904    }
905
906    private double[] _getDoubleArray(String metadataName, double[] defaultValues, boolean failIfNotExist)
907    {
908        try
909        {
910            Property property = _node.getProperty(METADATA_PREFIX + metadataName);
911
912            if (property.getDefinition().isMultiple())
913            {
914                Value[] values = property.getValues();
915
916                double[] results = new double[values.length];
917                for (int i = 0; i < values.length; i++)
918                {
919                    Value value = values[i];
920                    results[i] = value.getDouble();
921                }
922
923                return results;
924            }
925
926            Value value = property.getValue();
927            return new double[] {value.getDouble()};
928        }
929        catch (PathNotFoundException e)
930        {
931            if (failIfNotExist)
932            {
933                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
934            }
935            else
936            {
937                return defaultValues;
938            }
939        }
940        catch (RepositoryException e)
941        {
942            throw new AmetysRepositoryException("Unable to get double array property", e);
943        }
944    }
945    
946    public UserIdentity getUser(String metadataName) throws AmetysRepositoryException
947    {
948        return _getUser(metadataName, null, true);
949    }
950    
951    public UserIdentity getUser(String metadataName, UserIdentity defaultValue) throws AmetysRepositoryException
952    {
953        return _getUser(metadataName, defaultValue, false);
954    }
955    
956    private UserIdentity _getUser(String metadataName, UserIdentity defaultValue, boolean failIfNotExist) throws AmetysRepositoryException
957    {
958        try
959        {
960            Node userNode = _node.getNode(METADATA_PREFIX + metadataName);
961            return new UserIdentity(userNode.getProperty("ametys:login").getString(), userNode.getProperty("ametys:population").getString());
962        }
963        catch (PathNotFoundException e)
964        {
965            if (failIfNotExist)
966            {
967                throw new UnknownMetadataException("Unknown metadata " + METADATA_PREFIX + metadataName + " of " + _node, e);
968            }
969            else
970            {
971                return defaultValue;
972            }
973        }
974        catch (RepositoryException e)
975        {
976            throw new AmetysRepositoryException("Unable to get User metadata", e);
977        }
978    }
979    
980    @Override
981    public UserIdentity[] getUserArray(String metadataName) throws AmetysRepositoryException
982    {
983        return _getUserArray(metadataName, null, true);
984    }
985    
986    @Override
987    public UserIdentity[] getUserArray(String metadataName, UserIdentity[] defaultValues) throws AmetysRepositoryException
988    {
989        return _getUserArray(metadataName, defaultValues, false);
990    }
991    
992    private UserIdentity[] _getUserArray(String metadataName, UserIdentity[] defaultValues, boolean failIfNotExist) throws AmetysRepositoryException
993    {
994        try
995        {
996            if (_node.hasNode(METADATA_PREFIX + metadataName))
997            {
998                List<UserIdentity> results = new ArrayList<>();
999                
1000                NodeIterator it = _node.getNodes(METADATA_PREFIX + metadataName);
1001                while (it.hasNext())
1002                {
1003                    Node next = (Node) it.next();
1004                    results.add(new UserIdentity(next.getProperty("ametys:login").getString(), next.getProperty("ametys:population").getString()));
1005                }
1006                
1007                return results.toArray(new UserIdentity[results.size()]);
1008            }
1009            else if (failIfNotExist)
1010            {
1011                throw new UnknownMetadataException("The metadata '" + metadataName + "' does not exist");
1012            }
1013            else
1014            {
1015                return defaultValues;
1016            }
1017        }
1018        catch (RepositoryException e)
1019        {
1020            throw new AmetysRepositoryException("Unable to get User array metadata", e);
1021        }
1022    }
1023
1024    @Override
1025    public void setMetadata(String metadataName, String value) throws AmetysRepositoryException
1026    {
1027        if (value == null)
1028        {
1029            throw new NullPointerException("metadata value cannot be null");
1030        }
1031
1032        _checkMetadataName(metadataName);
1033
1034        try
1035        {
1036            _checkLock();
1037            _node.setProperty(METADATA_PREFIX + metadataName, value);
1038        }
1039        catch (RepositoryException e)
1040        {
1041            throw new AmetysRepositoryException("Unable to set property", e);
1042        }
1043    }
1044
1045    @Override
1046    public void setMetadata(String metadataName, String[] value) throws AmetysRepositoryException
1047    {
1048        if (value == null)
1049        {
1050            throw new NullPointerException("metadata value cannot be null");
1051        }
1052
1053        _checkMetadataName(metadataName);
1054
1055        try
1056        {
1057            _checkLock();
1058            _node.setProperty(METADATA_PREFIX + metadataName, value);
1059        }
1060        catch (RepositoryException e)
1061        {
1062            throw new AmetysRepositoryException("Unable to set property", e);
1063        }
1064    }
1065    
1066    @Override
1067    public void setMetadata(String metadataName, String value, Locale locale) throws AmetysRepositoryException
1068    {
1069        if (value == null)
1070        {
1071            throw new NullPointerException("metadata value cannot be null");
1072        }
1073
1074        _checkMetadataName(metadataName);
1075
1076        try
1077        {
1078            _checkLock();
1079            
1080            Node node = null;
1081            if (_node.hasNode(METADATA_PREFIX + metadataName))
1082            {
1083                node = _node.getNode(METADATA_PREFIX + metadataName);
1084            }
1085            else
1086            {
1087                node = _node.addNode(METADATA_PREFIX + metadataName, MULTILINGUAL_STRING_METADATA_NODETYPE);
1088            }
1089            
1090            node.setProperty(locale.toString(), value);
1091        }
1092        catch (RepositoryException e)
1093        {
1094            throw new AmetysRepositoryException("Unable to set property", e);
1095        }
1096    }
1097
1098    @Override
1099    public void setMetadata(String metadataName, Date value) throws AmetysRepositoryException
1100    {
1101        if (value == null)
1102        {
1103            throw new NullPointerException("metadata value cannot be null");
1104        }
1105
1106        GregorianCalendar calendar = new GregorianCalendar();
1107        calendar.setTime(value);
1108
1109        _checkMetadataName(metadataName);
1110
1111        try
1112        {
1113            _checkLock();
1114            _node.setProperty(METADATA_PREFIX + metadataName, calendar);
1115        }
1116        catch (RepositoryException e)
1117        {
1118            throw new AmetysRepositoryException("Unable to set property", e);
1119        }
1120    }
1121
1122    @Override
1123    public void setMetadata(String metadataName, Date[] values) throws AmetysRepositoryException
1124    {
1125        if (values == null)
1126        {
1127            throw new NullPointerException("metadata value cannot be null");
1128        }
1129
1130        _checkMetadataName(metadataName);
1131
1132        try
1133        {
1134            _checkLock();
1135            Value[] jcrValues = new Value[values.length];
1136
1137            for (int i = 0; i < values.length; i++)
1138            {
1139                Date value = values[i];
1140
1141                GregorianCalendar calendar = new GregorianCalendar();
1142                calendar.setTime(value);
1143
1144                jcrValues[i] = _node.getSession().getValueFactory().createValue(calendar);
1145            }
1146
1147            _node.setProperty(METADATA_PREFIX + metadataName, jcrValues);
1148        }
1149        catch (RepositoryException e)
1150        {
1151            throw new AmetysRepositoryException("Unable to set property", e);
1152        }
1153    }
1154
1155    @Override
1156    public void setMetadata(String metadataName, long value) throws AmetysRepositoryException
1157    {
1158        _checkMetadataName(metadataName);
1159
1160        try
1161        {
1162            _checkLock();
1163            _node.setProperty(METADATA_PREFIX + metadataName, value);
1164        }
1165        catch (RepositoryException e)
1166        {
1167            throw new AmetysRepositoryException("Unable to set property", e);
1168        }
1169    }
1170
1171    @Override
1172    public void setMetadata(String metadataName, double value) throws AmetysRepositoryException
1173    {
1174        _checkMetadataName(metadataName);
1175
1176        try
1177        {
1178            _checkLock();
1179            _node.setProperty(METADATA_PREFIX + metadataName, value);
1180        }
1181        catch (RepositoryException e)
1182        {
1183            throw new AmetysRepositoryException("Unable to set property", e);
1184        }
1185    }
1186
1187    @Override
1188    public void setMetadata(String metadataName, long[] values) throws AmetysRepositoryException
1189    {
1190        if (values == null)
1191        {
1192            throw new NullPointerException("metadata value cannot be null");
1193        }
1194
1195        _checkMetadataName(metadataName);
1196
1197        try
1198        {
1199            _checkLock();
1200            Value[] jcrValues = new Value[values.length];
1201
1202            for (int i = 0; i < values.length; i++)
1203            {
1204                long value = values[i];
1205                jcrValues[i] = _node.getSession().getValueFactory().createValue(value);
1206            }
1207
1208            _node.setProperty(METADATA_PREFIX + metadataName, jcrValues);
1209        }
1210        catch (RepositoryException e)
1211        {
1212            throw new AmetysRepositoryException("Unable to set property", e);
1213        }
1214    }
1215
1216    @Override
1217    public void setMetadata(String metadataName, double[] values) throws AmetysRepositoryException
1218    {
1219        if (values == null)
1220        {
1221            throw new NullPointerException("metadata value cannot be null");
1222        }
1223
1224        _checkMetadataName(metadataName);
1225
1226        try
1227        {
1228            _checkLock();
1229            Value[] jcrValues = new Value[values.length];
1230
1231            for (int i = 0; i < values.length; i++)
1232            {
1233                double value = values[i];
1234                jcrValues[i] = _node.getSession().getValueFactory().createValue(value);
1235            }
1236
1237            _node.setProperty(METADATA_PREFIX + metadataName, jcrValues);
1238        }
1239        catch (RepositoryException e)
1240        {
1241            throw new AmetysRepositoryException("Unable to set property", e);
1242        }
1243    }
1244
1245    @Override
1246    public void setMetadata(String metadataName, boolean value) throws AmetysRepositoryException
1247    {
1248        _checkMetadataName(metadataName);
1249
1250        try
1251        {
1252            _checkLock();
1253            _node.setProperty(METADATA_PREFIX + metadataName, value);
1254        }
1255        catch (RepositoryException e)
1256        {
1257            throw new AmetysRepositoryException("Unable to set property", e);
1258        }
1259    }
1260    
1261    @Override
1262    public void setMetadata(String metadataName, boolean[] values) throws AmetysRepositoryException
1263    {
1264        if (values == null)
1265        {
1266            throw new NullPointerException("metadata value cannot be null");
1267        }
1268
1269        _checkMetadataName(metadataName);
1270
1271        try
1272        {
1273            _checkLock();
1274            Value[] jcrValues = new Value[values.length];
1275
1276            for (int i = 0; i < values.length; i++)
1277            {
1278                boolean value = values[i];
1279                jcrValues[i] = _node.getSession().getValueFactory().createValue(value);
1280            }
1281
1282            _node.setProperty(METADATA_PREFIX + metadataName, jcrValues);
1283        }
1284        catch (RepositoryException e)
1285        {
1286            throw new AmetysRepositoryException("Unable to set property", e);
1287        }
1288    }
1289    
1290    @Override
1291    public void setMetadata(String metadataName, UserIdentity value) throws AmetysRepositoryException
1292    {
1293        _checkMetadataName(metadataName);
1294        
1295        try
1296        {
1297            _checkLock();
1298            
1299            Node userNode = null;
1300            if (_node.hasNode(METADATA_PREFIX + metadataName))
1301            {
1302                userNode = _node.getNode(METADATA_PREFIX + metadataName);
1303            }
1304            else
1305            {
1306                userNode = _node.addNode(METADATA_PREFIX + metadataName, RepositoryConstants.USER_NODETYPE);
1307            }
1308            userNode.setProperty(METADATA_PREFIX + "login", value.getLogin());
1309            userNode.setProperty(METADATA_PREFIX + "population", value.getPopulationId());
1310        }
1311        catch (RepositoryException e)
1312        {
1313            throw new AmetysRepositoryException("Unable to set property", e);
1314        }
1315    }
1316    
1317    @Override
1318    public void setMetadata(String metadataName, UserIdentity[] values) throws AmetysRepositoryException
1319    {
1320        if (values == null)
1321        {
1322            throw new NullPointerException("metadata value cannot be null");
1323        }
1324
1325        _checkMetadataName(metadataName);
1326
1327        try
1328        {
1329            _checkLock();
1330            
1331            NodeIterator it = _node.getNodes(METADATA_PREFIX + metadataName);
1332            while (it.hasNext())
1333            {
1334                Node next = (Node) it.next();
1335                next.remove();
1336            }
1337            
1338            for (UserIdentity value : values)
1339            {
1340                Node userNode = _node.addNode(METADATA_PREFIX + metadataName, RepositoryConstants.USER_NODETYPE);
1341                userNode.setProperty(METADATA_PREFIX + "login", value.getLogin());
1342                userNode.setProperty(METADATA_PREFIX + "population", value.getPopulationId());
1343            }
1344        }
1345        catch (RepositoryException e)
1346        {
1347            throw new AmetysRepositoryException("Unable to set property", e);
1348        }
1349    }
1350    
1351    @Override
1352    public void setMetadata(String metadataName, MultilingualString value) throws AmetysRepositoryException
1353    {
1354        if (value == null)
1355        {
1356            throw new NullPointerException("metadata value cannot be null");
1357        }
1358        
1359        _checkMetadataName(metadataName);
1360
1361        try
1362        {
1363            _checkLock();
1364            
1365            NodeIterator it = _node.getNodes(METADATA_PREFIX + metadataName);
1366            while (it.hasNext())
1367            {
1368                Node next = (Node) it.next();
1369                next.remove();
1370            }
1371            
1372            Node node = null;
1373            if (_node.hasNode(METADATA_PREFIX + metadataName))
1374            {
1375                node = _node.getNode(METADATA_PREFIX + metadataName);
1376            }
1377            else
1378            {
1379                node = _node.addNode(METADATA_PREFIX + metadataName, MULTILINGUAL_STRING_METADATA_NODETYPE);
1380            }
1381            
1382            for (Locale locale : value.getLocales())
1383            {
1384                node.setProperty(locale.toString(), value.getValue(locale));
1385            }
1386        }
1387        catch (RepositoryException e)
1388        {
1389            throw new AmetysRepositoryException("Unable to set property", e);
1390        }
1391    }
1392
1393    public String[] getMetadataNames() throws AmetysRepositoryException
1394    {
1395        try
1396        {
1397            PropertyIterator itProp = _node.getProperties(METADATA_PREFIX + "*");
1398            NodeIterator itNode = _node.getNodes(METADATA_PREFIX + "*");
1399
1400            Set<String> names = new HashSet<>();
1401
1402            while (itProp.hasNext())
1403            {
1404                Property property = itProp.nextProperty();
1405                names.add(property.getName().substring(METADATA_PREFIX.length()));
1406            }
1407
1408            while (itNode.hasNext())
1409            {
1410                Node node = itNode.nextNode();
1411                
1412                // Filtering out AmetysObjects: they're not metadata (except object collections and users).
1413                if (!NodeTypeHelper.isNodeType(node, AmetysObjectResolver.OBJECT_TYPE) || NodeTypeHelper.isNodeType(node, OBJECT_COLLECTION_METADATA_NODETYPE) || NodeTypeHelper.isNodeType(node, RepositoryConstants.USER_NODETYPE) || NodeTypeHelper.isNodeType(node, MULTILINGUAL_STRING_METADATA_NODETYPE))
1414                {
1415                    names.add(node.getName().substring(METADATA_PREFIX.length()));
1416                }
1417            }
1418
1419            String[] result = new String[names.size()];
1420            names.toArray(result);
1421
1422            return result;
1423        }
1424        catch (RepositoryException e)
1425        {
1426            throw new AmetysRepositoryException("Unable to get metadata names", e);
1427        }
1428    }
1429
1430    public boolean isMultiple(String metadataName) throws AmetysRepositoryException
1431    {
1432        try
1433        {
1434            String realMetadataName = METADATA_PREFIX + metadataName;
1435            
1436            if (_node.hasProperty(realMetadataName))
1437            {
1438                Property property = _node.getProperty(realMetadataName);
1439    
1440                if (property.getDefinition().isMultiple())
1441                {
1442                    return true;
1443                }
1444    
1445                return false;
1446            }
1447            
1448            if (_node.hasNode(realMetadataName))
1449            {
1450                String nodeType = NodeTypeHelper.getNodeTypeName(_node.getNode(realMetadataName));
1451                
1452                // Object collection is multi-valued.
1453                if (OBJECT_COLLECTION_METADATA_NODETYPE.equals(nodeType))
1454                {
1455                    return true;
1456                }
1457                
1458                // User metadata
1459                if (nodeType.equals(METADATA_PREFIX + "user"))
1460                {
1461                    return _node.getNodes(realMetadataName).getSize() > 1;
1462                }
1463                
1464                // All other complex metadata are single valued.
1465                return false;
1466            }
1467            
1468            throw new UnknownMetadataException("No metadata for name: " + metadataName);
1469        }
1470        catch (RepositoryException e)
1471        {
1472            throw new AmetysRepositoryException("Unable to determine if metadata " + metadataName + " is multiple", e);
1473        }
1474    }
1475
1476    /**
1477     * Check if the metadata name is valid.
1478     * @param metadataName The metadata name to check.
1479     * @throws AmetysRepositoryException If the metadata name is invalid.
1480     */
1481    protected void _checkMetadataName(String metadataName) throws AmetysRepositoryException
1482    {
1483        // Id null
1484        if (metadataName == null)
1485        {
1486            throw new AmetysRepositoryException("Invalid metadata name (null)");
1487        }
1488
1489        // Id valide
1490        if (!_getMetadataNamePattern().matcher(metadataName).matches())
1491        {
1492            throw new AmetysRepositoryException("Invalid metadata name '" + metadataName + "'. This value is not permitted: only [a-zA-Z][a-zA-Z0-9-_]* are allowed.");
1493        }
1494    }
1495
1496    /**
1497     * Get the metadata name pattern to test validity.
1498     * @return The metadata name pattern.
1499     */
1500    protected static Pattern _getMetadataNamePattern()
1501    {
1502        if (__metadataNamePattern == null)
1503        {
1504            // [a-zA-Z][a-zA-Z0-9_]*
1505            __metadataNamePattern = Pattern.compile("[a-z][a-z0-9-_]*", Pattern.CASE_INSENSITIVE);
1506        }
1507
1508        return __metadataNamePattern;
1509    }
1510
1511    public ModifiableBinaryMetadata getBinaryMetadata(String metadataName) throws AmetysRepositoryException
1512    {
1513        return getBinaryMetadata(metadataName, false);
1514    }
1515    
1516    public ModifiableBinaryMetadata getBinaryMetadata(String metadataName, boolean createNew) throws AmetysRepositoryException
1517    {
1518        try
1519        {
1520            if (_node.hasNode(METADATA_PREFIX + metadataName))
1521            {
1522                Node node = _node.getNode(METADATA_PREFIX + metadataName);
1523                return new JCRBinaryMetadata(node);
1524            }
1525            else if (createNew)
1526            {
1527                _checkLock();
1528                Node node = _node.addNode(METADATA_PREFIX + metadataName, METADATA_PREFIX + "binaryMetadata");
1529                return new JCRBinaryMetadata(node);
1530            }
1531            else
1532            {
1533                throw new UnknownMetadataException("No BinaryMetadata named " + metadataName);
1534            }
1535        }
1536        catch (RepositoryException e)
1537        {
1538            throw new AmetysRepositoryException(e);
1539        }
1540    }
1541
1542    public ModifiableCompositeMetadata getCompositeMetadata(String metadataName) throws AmetysRepositoryException
1543    {
1544        return getCompositeMetadata(metadataName, false);
1545    }
1546    
1547    public ModifiableCompositeMetadata getCompositeMetadata(String metadataName, boolean createNew) throws AmetysRepositoryException
1548    {
1549        try
1550        {
1551            if (_node.hasNode(METADATA_PREFIX + metadataName))
1552            {
1553                Node node = _node.getNode(METADATA_PREFIX + metadataName);
1554                return new JCRCompositeMetadata(node, _resolver);
1555            }
1556            else if (createNew)
1557            {
1558                _checkLock();
1559                Node node = _node.addNode(METADATA_PREFIX + metadataName, "ametys:compositeMetadata");
1560                return new JCRCompositeMetadata(node, _resolver);
1561            }
1562            else
1563            {
1564                throw new UnknownMetadataException("No CompositeMetadata named " + metadataName);
1565            }
1566        }
1567        catch (RepositoryException e)
1568        {
1569            throw new AmetysRepositoryException(e);
1570        }
1571    }
1572    
1573    public ModifiableRichText getRichText(String metadataName) throws UnknownMetadataException, AmetysRepositoryException
1574    {
1575        return getRichText(metadataName, false);
1576    }
1577    
1578    public ModifiableRichText getRichText(String metadataName, boolean createNew) throws UnknownMetadataException, AmetysRepositoryException
1579    {
1580        try
1581        {
1582            if (_node.hasNode(METADATA_PREFIX + metadataName))
1583            {
1584                Node node = _node.getNode(METADATA_PREFIX + metadataName);
1585                return new JCRRichText(node, _resolver);
1586            }
1587            else if (createNew)
1588            {
1589                _checkLock();
1590                Node node = _node.addNode(METADATA_PREFIX + metadataName, METADATA_PREFIX + "richText");
1591                return new JCRRichText(node, _resolver);
1592            }
1593            else
1594            {
1595                throw new UnknownMetadataException("No RichText named " + metadataName);
1596            }
1597        }
1598        catch (RepositoryException e)
1599        {
1600            throw new AmetysRepositoryException(e);
1601        }
1602    }
1603    
1604    public ModifiableTraversableAmetysObject getObjectCollection(String metadataName) throws AmetysRepositoryException
1605    {
1606        return getObjectCollection(metadataName, false);
1607    }
1608    
1609    public ModifiableTraversableAmetysObject getObjectCollection(String metadataName, boolean createNew) throws AmetysRepositoryException
1610    {
1611        try
1612        {
1613            if (_node.hasNode(METADATA_PREFIX + metadataName))
1614            {
1615                Node node = _node.getNode(METADATA_PREFIX + metadataName);
1616                return _resolver.resolve(node, false);
1617            }
1618            else if (createNew)
1619            {
1620                _checkLock();
1621                Node node = _node.addNode(METADATA_PREFIX + metadataName, OBJECT_COLLECTION_METADATA_NODETYPE);
1622                return _resolver.resolve(node, false);
1623            }
1624            else
1625            {
1626                throw new UnknownMetadataException("No object collection metadata named " + metadataName);
1627            }
1628        }
1629        catch (RepositoryException e)
1630        {
1631            throw new AmetysRepositoryException(e);
1632        }
1633    }
1634    
1635    @Override
1636    public List<MetadataComment> getComments(String metadataName)
1637    {
1638        List<MetadataComment> comments = new ArrayList<>();
1639        String commentsNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + metadataName + METADATA_COMMENTS_SUFFIX;
1640        
1641        try
1642        {
1643            if (_node.hasNode(commentsNodeName))
1644            {
1645                Node commentsNode = _node.getNode(commentsNodeName);
1646                NodeIterator iterator = commentsNode.getNodes();
1647                
1648                while (iterator.hasNext())
1649                {
1650                    Node node = iterator.nextNode();
1651                    
1652                    String text = node.getProperty(METADATA_PREFIX + "comment").getString();
1653                    Date date = node.getProperty(METADATA_PREFIX + "date").getDate().getTime();
1654                    String author = node.getProperty(METADATA_PREFIX + "author").getString();
1655                    
1656                    comments.add(new MetadataComment(text, date, author));
1657                }
1658            }
1659            
1660            return comments;
1661        }
1662        catch (RepositoryException e)
1663        {
1664            throw new AmetysRepositoryException("Unable to retrieves the comments for metadata : " + metadataName, e);
1665        }
1666    }
1667    
1668    @Override
1669    public boolean hasComments(String metadataName)
1670    {
1671        String commentsNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + metadataName + METADATA_COMMENTS_SUFFIX;
1672        
1673        try
1674        {
1675            if (_node.hasNode(commentsNodeName))
1676            {
1677                Node commentsNode = _node.getNode(commentsNodeName);
1678                return commentsNode.getNodes().hasNext();
1679            }
1680            
1681            return false;
1682        }
1683        catch (RepositoryException e)
1684        {
1685            throw new AmetysRepositoryException("Unable to determine if this node has at least one comment for metadata : " + metadataName, e);
1686        }
1687    }
1688
1689    @Override
1690    public boolean hasComment(String metadataName, int index)
1691    {
1692        String name = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + metadataName + METADATA_COMMENTS_SUFFIX + '/' + RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + index;
1693        try
1694        {
1695            return _node.hasNode(name);
1696        }
1697        catch (RepositoryException e)
1698        {
1699            throw new AmetysRepositoryException("Unable to determine if this node has a comment for metadata : " + metadataName, e);
1700        }
1701    }
1702
1703    @Override
1704    public void addComment(String metadataName, String text, String author, Date date)
1705    {
1706        if (text == null)
1707        {
1708            throw new NullPointerException("text of the comment cannot be null");
1709        }
1710        
1711        String commentsNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + metadataName + METADATA_COMMENTS_SUFFIX;
1712        
1713        try
1714        {
1715            _checkLock();
1716            if (!_node.hasNode(commentsNodeName))
1717            {
1718                _node.addNode(commentsNodeName, "ametys:compositeMetadata");
1719            }
1720            
1721            Node commentsNode = _node.getNode(commentsNodeName);
1722            String nodeName = null;
1723            int i = 0;
1724            
1725            // Loop over existing comments
1726            do
1727            {
1728                nodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + ++i;
1729            } while (commentsNode.hasNode(nodeName));
1730            
1731            // Add the new comment node at the end
1732            Node commentNode = commentsNode.addNode(nodeName, "ametys:compositeMetadata");
1733            
1734            GregorianCalendar calendar = new GregorianCalendar();
1735            if (date != null)
1736            {
1737                calendar.setTime(date);
1738            }
1739            
1740            commentNode.setProperty(METADATA_PREFIX + "comment", text);
1741            commentNode.setProperty(METADATA_PREFIX + "date", calendar);
1742            commentNode.setProperty(METADATA_PREFIX + "author", author);
1743        }
1744        catch (RepositoryException e)
1745        {
1746            throw new AmetysRepositoryException("Unable to add a comment for metadata : " + metadataName, e);
1747        }
1748    }
1749
1750    @Override
1751    public void editComment(String metadataName, int index, String text, String author, Date date)
1752    {
1753        if (text == null)
1754        {
1755            throw new NullPointerException("text of the comment cannot be null");
1756        }
1757        
1758        String commentNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + metadataName + METADATA_COMMENTS_SUFFIX + '/' + RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + index;
1759        
1760        try
1761        {
1762            _checkLock();
1763            Node commentNode = _node.getNode(commentNodeName);
1764            
1765            GregorianCalendar calendar = new GregorianCalendar();
1766            if (date != null)
1767            {
1768                calendar.setTime(date);
1769            }
1770            
1771            commentNode.setProperty(METADATA_PREFIX + "comment", text);
1772            commentNode.setProperty(METADATA_PREFIX + "date", calendar);
1773            commentNode.setProperty(METADATA_PREFIX + "author", author);
1774        }
1775        catch (RepositoryException e)
1776        {
1777            throw new AmetysRepositoryException("Unable to edit the comment at index +" + index + " for metadata : " + metadataName, e);
1778        }
1779    }
1780
1781    @Override
1782    public void deleteComment(String metadataName, int index)
1783    {
1784        String commentNodeName = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + metadataName + METADATA_COMMENTS_SUFFIX + '/' + RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + index;
1785        
1786        try
1787        {
1788            _checkLock();
1789            _node.getNode(commentNodeName).remove();
1790        }
1791        catch (RepositoryException e)
1792        {
1793            throw new AmetysRepositoryException("Unable to remove the comment at index +" + index + " for metadata : " + metadataName, e);
1794        }
1795    }
1796    
1797    private void _checkLock() throws RepositoryException
1798    {
1799        if (!_lockAlreadyChecked && _node.isLocked())
1800        {
1801            LockManager lockManager = _node.getSession().getWorkspace().getLockManager();
1802            
1803            Lock lock = lockManager.getLock(_node.getPath());
1804            Node lockHolder = lock.getNode();
1805            
1806            lockManager.addLockToken(lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString());
1807            _lockAlreadyChecked = true;
1808        }
1809    }
1810}