001/*
002 *  Copyright 2016 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.cms.content.external;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.util.Date;
021import java.util.Objects;
022
023import org.apache.commons.lang3.ArrayUtils;
024
025import org.ametys.cms.content.external.ExternalizableMetadataProvider.ExternalizableMetadataStatus;
026import org.ametys.core.user.UserIdentity;
027import org.ametys.plugins.repository.AmetysRepositoryException;
028import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
029import org.ametys.plugins.repository.TraversableAmetysObject;
030import org.ametys.plugins.repository.metadata.BinaryMetadata;
031import org.ametys.plugins.repository.metadata.CompositeMetadata;
032import org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType;
033import org.ametys.plugins.repository.metadata.ModifiableBinaryMetadata;
034import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
035import org.ametys.plugins.repository.metadata.ModifiableFile;
036import org.ametys.plugins.repository.metadata.ModifiableFolder;
037import org.ametys.plugins.repository.metadata.ModifiableResource;
038import org.ametys.plugins.repository.metadata.ModifiableRichText;
039import org.ametys.plugins.repository.metadata.RichText;
040import org.ametys.plugins.repository.metadata.UnknownMetadataException;
041
042/**
043 * Helper to get and set externalizable metadata of a content.
044 * An externalizable metadata is a tri-state metadata with:<br>
045 * <ul>
046 *    <li>the real value</li>
047 *    <li>the alternative value</li>
048 *    <li>the status, which represents the nature of real value : local or external</li>
049 * </ul>
050 *
051 */
052public final class ExternalizableMetadataHelper
053{
054    /** Suffix used for the alternative value */
055    public static final String ALTERNATIVE_SUFFIX = "__alt";
056    /** Suffix used for the status value */
057    public static final String STATUS_SUFFIX = "__status";
058    
059    private static final String __TEMP_SUFFIX = "__temp";
060    
061    private ExternalizableMetadataHelper ()
062    {
063        // Empty
064    }
065    
066    /**
067     * Set the external value of a metadata
068     * @param metadataHolder The parent composite metadata
069     * @param metadataName The metadata name
070     * @param extValue The external value
071     * @param forceExternalStatus Set to true to force new status to external. Set the false to not update the current status.
072     * @return true if changes were made
073     */
074    public static boolean setExternalMetadata (ModifiableCompositeMetadata metadataHolder, String metadataName, Object extValue, boolean forceExternalStatus)
075    {
076        boolean hasChanges = false;
077        
078        String oldStatus = metadataHolder.getString(metadataName + STATUS_SUFFIX, null);
079        if (forceExternalStatus || oldStatus == null)
080        {
081            setStatus(metadataHolder, metadataName, ExternalizableMetadataStatus.EXTERNAL);
082            hasChanges = true;
083        }
084        
085        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
086        switch (currentStatus)
087        {
088            case EXTERNAL:
089                if (oldStatus == null && metadataHolder.hasMetadata(metadataName))
090                {
091                    // The metadata has not externalizable until now, remove the real value
092                    metadataHolder.removeMetadata(metadataName);
093                    hasChanges = true;
094                }
095                else if (ExternalizableMetadataStatus.LOCAL.name().toLowerCase().equals(oldStatus))
096                {
097                    // If status has changed, move real value to alternative value
098                    hasChanges = _copyMetadata(metadataHolder, metadataName, metadataName + ALTERNATIVE_SUFFIX) || hasChanges;
099                }
100                hasChanges = _setMetadata(metadataHolder, metadataName, extValue) || hasChanges;
101                break;
102            case LOCAL:
103                if (ExternalizableMetadataStatus.EXTERNAL.name().toLowerCase().equals(oldStatus))
104                {
105                    // If status has changed, move alternative value to real value
106                    hasChanges = _copyMetadata(metadataHolder, metadataName + ALTERNATIVE_SUFFIX, metadataName) || hasChanges;
107                }
108                hasChanges = _setMetadata(metadataHolder, metadataName + ALTERNATIVE_SUFFIX, extValue) || hasChanges;
109                break;
110            default:
111                break;
112        }
113        
114        return hasChanges;
115    }
116    
117    /**
118     * Remove the metadata holding the external value if exists
119     * @param metadataHolder The parent composite metadata
120     * @param metadataName The metadata name to delete
121     * @return true if a remove was done
122     */
123    public static boolean removeExternalMetadataIfExists (ModifiableCompositeMetadata metadataHolder, String metadataName)
124    {
125        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
126        switch (currentStatus)
127        {
128            case EXTERNAL:
129                if (metadataHolder.hasMetadata(metadataName))
130                {
131                    metadataHolder.removeMetadata(metadataName);
132                    return true;
133                }
134                break;
135            default:
136                if (metadataHolder.hasMetadata(metadataName + ALTERNATIVE_SUFFIX))
137                {
138                    metadataHolder.removeMetadata(metadataName + ALTERNATIVE_SUFFIX);
139                    return true;
140                }
141                break;
142        }
143        return false;
144    }
145    
146    /**
147     * Set the status of an externalizable metadata
148     * @param metadataHolder The parent composite metadata
149     * @param metadataName The metadata name
150     * @param status The new status
151     */
152    public static void setStatus (ModifiableCompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
153    {
154        metadataHolder.setMetadata(metadataName + STATUS_SUFFIX, status.name().toLowerCase());
155    }
156    
157    /**
158     * Set the status of an externalizable metadata
159     * @param metadataHolder The parent composite metadata
160     * @param metadataName The metadata name
161     * @param status The new status
162     * @return true if changes were made
163     */
164    public static boolean updateStatus (ModifiableCompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
165    {
166        String oldStatus = metadataHolder.getString(metadataName + STATUS_SUFFIX, null);
167        
168        // If status has changed, local and external values should be inverted
169        if (!status.name().toLowerCase().equals(oldStatus))
170        {
171            // Set the new status
172            setStatus(metadataHolder, metadataName, status);
173            
174            // First copy the used metadata in temporary metadata if exists
175            _copyMetadata(metadataHolder, metadataName, metadataName + __TEMP_SUFFIX);
176            
177            // Then copy the alternative metadata to the used metadata if exists
178            _copyMetadata(metadataHolder, metadataName + ALTERNATIVE_SUFFIX, metadataName);
179            
180            // Finally copy the temporary metadata to the alternative metadata if exists
181            _copyMetadata(metadataHolder, metadataName + __TEMP_SUFFIX, metadataName + ALTERNATIVE_SUFFIX);
182            
183            if (metadataHolder.hasMetadata(metadataName + __TEMP_SUFFIX))
184            {
185                metadataHolder.removeMetadata(metadataName + __TEMP_SUFFIX);
186            }
187            
188            return true;
189        }
190        return false;
191        
192    }
193    
194    /**
195     * Remove the metadata holding the local value if exists
196     * @param metadataHolder The parent composite metadata
197     * @param metadataName The metadata name to delete
198     * @return true if a remove was done
199     */
200    public static boolean removeLocalMetadataIfExists (ModifiableCompositeMetadata metadataHolder, String metadataName)
201    {
202        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
203        switch (currentStatus)
204        {
205            case EXTERNAL:
206                if (metadataHolder.hasMetadata(metadataName + ALTERNATIVE_SUFFIX))
207                {
208                    metadataHolder.removeMetadata(metadataName + ALTERNATIVE_SUFFIX);
209                    return true;
210                }
211                break;
212            default:
213                if (metadataHolder.hasMetadata(metadataName))
214                {
215                    metadataHolder.removeMetadata(metadataName);
216                    return true;
217                }
218                break;
219        }
220        return false;
221    }
222    
223    /**
224     * Set the local value and the status of an externalizable metadata
225     * @param metadataHolder The parent composite metadata
226     * @param metadataName The metadata name
227     * @param localValue The local value of metadata
228     * @param status The status of metadata
229     */
230    public static void setLocalMetadata (ModifiableCompositeMetadata metadataHolder, String metadataName, Object localValue, ExternalizableMetadataStatus status)
231    {
232        String oldStatus = metadataHolder.getString(metadataName + STATUS_SUFFIX, null);
233        setStatus(metadataHolder, metadataName, status);
234        
235        switch (status)
236        {
237            case EXTERNAL:
238                if (oldStatus == null && metadataHolder.hasMetadata(metadataName))
239                {
240                    // The metadata has not externalizable until now, remove the real value
241                    metadataHolder.removeMetadata(metadataName);
242                }
243                else if (ExternalizableMetadataStatus.LOCAL.name().toLowerCase().equals(oldStatus))
244                {
245                    // If status has changed, move alternative value to real value
246                    _copyMetadata(metadataHolder, metadataName + ALTERNATIVE_SUFFIX, metadataName);
247                }
248                _setMetadata(metadataHolder, metadataName + ALTERNATIVE_SUFFIX, localValue);
249                break;
250            case LOCAL:
251                if (ExternalizableMetadataStatus.EXTERNAL.name().toLowerCase().equals(oldStatus))
252                {
253                    // If status has changed, move real value to alternative value
254                    _copyMetadata(metadataHolder, metadataName, metadataName + ALTERNATIVE_SUFFIX);
255                }
256                _setMetadata(metadataHolder, metadataName, localValue);
257                break;
258            default:
259                break;
260        }
261    }
262    
263    /**
264     * Set a composite metadata as local value and the status of an externalizable metadata
265     * @param metadataHolder The parent composite metadata
266     * @param metadataName The metadata name
267     * @param status The status of metadata
268     * @return The local composite metadata
269     */
270    public static ModifiableCompositeMetadata setLocalCompositeMetadata (ModifiableCompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
271    {
272        String oldStatus = metadataHolder.getString(metadataName + STATUS_SUFFIX, null);
273        setStatus(metadataHolder, metadataName, status);
274        
275        switch (status)
276        {
277            case EXTERNAL:
278                if (oldStatus == null && metadataHolder.hasMetadata(metadataName))
279                {
280                    // The metadata has not externalizable until now, remove the real value
281                    metadataHolder.removeMetadata(metadataName);
282                }
283                else if (ExternalizableMetadataStatus.LOCAL.name().toLowerCase().equals(oldStatus))
284                {
285                    // If status has changed, move alternative value to real value
286                    _copyCompositeMetadata(metadataHolder, metadataName + ALTERNATIVE_SUFFIX, metadataName);
287                }
288                return metadataHolder.getCompositeMetadata(metadataName + ALTERNATIVE_SUFFIX, true);
289            case LOCAL:
290                if (ExternalizableMetadataStatus.EXTERNAL.name().toLowerCase().equals(oldStatus))
291                {
292                    // If status has changed, move real value to alternative value
293                    _copyCompositeMetadata(metadataHolder, metadataName, metadataName + ALTERNATIVE_SUFFIX);
294                }
295                return metadataHolder.getCompositeMetadata(metadataName, true);
296            default:
297                return null;
298        }
299    }
300    
301    /**
302     * Set a binary metadata as local value and the status of an externalizable metadata
303     * @param metadataHolder The parent composite metadata
304     * @param metadataName The metadata name
305     * @param status The status of metadata
306     * @return The local binary metadata
307     */
308    public static ModifiableBinaryMetadata setLocalBinaryMetadata (ModifiableCompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
309    {
310        String oldStatus = metadataHolder.getString(metadataName + STATUS_SUFFIX, null);
311        setStatus(metadataHolder, metadataName, status);
312        
313        switch (status)
314        {
315            case EXTERNAL:
316                if (oldStatus == null && metadataHolder.hasMetadata(metadataName))
317                {
318                    // The metadata has not externalizable until now, remove the real value
319                    metadataHolder.removeMetadata(metadataName);
320                }
321                else if (ExternalizableMetadataStatus.LOCAL.name().toLowerCase().equals(oldStatus))
322                {
323                    // If status has changed, move alternative value to real value
324                    _copyBinaryMetadata(metadataHolder, metadataName + ALTERNATIVE_SUFFIX, metadataName);
325                }
326                return metadataHolder.getBinaryMetadata(metadataName + ALTERNATIVE_SUFFIX, true);
327            case LOCAL:
328                if (ExternalizableMetadataStatus.EXTERNAL.name().toLowerCase().equals(oldStatus))
329                {
330                    // If status has changed, move real value to alternative value
331                    _copyBinaryMetadata(metadataHolder, metadataName, metadataName + ALTERNATIVE_SUFFIX);
332                }
333                return metadataHolder.getBinaryMetadata(metadataName, true);
334            default:
335                return null;
336        }
337    }
338    
339    /**
340     * Set the status of a binary metadata. 
341     * If status has changed, the local and external values will be inverted. 
342     * @param metadataHolder The parent composite metadata
343     * @param metadataName The metadata name
344     * @param status The new status of metadata
345     */
346    public static void updateBinaryMetadataStatus (ModifiableCompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
347    {
348        String oldStatus = metadataHolder.getString(metadataName + STATUS_SUFFIX, null);
349        
350        // If status has changed, local and external values should be inverted
351        if (!status.name().toLowerCase().equals(oldStatus))
352        {
353            // Set the new status
354            setStatus(metadataHolder, metadataName, status);
355            
356            // First copy the used metadata in temporary metadata if exists
357            _copyBinaryMetadata(metadataHolder, metadataName, metadataName + __TEMP_SUFFIX);
358            
359            // Then copy the alternative metadata to the used metadata if exists
360            _copyBinaryMetadata(metadataHolder, metadataName + ALTERNATIVE_SUFFIX, metadataName);
361            
362            // Finally copy the temporary metadata to the alternative metadata if exists
363            _copyBinaryMetadata(metadataHolder, metadataName + __TEMP_SUFFIX, metadataName + ALTERNATIVE_SUFFIX);
364            
365            if (metadataHolder.hasMetadata(metadataName + __TEMP_SUFFIX))
366            {
367                metadataHolder.removeMetadata(metadataName + __TEMP_SUFFIX);
368            }
369        }
370    }
371    
372    /**
373     * Set the status of a composite metadata. 
374     * If status has changed, the local and external values will be inverted. 
375     * @param metadataHolder The parent composite metadata
376     * @param metadataName The metadata name
377     * @param status The new status of metadata
378     */
379    public static void updateCompositeMetadataStatus (ModifiableCompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
380    {
381        String oldStatus = metadataHolder.getString(metadataName + STATUS_SUFFIX, null);
382        
383        // If status has changed, local and external values should be inverted
384        if (!status.name().toLowerCase().equals(oldStatus))
385        {
386            // Set the new status
387            setStatus(metadataHolder, metadataName, status);
388            
389            // First copy the used metadata in temporary metadata if exists
390            _copyCompositeMetadata(metadataHolder, metadataName, metadataName + __TEMP_SUFFIX);
391            
392            // Then copy the alternative metadata to the used metadata if exists
393            _copyCompositeMetadata(metadataHolder, metadataName + ALTERNATIVE_SUFFIX, metadataName);
394            
395            // Finally copy the temporary metadata to the alternative metadata if exists
396            _copyCompositeMetadata(metadataHolder, metadataName + __TEMP_SUFFIX, metadataName + ALTERNATIVE_SUFFIX);
397            
398            if (metadataHolder.hasMetadata(metadataName + __TEMP_SUFFIX))
399            {
400                metadataHolder.removeMetadata(metadataName + __TEMP_SUFFIX);
401            }
402        }
403    }
404    
405    /**
406     * Set a richtext metadata as local value and the status of an externalizable metadata
407     * @param metadataHolder The parent composite metadata
408     * @param metadataName The metadata name
409     * @param status The status of metadata
410     * @return The local richtext metadata
411     */
412    public static ModifiableRichText setLocalRichTextMetadata (ModifiableCompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
413    {
414        String oldStatus = metadataHolder.getString(metadataName + STATUS_SUFFIX, null);
415        setStatus(metadataHolder, metadataName, status);
416        
417        switch (status)
418        {
419            case EXTERNAL:
420                if (oldStatus == null && metadataHolder.hasMetadata(metadataName))
421                {
422                    // The metadata has not externalizable until now, remove the real value
423                    metadataHolder.removeMetadata(metadataName);
424                }
425                else if (ExternalizableMetadataStatus.LOCAL.name().toLowerCase().equals(oldStatus))
426                {
427                    // If status has changed, move alternative value to real value
428                    _copyRichTextMetadata(metadataHolder, metadataName + ALTERNATIVE_SUFFIX, metadataName);
429                }
430                return metadataHolder.getRichText(metadataName + ALTERNATIVE_SUFFIX, true);
431            case LOCAL:
432                if (ExternalizableMetadataStatus.EXTERNAL.name().toLowerCase().equals(oldStatus))
433                {
434                    // If status has changed, move real value to alternative value
435                    _copyRichTextMetadata(metadataHolder, metadataName, metadataName + ALTERNATIVE_SUFFIX);
436                }
437                return metadataHolder.getRichText(metadataName, true);
438            default:
439                return null;
440        }
441    }
442    
443    /**
444     * Set the status of a {@link RichText} metadata. 
445     * If status has changed, the local and external values will be inverted. 
446     * @param metadataHolder The parent composite metadata
447     * @param metadataName The metadata name
448     * @param status The new status of metadata
449     */
450    public static void updateRichTextMetadataStatus (ModifiableCompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
451    {
452        String oldStatus = metadataHolder.getString(metadataName + STATUS_SUFFIX, null);
453        
454        // If status has changed, local and external values should be inverted
455        if (!status.name().toLowerCase().equals(oldStatus))
456        {
457            // Set the new status
458            setStatus(metadataHolder, metadataName, status);
459            
460            // First copy the used metadata in temporary metadata if exists
461            _copyRichTextMetadata(metadataHolder, metadataName, metadataName + __TEMP_SUFFIX);
462            
463            // Then copy the alternative metadata to the used metadata if exists
464            _copyRichTextMetadata(metadataHolder, metadataName + ALTERNATIVE_SUFFIX, metadataName);
465            
466            // Finally copy the temporary metadata to the alternative metadata if exists
467            _copyRichTextMetadata(metadataHolder, metadataName + __TEMP_SUFFIX, metadataName + ALTERNATIVE_SUFFIX);
468            
469            if (metadataHolder.hasMetadata(metadataName + __TEMP_SUFFIX))
470            {
471                metadataHolder.removeMetadata(metadataName + __TEMP_SUFFIX);
472            }
473        }
474    }
475    
476    /**
477     * Copy a externalizable metadata. The status, the in-use and alternative values will be copied.
478     * @param srCompositeMetadata The source metadata holder
479     * @param destCompositeMetadata The metadata holder of destination
480     * @param metadataName The metadata name
481     */
482    public static void copyExternalizableMetadata (ModifiableCompositeMetadata srCompositeMetadata, ModifiableCompositeMetadata destCompositeMetadata, String metadataName)
483    {
484        ExternalizableMetadataStatus status = getStatus(srCompositeMetadata, metadataName);
485        setStatus(destCompositeMetadata, metadataName, status);
486        
487        _copyMetadata(srCompositeMetadata, metadataName, destCompositeMetadata, metadataName);
488        _copyMetadata(srCompositeMetadata, metadataName + ALTERNATIVE_SUFFIX, destCompositeMetadata, metadataName);
489    }
490    
491    /**
492     * Remove the metadata, alternative value and status if exist
493     * @param metadataHolder The parent composite metadata
494     * @param metadataName The metadata name to delete
495     * @return true if a remove was done
496     */
497    public static boolean removeMetadataIfExists (ModifiableCompositeMetadata metadataHolder, String metadataName)
498    {
499        boolean hasChanges = false;
500        if (metadataHolder.hasMetadata(metadataName))
501        {
502            metadataHolder.removeMetadata(metadataName);
503            hasChanges = true;
504        }
505        hasChanges = _removeAltAndStatusMetadata(metadataHolder, metadataName) || hasChanges;
506        return hasChanges;
507    }
508    
509    /**
510     * Set the value of a metadata, removing the alternative value and status if exists.
511     * @param metadataHolder The parent composite metadata
512     * @param metadataName The metadata name
513     * @param value The value
514     * @return true if changes were made
515     */
516    public static boolean setMetadata (ModifiableCompositeMetadata metadataHolder, String metadataName, Object value)
517    {
518        boolean hasChanges = _setMetadata(metadataHolder, metadataName, value);
519        hasChanges = _removeAltAndStatusMetadata(metadataHolder, metadataName) || hasChanges;
520        return hasChanges;
521    }
522    
523    /**
524     * Get and create if necessary a RichText metadata, removing the alternative value and status if exists.
525     * @param metadataHolder The parent metadata holder
526     * @param metadataName The metadata name
527     * @return the rich text metadata
528     */
529    public static ModifiableRichText getRichTextMetadata (ModifiableCompositeMetadata metadataHolder, String metadataName)
530    {
531        _removeAltAndStatusMetadata(metadataHolder, metadataName);
532        return metadataHolder.getRichText(metadataName, true);
533    }
534    
535    /**
536     * Get and create if necessary a binary metadata, removing the alternative value and status if exists.
537     * @param metadataHolder The parent metadata holder
538     * @param metadataName The metadata name
539     * @return the binary metadata
540     */
541    public static ModifiableBinaryMetadata getBinaryMetadata (ModifiableCompositeMetadata metadataHolder, String metadataName)
542    {
543        _removeAltAndStatusMetadata(metadataHolder, metadataName);
544        return metadataHolder.getBinaryMetadata(metadataName, true);
545    }
546    
547    /**
548     * Get and create if necessary a composite metadata, removing the alternative value and status if exists.
549     * @param metadataHolder The parent metadata holder
550     * @param metadataName The metadata name
551     * @return the composite metadata
552     */
553    public static ModifiableCompositeMetadata getCompositeMetadata (ModifiableCompositeMetadata metadataHolder, String metadataName)
554    {
555        _removeAltAndStatusMetadata(metadataHolder, metadataName);
556        return metadataHolder.getCompositeMetadata(metadataName, true);
557    }
558    
559    /**
560     * Get and create if necessary a object collection metadata, removing the alternative value and status if exists.
561     * @param metadataHolder The parent metadata holder
562     * @param metadataName The metadata name
563     * @return the object collection metadata
564     */
565    public static ModifiableTraversableAmetysObject getObjectCollection (ModifiableCompositeMetadata metadataHolder, String metadataName)
566    {
567        _removeAltAndStatusMetadata(metadataHolder, metadataName);
568        return metadataHolder.getObjectCollection(metadataName, true);
569    }
570    
571    private static boolean _removeAltAndStatusMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName)
572    {
573        boolean hasChanges = false;
574        if (metadataHolder.hasMetadata(metadataName + ALTERNATIVE_SUFFIX))
575        {
576            metadataHolder.removeMetadata(metadataName + ALTERNATIVE_SUFFIX);
577            hasChanges = true;
578        }
579        if (metadataHolder.hasMetadata(metadataName + STATUS_SUFFIX))
580        {
581            metadataHolder.removeMetadata(metadataName + STATUS_SUFFIX);
582            hasChanges = true;
583        }
584        return hasChanges;
585    }
586    
587    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, Object value)
588    {
589        if (value instanceof String)
590        {
591            return _setMetadata(metadataHolder, metadataName, (String) value);
592        }
593        else if (value instanceof String[])
594        {
595            return _setMetadata(metadataHolder, metadataName, (String[]) value);
596        }
597        else if (value instanceof Date)
598        {
599            return _setMetadata(metadataHolder, metadataName, (Date) value);
600        }
601        else if (value instanceof Date[])
602        {
603            return _setMetadata(metadataHolder, metadataName, (Date[]) value);
604        }
605        else if (value instanceof Boolean)
606        {
607            return _setMetadata(metadataHolder, metadataName, (Boolean) value);
608        }
609        else if (value instanceof Boolean[])
610        {
611            return _setMetadata(metadataHolder, metadataName, (Boolean[]) value);
612        }
613        else if (value instanceof Double)
614        {
615            return _setMetadata(metadataHolder, metadataName, (Double) value);
616        }
617        else if (value instanceof Double[])
618        {
619            return _setMetadata(metadataHolder, metadataName, (Double[]) value);
620        }
621        else if (value instanceof Long)
622        {
623            return _setMetadata(metadataHolder, metadataName, (Long) value);
624        }
625        else if (value instanceof Long[])
626        {
627            return _setMetadata(metadataHolder, metadataName, (Long[]) value);
628        }
629        else if (value instanceof UserIdentity)
630        {
631            return _setMetadata(metadataHolder, metadataName, (UserIdentity) value);
632        }
633        else if (value instanceof UserIdentity[])
634        {
635            return _setMetadata(metadataHolder, metadataName, (UserIdentity[]) value);
636        }
637        else
638        {
639            throw new UnsupportedOperationException("The type of value " + value.getClass().getName() + " is not suppported");
640        }
641    }
642    
643    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, String value)
644    {
645        if (!value.equals(metadataHolder.getString(metadataName, null)))
646        {
647            metadataHolder.setMetadata(metadataName, value); 
648            return true;
649        }
650        return false;
651    }
652    
653    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, String[] values)
654    {
655        if (!Objects.deepEquals(values, metadataHolder.getStringArray(metadataName, null)))
656        {
657            metadataHolder.setMetadata(metadataName, values); 
658            return true;
659        }
660        return false;
661    }
662    
663    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, Date value)
664    {
665        if (!value.equals(metadataHolder.getDate(metadataName, null)))
666        {
667            metadataHolder.setMetadata(metadataName, value);
668            return true;
669        }
670        return false;
671    }
672    
673    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, Date[] values)
674    {
675        if (!Objects.deepEquals(values, metadataHolder.getDateArray(metadataName, null)))
676        {
677            metadataHolder.setMetadata(metadataName, values); 
678            return true;
679        }
680        return false;
681    }
682    
683    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, Boolean value)
684    {
685        if (!metadataHolder.hasMetadata(metadataName) || !value.equals(metadataHolder.getBoolean(metadataName)))
686        {
687            metadataHolder.setMetadata(metadataName, value);
688            return true;
689        }
690        return false;
691    }
692    
693    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, Boolean[] values)
694    {
695        if (!Objects.deepEquals(values, metadataHolder.getBooleanArray(metadataName, null)))
696        {
697            metadataHolder.setMetadata(metadataName, ArrayUtils.toPrimitive(values)); 
698            return true;
699        }
700        return false;
701    }
702    
703    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, Long value)
704    {
705        if (!metadataHolder.hasMetadata(metadataName) || !value.equals(metadataHolder.getLong(metadataName)))
706        {
707            metadataHolder.setMetadata(metadataName, value);
708            return true;
709        }
710        return false;
711    }
712    
713    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, Long[] values)
714    {
715        if (!Objects.deepEquals(values, metadataHolder.getLongArray(metadataName, null)))
716        {
717            metadataHolder.setMetadata(metadataName, ArrayUtils.toPrimitive(values)); 
718            return true;
719        }
720        return false;
721    }
722    
723    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, Double value)
724    {
725        if (!metadataHolder.hasMetadata(metadataName) || !value.equals(metadataHolder.getDouble(metadataName)))
726        {
727            metadataHolder.setMetadata(metadataName, value);
728            return true;
729        }
730        return false;
731    }
732    
733    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, Double[] values)
734    {
735        if (!Objects.deepEquals(values, metadataHolder.getDoubleArray(metadataName, null)))
736        {
737            metadataHolder.setMetadata(metadataName, ArrayUtils.toPrimitive(values)); 
738            return true;
739        }
740        return false;
741    }
742    
743    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, UserIdentity value)
744    {
745        if (!value.equals(metadataHolder.getUser(metadataName, null)))
746        {
747            metadataHolder.setMetadata(metadataName, value); 
748            return true;
749        }
750        return false;
751    }
752    
753    private static boolean _setMetadata(ModifiableCompositeMetadata metadataHolder, String metadataName, UserIdentity[] values)
754    {
755        if (!Objects.deepEquals(values, metadataHolder.getUserArray(metadataName, null)))
756        {
757            metadataHolder.setMetadata(metadataName, values); 
758            return true;
759        }
760        return false;
761    }
762    
763    private static void _copyCompositeMetadata (ModifiableCompositeMetadata metadataHolder, String srcMetadataName, String destMetadataName)
764    {
765        if (metadataHolder.hasMetadata(srcMetadataName))
766        {
767            // If the source metadata exists, copy it
768            ModifiableCompositeMetadata srcMetadata = metadataHolder.getCompositeMetadata(srcMetadataName);
769            ModifiableCompositeMetadata destMetadata = metadataHolder.getCompositeMetadata(destMetadataName, true);
770            
771            srcMetadata.copyTo(destMetadata);
772        }
773        else if (metadataHolder.hasMetadata(destMetadataName))
774        {
775            // If the source metadata does not exists, remove the metadata of destination (copy an unexisting metadata)
776            metadataHolder.removeMetadata(destMetadataName);
777        }
778    }
779    
780    private static void _copyBinaryMetadata (ModifiableCompositeMetadata metadataHolder, String srcMetadataName, String destMetadataName)
781    {
782        if (metadataHolder.hasMetadata(srcMetadataName))
783        {
784            // If the source metadata exists, copy it
785            ModifiableBinaryMetadata srcMetadata = metadataHolder.getBinaryMetadata(srcMetadataName);
786            ModifiableBinaryMetadata destMetadata = metadataHolder.getBinaryMetadata(destMetadataName, true);
787            
788            destMetadata.setFilename(srcMetadata.getFilename());
789            destMetadata.setMimeType(srcMetadata.getMimeType());
790            destMetadata.setEncoding(srcMetadata.getEncoding());
791            destMetadata.setLastModified(srcMetadata.getLastModified());
792            destMetadata.setInputStream(srcMetadata.getInputStream());
793        }
794        else if (metadataHolder.hasMetadata(destMetadataName))
795        {
796            // If the source metadata does not exists, remove the metadata of destination (copy an unexisting metadata)
797            metadataHolder.removeMetadata(destMetadataName);
798        }
799    }
800    
801    private static void _copyRichTextMetadata (ModifiableCompositeMetadata metadataHolder, String srcMetadataName, String destMetadataName)
802    {
803        if (metadataHolder.hasMetadata(srcMetadataName))
804        {
805            // If the source metadata exists, copy it
806            ModifiableRichText srcMetadata = metadataHolder.getRichText(srcMetadataName);
807            ModifiableRichText destMetadata = metadataHolder.getRichText(destMetadataName, true);
808            
809            destMetadata.setEncoding(srcMetadata.getEncoding());
810            destMetadata.setMimeType(srcMetadata.getMimeType());
811            destMetadata.setLastModified(srcMetadata.getLastModified());
812            destMetadata.setInputStream(srcMetadata.getInputStream());
813            
814            ModifiableFolder destDataFolder = destMetadata.getAdditionalDataFolder();
815            ModifiableFolder srcDataFolder = srcMetadata.getAdditionalDataFolder();
816            
817            for (ModifiableFile srcFile : srcDataFolder.getFiles())
818            {
819                ModifiableResource destFile = destDataFolder.addFile(srcFile.getName()).getResource();
820                destFile.setEncoding(srcFile.getResource().getEncoding());
821                destFile.setMimeType(srcFile.getResource().getMimeType());
822                destFile.setLastModified(srcFile.getResource().getLastModified());
823                destFile.setInputStream(srcFile.getResource().getInputStream());
824            }
825        }
826        else if (metadataHolder.hasMetadata(destMetadataName))
827        {
828            // If the source metadata does not exists, remove the metadata of destination (copy an unexisting metadata)
829            metadataHolder.removeMetadata(destMetadataName);
830        }
831    }
832    
833    private static boolean _copyMetadata (ModifiableCompositeMetadata metadataHolder, String srcMetadataName, String destMetadatName)
834    {
835        return _copyMetadata(metadataHolder, srcMetadataName, metadataHolder, destMetadatName);
836    }
837    
838    private static boolean _copyMetadata (ModifiableCompositeMetadata srcMetadataHolder, String srcMetadataName, ModifiableCompositeMetadata destMetadataHolder, String destMetadatName)
839    {
840        boolean hasChanges = false;
841        if (srcMetadataHolder.hasMetadata(srcMetadataName))
842        {
843            // If the source metadata exists, copy it
844            MetadataType type = srcMetadataHolder.getType(srcMetadataName);
845            boolean isMultiple = srcMetadataHolder.isMultiple(srcMetadataName);
846            hasChanges = true;
847            
848            switch (type)
849            {
850                case STRING:
851                    if (isMultiple)
852                    {
853                        destMetadataHolder.setMetadata(destMetadatName, srcMetadataHolder.getStringArray(srcMetadataName));
854                    }
855                    else
856                    {
857                        destMetadataHolder.setMetadata(destMetadatName, srcMetadataHolder.getString(srcMetadataName));
858                    }
859                    break;
860                case DATE:
861                    if (isMultiple)
862                    {
863                        destMetadataHolder.setMetadata(destMetadatName, srcMetadataHolder.getDateArray(srcMetadataName));
864                    }
865                    else
866                    {
867                        destMetadataHolder.setMetadata(destMetadatName, srcMetadataHolder.getDate(srcMetadataName));
868                    }
869                    break;
870                case DOUBLE:
871                    if (isMultiple)
872                    {
873                        destMetadataHolder.setMetadata(destMetadatName, srcMetadataHolder.getDoubleArray(srcMetadataName));
874                    }
875                    else
876                    {
877                        destMetadataHolder.setMetadata(destMetadatName, srcMetadataHolder.getDouble(srcMetadataName));
878                    }
879                    break;
880                case LONG:
881                    if (isMultiple)
882                    {
883                        destMetadataHolder.setMetadata(destMetadatName, srcMetadataHolder.getLongArray(srcMetadataName));
884                    }
885                    else
886                    {
887                        destMetadataHolder.setMetadata(destMetadatName, srcMetadataHolder.getLong(srcMetadataName));
888                    }
889                    break;
890                case BOOLEAN:
891                    if (isMultiple)
892                    {
893                        destMetadataHolder.setMetadata(destMetadatName, srcMetadataHolder.getBooleanArray(srcMetadataName));
894                    }
895                    else
896                    {
897                        destMetadataHolder.setMetadata(destMetadatName, srcMetadataHolder.getBoolean(srcMetadataName));
898                    }
899                    break;
900                case BINARY:
901                    ModifiableBinaryMetadata srcBinaryMetadata = srcMetadataHolder.getBinaryMetadata(srcMetadataName);
902                    ModifiableBinaryMetadata destBinaryMetadata = srcMetadataHolder.getBinaryMetadata(destMetadatName, true);
903                    
904                    _copyBinaryMetadata (srcBinaryMetadata, destBinaryMetadata);
905                    break;
906                case RICHTEXT:
907                    ModifiableRichText srcRichText = srcMetadataHolder.getRichText(srcMetadataName);
908                    ModifiableRichText destRichText = srcMetadataHolder.getRichText(destMetadatName, true);
909                    
910                    _copyRichText(srcRichText, destRichText);
911                    break;
912                case USER:
913                case COMPOSITE:
914                    ModifiableCompositeMetadata srcCompositeMeta = srcMetadataHolder.getCompositeMetadata(srcMetadataName);
915                    ModifiableCompositeMetadata destCompositeMeta = srcMetadataHolder.getCompositeMetadata(destMetadatName, true);
916                    
917                    srcCompositeMeta.copyTo(destCompositeMeta);
918                    break;
919                case OBJECT_COLLECTION:
920                    throw new UnsupportedOperationException("OBJECT_COLLECTION cannot be copied");
921                    /*ModifiableTraversableAmetysObject srcCollection = srcMetadataHolder.getObjectCollection(srcMetadataName);
922                    ModifiableTraversableAmetysObject destCollection = srcMetadataHolder.getObjectCollection(destMetadatName, true);
923                    
924                    _moveObjectCollection(srcCollection, destCollection);
925                    break;*/
926                default:
927                    break;
928            }
929        }
930        else if (destMetadataHolder.hasMetadata(destMetadatName))
931        {
932            // If the source metadata does not exists, remove the metadata of destination (copy an unexisting metadata)
933            destMetadataHolder.removeMetadata(destMetadatName);
934            hasChanges = true;
935        }
936        
937        return hasChanges;
938        
939    }
940    
941    /*private static void _moveObjectCollection (ModifiableTraversableAmetysObject srcCollection, ModifiableTraversableAmetysObject destCollection)
942    {
943        try (AmetysObjectIterable<AmetysObject> children = srcCollection.getChildren())
944        {
945            for (AmetysObject ao : children)
946            {
947                // TODO It will be great if content was a MoveableAmetysObject ...
948                // otherwise move JCR nodes ?
949            }
950        }
951    }*/
952    
953    private static void _copyBinaryMetadata(ModifiableBinaryMetadata srcBinaryMetadata, ModifiableBinaryMetadata destBinaryMetadata)
954    {
955        destBinaryMetadata.setEncoding(srcBinaryMetadata.getEncoding());
956        destBinaryMetadata.setFilename(srcBinaryMetadata.getFilename());
957        destBinaryMetadata.setMimeType(srcBinaryMetadata.getMimeType());
958        destBinaryMetadata.setLastModified(srcBinaryMetadata.getLastModified());
959        
960        try (InputStream is = srcBinaryMetadata.getInputStream())
961        {
962            destBinaryMetadata.setInputStream(is);
963        }
964        catch (IOException e)
965        {
966            // Ignore on close input stream
967        }
968    }
969    
970    private static void _copyRichText(ModifiableRichText srcRichText, ModifiableRichText destRichText)
971    {
972        destRichText.setEncoding(srcRichText.getEncoding());
973        destRichText.setMimeType(srcRichText.getMimeType());
974        destRichText.setLastModified(srcRichText.getLastModified());
975        
976        try (InputStream is = srcRichText.getInputStream())
977        {
978            destRichText.setInputStream(is);
979        }
980        catch (IOException e)
981        {
982            // Ignore on close input stream
983        }
984        
985        ModifiableFolder destDataFolder = destRichText.getAdditionalDataFolder();
986        ModifiableFolder srcDataFolder = srcRichText.getAdditionalDataFolder();
987        
988        for (ModifiableFile srcFile : srcDataFolder.getFiles())
989        {
990            ModifiableResource srcResource = srcFile.getResource();
991            
992            ModifiableResource destFile = destDataFolder.addFile(srcFile.getName()).getResource();
993            destFile.setEncoding(srcResource.getEncoding());
994            destFile.setMimeType(srcResource.getMimeType());
995            destFile.setLastModified(srcResource.getLastModified());
996            
997            try (InputStream is = srcResource.getInputStream())
998            {
999                destFile.setInputStream(srcResource.getInputStream());
1000            }
1001            catch (IOException e)
1002            {
1003             // Ignore on close input stream
1004            }
1005        }
1006    }
1007    
1008    // ---------------------------------------------
1009    //              GETTERS
1010    //----------------------------------------------
1011    /**
1012     * Get the current status of a externalizable metadata 
1013     * @param metadataHolder The parent composite metadata
1014     * @param metadataName The metadata name
1015     * @return The current status
1016     */
1017    public static ExternalizableMetadataStatus getStatus (CompositeMetadata metadataHolder, String metadataName)
1018    {
1019        String status = metadataHolder.getString(metadataName + STATUS_SUFFIX, "local");
1020        return ExternalizableMetadataStatus.valueOf(status.toUpperCase());
1021    }
1022    
1023    /**
1024     * Determine if the externalizable metadata exists.
1025     * @param metadataHolder The parent composite metadata
1026     * @param metadataName the metadataName to test.
1027     * @param status The status of the metadata to test
1028     * @return <code>true</code> if the given metadata exists, <code>false</code> otherwise.
1029     * @throws AmetysRepositoryException if an error occurs.
1030     */
1031    public static boolean hasMetadata (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1032    {
1033        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1034        if (currentStatus == status)
1035        {
1036            return metadataHolder.hasMetadata(metadataName);
1037        }
1038        else
1039        {
1040            return metadataHolder.hasMetadata(metadataName + ALTERNATIVE_SUFFIX);
1041        }
1042    }
1043    
1044    /**
1045     * Returns the type of metadata holding the value with the given status
1046     * @param metadataHolder The parent composite metadata
1047     * @param metadataName metadata name.
1048     * @return the type of the given metadata.
1049     * @param status The status of the metadata to test
1050     * @throws UnknownMetadataException if the named metadata does not exist.
1051     * @throws AmetysRepositoryException if an error occurs.
1052     */
1053    public static MetadataType getType (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1054    {
1055        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1056        if (currentStatus == status)
1057        {
1058            return metadataHolder.getType(metadataName);
1059        }
1060        else
1061        {
1062            return metadataHolder.getType(metadataName + ALTERNATIVE_SUFFIX);
1063        }
1064    }
1065    
1066    /**
1067     * Get the metadata name holding the value with the given status
1068     * @param metadataHolder The parent composite metadata
1069     * @param metadataName metadata name.
1070     * @param status The status of the metadata to test
1071     * @return The metadata name
1072     */
1073    public static String getMetadataName (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1074    {
1075        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1076        if (currentStatus == status)
1077        {
1078            return metadataName;
1079        }
1080        else
1081        {
1082            return metadataName + ALTERNATIVE_SUFFIX;
1083        }
1084    }
1085    
1086    /**
1087     * Returns the metadata value, local or external, as String.<br> 
1088     * If the metadata is multi-valued, one of the value is returned.<br>
1089     * If the metadata does not exist, an {@link UnknownMetadataException} is thrown.
1090     * @param metadataHolder The parent composite metadata
1091     * @param metadataName the metadata name.
1092     * @param status The status of the metadata to retrieve
1093     * @return the metadata value as String.
1094     * @throws UnknownMetadataException if the named metadata does not exist.
1095     * @throws AmetysRepositoryException if an error occurs.
1096     */
1097    public static String getString (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1098    {
1099        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1100        if (currentStatus == status)
1101        {
1102            return metadataHolder.getString(metadataName);
1103        }
1104        else
1105        {
1106            return metadataHolder.getString(metadataName + ALTERNATIVE_SUFFIX);
1107        }
1108    }
1109    
1110    /**
1111     * Returns the named metadata's value, local or external, as String array.
1112     * @param metadataHolder The parent composite metadata
1113     * @param metadataName the metadata name.
1114     * @param status The status of the metadata to retrieve
1115     * @return metadata value as String array.
1116     * @throws UnknownMetadataException if the named metadata does not exist.
1117     * @throws AmetysRepositoryException if an error occurs.
1118     */
1119    public static String[] getStringArray (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1120    {
1121        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1122        if (currentStatus == status)
1123        {
1124            return metadataHolder.getStringArray(metadataName);
1125        }
1126        else
1127        {
1128            return metadataHolder.getStringArray(metadataName + ALTERNATIVE_SUFFIX);
1129        }
1130    }
1131    
1132    /**
1133     * Returns the named metadata's value, local or external, as Date.
1134     * @param metadataHolder The parent composite metadata
1135     * @param metadataName the metadata name.
1136     * @param status The status of the metadata to retrieve
1137     * @return metadata value as Date.
1138     * @throws UnknownMetadataException if the named metadata does not exist.
1139     * @throws AmetysRepositoryException if an error occurs.
1140     */
1141    public static Date getDate (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1142    {
1143        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1144        if (currentStatus == status)
1145        {
1146            return metadataHolder.getDate(metadataName);
1147        }
1148        else
1149        {
1150            return metadataHolder.getDate(metadataName + ALTERNATIVE_SUFFIX);
1151        }
1152    }
1153    
1154    /**
1155     * Returns the named metadata's value, local or external, as Date array.
1156     * @param metadataHolder The parent composite metadata
1157     * @param metadataName the metadata name.
1158     * @param status The status of the metadata to retrieve
1159     * @return metadata value as Date array.
1160     * @throws UnknownMetadataException if the named metadata does not exist.
1161     * @throws AmetysRepositoryException if an error occurs.
1162     */
1163    public static Date[] getDateArray (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1164    {
1165        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1166        if (currentStatus == status)
1167        {
1168            return metadataHolder.getDateArray(metadataName);
1169        }
1170        else
1171        {
1172            return metadataHolder.getDateArray(metadataName + ALTERNATIVE_SUFFIX);
1173        }
1174    }
1175    
1176    /**
1177     * Returns the named metadata's value, local or external, as long.
1178     * @param metadataHolder The parent composite metadata
1179     * @param metadataName the metadata name.
1180     * @param status The status of the metadata to retrieve
1181     * @return metadata value as long.
1182     * @throws UnknownMetadataException if the named metadata does not exist.
1183     * @throws AmetysRepositoryException if an error occurs.
1184     */
1185    public static long getLong (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1186    {
1187        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1188        if (currentStatus == status)
1189        {
1190            return metadataHolder.getLong(metadataName);
1191        }
1192        else
1193        {
1194            return metadataHolder.getLong(metadataName + ALTERNATIVE_SUFFIX);
1195        }
1196    }
1197    
1198    /**
1199     * Returns the named metadata's value, local or external, as long array.
1200     * @param metadataHolder The parent composite metadata
1201     * @param metadataName the metadata name.
1202     * @param status The status of the metadata to retrieve
1203     * @return metadata value as long array.
1204     * @throws UnknownMetadataException if the named metadata does not exist.
1205     * @throws AmetysRepositoryException if an error occurs.
1206     */
1207    public static long[] getLongArray (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1208    {
1209        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1210        if (currentStatus == status)
1211        {
1212            return metadataHolder.getLongArray(metadataName);
1213        }
1214        else
1215        {
1216            return metadataHolder.getLongArray(metadataName + ALTERNATIVE_SUFFIX);
1217        }
1218    }
1219    
1220    /**
1221     * Returns the named metadata's value, local or external, as double.
1222     * @param metadataHolder The parent composite metadata
1223     * @param metadataName the metadata name.
1224     * @param status The status of the metadata to retrieve
1225     * @return metadata value as double.
1226     * @throws UnknownMetadataException if the named metadata does not exist.
1227     * @throws AmetysRepositoryException if an error occurs.
1228     */
1229    public static double getDouble (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1230    {
1231        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1232        if (currentStatus == status)
1233        {
1234            return metadataHolder.getDouble(metadataName);
1235        }
1236        else
1237        {
1238            return metadataHolder.getDouble(metadataName + ALTERNATIVE_SUFFIX);
1239        }
1240    }
1241    
1242    /**
1243     * Returns the named metadata's value, local or external, as double array.
1244     * @param metadataHolder The parent composite metadata
1245     * @param metadataName the metadata name.
1246     * @param status The status of the metadata to retrieve
1247     * @return metadata value as double array.
1248     * @throws UnknownMetadataException if the named metadata does not exist.
1249     * @throws AmetysRepositoryException if an error occurs.
1250     */
1251    public static double[] getDoubleArray (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1252    {
1253        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1254        if (currentStatus == status)
1255        {
1256            return metadataHolder.getDoubleArray(metadataName);
1257        }
1258        else
1259        {
1260            return metadataHolder.getDoubleArray(metadataName + ALTERNATIVE_SUFFIX);
1261        }
1262    }
1263    
1264    /**
1265     * Returns the named metadata's value, local or external, as boolean.
1266     * @param metadataHolder The parent composite metadata
1267     * @param metadataName the metadata name.
1268     * @param status The status of the metadata to retrieve
1269     * @return metadata value as boolean.
1270     * @throws UnknownMetadataException if the named metadata does not exist.
1271     * @throws AmetysRepositoryException if an error occurs.
1272     */
1273    public static boolean getBoolean (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1274    {
1275        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1276        if (currentStatus == status)
1277        {
1278            return metadataHolder.getBoolean(metadataName);
1279        }
1280        else
1281        {
1282            return metadataHolder.getBoolean(metadataName + ALTERNATIVE_SUFFIX);
1283        }
1284    }
1285    
1286    /**
1287     * Returns the named metadata's value, local or external, as boolean array.
1288     * @param metadataHolder The parent composite metadata
1289     * @param metadataName the metadata name.
1290     * @param status The status of the metadata to retrieve
1291     * @return metadata value as boolean array.
1292     * @throws UnknownMetadataException if the named metadata does not exist.
1293     * @throws AmetysRepositoryException if an error occurs.
1294     */
1295    public static boolean[] getBooleanArray (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1296    {
1297        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1298        if (currentStatus == status)
1299        {
1300            return metadataHolder.getBooleanArray(metadataName);
1301        }
1302        else
1303        {
1304            return metadataHolder.getBooleanArray(metadataName + ALTERNATIVE_SUFFIX);
1305        }
1306    }
1307    
1308    /**
1309     * Returns the named metadata's value, local or external, as {@link UserIdentity}.
1310     * @param metadataHolder The parent composite metadata
1311     * @param metadataName the metadata name.
1312     * @param status The status of the metadata to retrieve
1313     * @return metadata value as UserIdentity.
1314     * @throws UnknownMetadataException if the named metadata does not exist.
1315     * @throws AmetysRepositoryException if an error occurs.
1316     */
1317    public static UserIdentity getUser (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1318    {
1319        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1320        if (currentStatus == status)
1321        {
1322            return metadataHolder.getUser(metadataName);
1323        }
1324        else
1325        {
1326            return metadataHolder.getUser(metadataName + ALTERNATIVE_SUFFIX);
1327        }
1328    }
1329    
1330    /**
1331     * Returns the named metadata's value, local or external, as array of {@link UserIdentity} .
1332     * @param metadataHolder The parent composite metadata
1333     * @param metadataName the metadata name.
1334     * @param status The status of the metadata to retrieve
1335     * @return metadata value as UserIdentity.
1336     * @throws UnknownMetadataException if the named metadata does not exist.
1337     * @throws AmetysRepositoryException if an error occurs.
1338     */
1339    public static UserIdentity[] getUserArray (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1340    {
1341        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1342        if (currentStatus == status)
1343        {
1344            return metadataHolder.getUserArray(metadataName);
1345        }
1346        else
1347        {
1348            return metadataHolder.getUserArray(metadataName + ALTERNATIVE_SUFFIX);
1349        }
1350    }
1351    
1352    /**
1353     * Returns the named metadata's value, local or external, as {@link BinaryMetadata}.
1354     * @param metadataHolder The parent composite metadata
1355     * @param metadataName the metadata name.
1356     * @param status The status of the metadata to retrieve
1357     * @return metadata value as {@link BinaryMetadata}.
1358     * @throws UnknownMetadataException if the named metadata does not exist.
1359     * @throws AmetysRepositoryException if an error occurs.
1360     */
1361    public static BinaryMetadata getBinaryMetadata (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1362    {
1363        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1364        if (currentStatus == status)
1365        {
1366            return metadataHolder.getBinaryMetadata(metadataName);
1367        }
1368        else
1369        {
1370            return metadataHolder.getBinaryMetadata(metadataName + ALTERNATIVE_SUFFIX);
1371        }
1372    }
1373    
1374    /**
1375     * Returns the named metadata's value, local or external, as {@link BinaryMetadata}.
1376     * @param metadataHolder The parent composite metadata
1377     * @param metadataName the metadata name.
1378     * @param status The status of the metadata to retrieve
1379     * @param createNew <code>true</code> to create automatically the {@link BinaryMetadata} when missing.
1380     * @return metadata value as {@link BinaryMetadata}.
1381     * @throws UnknownMetadataException if the named metadata does not exist.
1382     * @throws AmetysRepositoryException if an error occurs.
1383     */
1384    public static ModifiableBinaryMetadata getBinaryMetadata (ModifiableCompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status, boolean createNew)
1385    {
1386        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1387        if (currentStatus == status)
1388        {
1389            return metadataHolder.getBinaryMetadata(metadataName, createNew);
1390        }
1391        else
1392        {
1393            return metadataHolder.getBinaryMetadata(metadataName + ALTERNATIVE_SUFFIX, createNew);
1394        }
1395    }
1396    
1397    /**
1398     * Returns the named metadata's value, local or external, as {@link RichText}.
1399     * @param metadataHolder The parent composite metadata
1400     * @param metadataName the metadata name.
1401     * @param status The status of the metadata to retrieve
1402     * @return metadata value as {@link RichText}.
1403     * @throws UnknownMetadataException if the named metadata does not exist.
1404     * @throws AmetysRepositoryException if an error occurs.
1405     */
1406    public static RichText getRichText (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1407    {
1408        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1409        if (currentStatus == status)
1410        {
1411            return metadataHolder.getRichText(metadataName);
1412        }
1413        else
1414        {
1415            return metadataHolder.getRichText(metadataName + ALTERNATIVE_SUFFIX);
1416        }
1417    }
1418    
1419    /**
1420     * Returns the named metadata's value, local or external, as {@link RichText}.
1421     * @param metadataHolder The parent composite metadata
1422     * @param metadataName the metadata name.
1423     * @param status The status of the metadata to retrieve
1424     * @param createNew <code>true</code> to create automatically the {@link RichText} when missing.
1425     * @return metadata value as {@link RichText}.
1426     * @throws UnknownMetadataException if the named metadata does not exist.
1427     * @throws AmetysRepositoryException if an error occurs.
1428     */
1429    public static ModifiableRichText getRichText (ModifiableCompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status, boolean createNew)
1430    {
1431        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1432        if (currentStatus == status)
1433        {
1434            return metadataHolder.getRichText(metadataName, createNew);
1435        }
1436        else
1437        {
1438            return metadataHolder.getRichText(metadataName + ALTERNATIVE_SUFFIX, createNew);
1439        }
1440    }
1441    
1442    /**
1443     * Returns the named metadata's value, local or external, as a {@link TraversableAmetysObject}.
1444     * @param metadataHolder The parent composite metadata
1445     * @param metadataName the metadata name.
1446     * @param status The status of the metadata to retrieve
1447     * @return metadata value as a {@link TraversableAmetysObject}.
1448     * @throws UnknownMetadataException if the named metadata does not exist.
1449     * @throws AmetysRepositoryException if an error occurs.
1450     */
1451    public static TraversableAmetysObject getObjectCollection (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1452    {
1453        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1454        if (currentStatus == status)
1455        {
1456            return metadataHolder.getObjectCollection(metadataName);
1457        }
1458        else
1459        {
1460            return metadataHolder.getObjectCollection(metadataName + ALTERNATIVE_SUFFIX);
1461        }
1462    }
1463    
1464    /**
1465     * Returns the named metadata's value, local or external, as a {@link TraversableAmetysObject}.
1466     * @param metadataHolder The parent composite metadata
1467     * @param metadataName the metadata name.
1468     * @param status The status of the metadata to retrieve
1469     * @return metadata value as a {@link TraversableAmetysObject}.
1470     * @throws UnknownMetadataException if the named metadata does not exist.
1471     * @throws AmetysRepositoryException if an error occurs.
1472     */
1473    public static CompositeMetadata getCompositeMetadata (CompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status)
1474    {
1475        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1476        if (currentStatus == status)
1477        {
1478            return metadataHolder.getCompositeMetadata(metadataName);
1479        }
1480        else
1481        {
1482            return metadataHolder.getCompositeMetadata(metadataName + ALTERNATIVE_SUFFIX);
1483        }
1484    }
1485    
1486    /**
1487     * Returns the named metadata's value, local or external, as a {@link CompositeMetadata}.
1488     * @param metadataHolder The parent composite metadata
1489     * @param metadataName the metadata name.
1490     * @param status The status of the metadata to retrieve
1491     * @param createNew <code>true</code> to create automatically the {@link CompositeMetadata} when missing.
1492     * @return metadata value as a {@link CompositeMetadata}.
1493     * @throws UnknownMetadataException if the named metadata does not exist.
1494     * @throws AmetysRepositoryException if an error occurs.
1495     */
1496    public static ModifiableCompositeMetadata getCompositeMetadata (ModifiableCompositeMetadata metadataHolder, String metadataName, ExternalizableMetadataStatus status, boolean createNew)
1497    {
1498        ExternalizableMetadataStatus currentStatus = getStatus(metadataHolder, metadataName);
1499        if (currentStatus == status)
1500        {
1501            return metadataHolder.getCompositeMetadata(metadataName, createNew);
1502        }
1503        else
1504        {
1505            return metadataHolder.getCompositeMetadata(metadataName + ALTERNATIVE_SUFFIX, createNew);
1506        }
1507    }
1508}