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