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