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