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