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