001/*
002 *  Copyright 2010 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.cms.contenttype;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Comparator;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.LinkedHashMap;
027import java.util.List;
028import java.util.Locale;
029import java.util.Map;
030import java.util.Set;
031import java.util.stream.Collectors;
032
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.cocoon.xml.AttributesImpl;
036import org.apache.cocoon.xml.XMLUtils;
037import org.apache.commons.io.IOUtils;
038import org.apache.commons.lang3.StringUtils;
039import org.xml.sax.ContentHandler;
040import org.xml.sax.SAXException;
041
042import org.ametys.cms.content.external.ExternalizableMetadataHelper;
043import org.ametys.cms.content.external.ExternalizableMetadataProvider.ExternalizableMetadataStatus;
044import org.ametys.cms.content.external.ExternalizableMetadataProviderExtensionPoint;
045import org.ametys.cms.repository.Content;
046import org.ametys.cms.transformation.RichTextTransformer;
047import org.ametys.cms.transformation.xslt.ResolveURIComponent;
048import org.ametys.core.user.UserIdentity;
049import org.ametys.core.util.DateUtils;
050import org.ametys.core.util.IgnoreRootHandler;
051import org.ametys.plugins.explorer.resources.Resource;
052import org.ametys.plugins.repository.AmetysObjectIterable;
053import org.ametys.plugins.repository.AmetysRepositoryException;
054import org.ametys.plugins.repository.TraversableAmetysObject;
055import org.ametys.plugins.repository.data.holder.DataHolder;
056import org.ametys.plugins.repository.metadata.BinaryMetadata;
057import org.ametys.plugins.repository.metadata.CommentableCompositeMetadata;
058import org.ametys.plugins.repository.metadata.CompositeMetadata;
059import org.ametys.plugins.repository.metadata.MetadataComment;
060import org.ametys.plugins.repository.metadata.MetadataSaxer;
061import org.ametys.plugins.repository.metadata.ModifiableRichText;
062import org.ametys.plugins.repository.metadata.MultilingualString;
063import org.ametys.plugins.repository.metadata.MultilingualStringHelper;
064import org.ametys.plugins.repository.metadata.RichText;
065import org.ametys.runtime.parameter.Enumerator;
066
067/**
068 * Component for helping SAXing metadata and especially metadata of a content.
069 * @deprecated Use {@link DataHolder} toSAX methods instead of this helper
070 */
071@Deprecated
072public class MetadataManager extends MetadataSaxer
073{
074    /** Avalon Role. */
075    @SuppressWarnings("hiding")
076    public static final String ROLE = MetadataManager.class.getName();
077    
078    /** A comparator for repeater entry names. */
079    public static final Comparator<String> REPEATER_ENTRY_COMPARATOR = new RepeaterEntryComparator();
080    
081    /** Content type extension point. */
082    protected ContentTypeExtensionPoint _contentTypeExtensionPoint;
083    /** Helper for content types */
084    protected ContentTypesHelper _contentTypesHelper;
085    /** The component to retrieve externalizable metadata */
086    protected ExternalizableMetadataProviderExtensionPoint _externalizableMetadataProviderEP;
087    
088    @Override
089    public void service(ServiceManager manager) throws ServiceException
090    {
091        super.service(manager);
092        _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
093        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
094        _externalizableMetadataProviderEP = (ExternalizableMetadataProviderExtensionPoint) manager.lookup(ExternalizableMetadataProviderExtensionPoint.ROLE);
095    }
096    
097    /**
098     * Get the local and external metadata
099     * @param content The content
100     * @return the externalizable metadata
101     */
102    protected Set<String> getExternalizableMetadata (Content content)
103    {
104        return _externalizableMetadataProviderEP.getExternalAndLocalMetadata(content);
105    }
106    
107    /**
108     * SAXes a composite metadata.
109     * @param contentHandler the content handler where to SAX into.
110     * @param content the content.
111     * @param compositeMetadata the composite metadata.
112     * @param metadataSet the metadata set to SAX or <code>null</code> for all metadata.
113     * @param metadataDefinition The metadata definition corresponding to the parameter compositeMetadata
114     * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string
115     * @param externalizableMetadata The externalizable (with local and external value) metadata
116     * @throws AmetysRepositoryException if an error occurs.
117     * @throws SAXException if an error occurs.
118     * @throws IOException if an error occurs.
119     */
120    public void saxMetadata(ContentHandler contentHandler, Content content, CompositeMetadata compositeMetadata, MetadataSet metadataSet, MetadataDefinition metadataDefinition, Locale defaultLocale, Set<String> externalizableMetadata) throws AmetysRepositoryException, SAXException, IOException
121    {
122        _saxMetadataSetElementOrAll(contentHandler, content, compositeMetadata, metadataSet, metadataDefinition, defaultLocale, "", false, metadataSet == null ? false : metadataSet.isEdition(), false, externalizableMetadata);
123    }
124    
125    /**
126     * SAXes a content metadata. This method does not check rights.
127     * @param contentHandler the content handler where to SAX into.
128     * @param content the content.
129     * @param metadataName the metadata name.
130     * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. Only use if initial content's language is not null. Can be null.
131     * @throws AmetysRepositoryException if an error occurs.
132     * @throws SAXException if an error occurs.
133     * @throws IOException if an error occurs.
134     */
135    public void saxMetadata(ContentHandler contentHandler, Content content, String metadataName, Locale defaultLocale) throws AmetysRepositoryException, SAXException, IOException
136    {
137        MetadataSet metadataSet = new MetadataSet();
138        MetadataDefinitionReference metaDefRef = new MetadataDefinitionReference(metadataName);
139        metadataSet.addElement(metaDefRef);
140        saxMetadata(contentHandler, content, metadataSet, defaultLocale);
141    }
142
143    /**
144     * SAX the content metadata if it is readable.
145     * @param contentHandler the content handler where to SAX into.
146     * @param content the content.
147     * @param metadataName the metadata name.
148     * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. Only use if initial content's language is not null. Can be null.
149     * @throws AmetysRepositoryException if an error occurs.
150     * @throws SAXException if an error occurs.
151     * @throws IOException if an error occurs.
152     */
153    public void saxReadableMetadata(ContentHandler contentHandler, Content content, String metadataName, Locale defaultLocale) throws AmetysRepositoryException, SAXException, IOException
154    {
155        MetadataSet metadataSet = new MetadataSet();
156        MetadataDefinitionReference metaDefRef = new MetadataDefinitionReference(metadataName);
157        metadataSet.addElement(metaDefRef);
158        saxReadableMetadata(contentHandler, content, metadataSet, defaultLocale);
159    }
160
161    
162    /**
163     * SAXes a content metadata set. This method does not check rights.
164     * @param contentHandler the content handler where to SAX into.
165     * @param content the content.
166     * @param metadataSet the metadata set.
167     * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. 
168     * Only to be valued if initial content's language is null, otherwise set this parameter to null.
169     * @throws AmetysRepositoryException if an error occurs.
170     * @throws SAXException if an error occurs.
171     * @throws IOException if an error occurs.
172     */
173    public void saxMetadata(ContentHandler contentHandler, Content content, MetadataSet metadataSet, Locale defaultLocale) throws AmetysRepositoryException, SAXException, IOException
174    {
175        _saxMetadataSetElementOrAll(contentHandler, content, content.getMetadataHolder(), metadataSet, null, defaultLocale, "", false, metadataSet.isEdition(), false, metadataSet.isEdition() ? getExternalizableMetadata(content) : new HashSet<>());
176    }
177    
178    /**
179     * SAXes the metadata comments of a content metadata set. This method does not check rights.
180     * @param contentHandler the content handler where to SAX into.
181     * @param content the content.
182     * @param metadataSet the metadata set.
183     * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. 
184     * Only to be valued if initial content's language is null, otherwise set this parameter to null.
185     * @throws AmetysRepositoryException if an error occurs.
186     * @throws SAXException if an error occurs.
187     * @throws IOException if an error occurs.
188     */
189    public void saxMetadataComments(ContentHandler contentHandler, Content content, MetadataSet metadataSet, Locale defaultLocale) throws AmetysRepositoryException, SAXException, IOException
190    {
191        _saxMetadataSetElementOrAll(contentHandler, content, content.getMetadataHolder(), metadataSet, null, defaultLocale, "", false, metadataSet.isEdition(), true, new HashSet<>());
192    }
193    
194    /**
195     * SAX a content metadata set that are readable.
196     * @param contentHandler the content handler where to SAX into.
197     * @param content the content.
198     * @param metadataSet the metadata set.
199     * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. 
200     * Only to be valued if initial content's language is null, otherwise set this parameter to null.
201     * @throws AmetysRepositoryException if an error occurs.
202     * @throws SAXException if an error occurs.
203     * @throws IOException if an error occurs.
204     */
205    public void saxReadableMetadata (ContentHandler contentHandler, Content content, MetadataSet metadataSet, Locale defaultLocale) throws AmetysRepositoryException, SAXException, IOException
206    {
207        _saxMetadataSetElementOrAll(contentHandler, content, content.getMetadataHolder(), metadataSet, null, defaultLocale, "", true, metadataSet.isEdition(), false, metadataSet.isEdition() ? getExternalizableMetadata(content) : new HashSet<>());
208    }
209    
210    private void _saxMetadataSetElementOrAll(ContentHandler contentHandler, Content content, CompositeMetadata metadata, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, Locale defaultLocale, String prefix, boolean checkRead, boolean editionRendering, boolean saxCommentsMode, Set<String> externalizableMetadata) throws AmetysRepositoryException, SAXException, IOException
211    {
212        if (metadataSetElement != null)
213        {
214            if (metadataSetElement instanceof MetadataDefinitionReference)
215            {
216                MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) metadataSetElement;
217                String subMetadataName = metadataDefRef.getMetadataName();
218                MetadataDefinition subMetadataDefinition = _getMetadataDefinition(content, metadataDefinition, subMetadataName);
219                
220                if (subMetadataDefinition != null)
221                {
222                    _saxMetadata(contentHandler, content, metadata, metadataDefRef, subMetadataDefinition, defaultLocale, prefix, checkRead, editionRendering, saxCommentsMode, externalizableMetadata);
223                }
224            }
225            else
226            {
227                for (AbstractMetadataSetElement subElement : metadataSetElement.getElements())
228                {
229                    _saxMetadataSetElementOrAll(contentHandler, content, metadata, subElement, metadataDefinition, defaultLocale, prefix, checkRead, editionRendering, saxCommentsMode, externalizableMetadata);
230                }
231            }
232        }
233        else
234        {
235            for (String metadataName : metadata.getMetadataNames())
236            {
237                MetadataDefinition childMetadataDefinition = _getMetadataDefinition(content, metadataDefinition, metadataName);
238                
239                if (childMetadataDefinition != null)
240                {
241                    _saxMetadata(contentHandler, content, metadata, null, childMetadataDefinition, defaultLocale, prefix, checkRead, editionRendering, saxCommentsMode, externalizableMetadata);
242                }
243            }
244        }
245    }
246
247    private MetadataDefinition _getMetadataDefinition(Content content, MetadataDefinition parentMetadataDefinition, String metadataName)
248    {
249        MetadataDefinition metadataDefinition = null;
250        
251        if (parentMetadataDefinition == null)
252        {
253            if (content != null)
254            {
255                metadataDefinition =  _contentTypesHelper.getMetadataDefinition(metadataName, content.getTypes(), content.getMixinTypes());
256            }
257        }
258        else
259        {
260            metadataDefinition = parentMetadataDefinition.getMetadataDefinition(metadataName);
261        }
262        
263        return metadataDefinition;
264    }
265
266    /**
267     * SAX a metadata
268     * @param contentHandler The content handler where to SAX into.
269     * @param content The content
270     * @param metadata The parent composite metadata.
271     * @param metadataSetElement The {@link MetadataSet} element.
272     * @param metadataDefinition The metadata definition
273     * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. Only use if initial content's language is not null. Can be null.
274     * @param prefix the metadata path prefix.
275     * @param checkRead true if need to check read right
276     * @param editionRendering true if the context rendering is for edition
277     * @param saxCommentsMode if true the comments will be SAXed for this metadata instead of the metadata value.
278     * @param externalizableMetadata The externalizable (with local and external value) metadata
279     * @throws AmetysRepositoryException if an error occurred
280     * @throws SAXException if an error occurred while SAXing
281     * @throws IOException if an error occurred
282     */
283    protected void _saxMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadata, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, Locale defaultLocale, String prefix, boolean checkRead, boolean editionRendering, boolean saxCommentsMode, Set<String> externalizableMetadata) throws AmetysRepositoryException, SAXException, IOException
284    {
285        if (saxCommentsMode)
286        {
287            _saxMetadataComments(contentHandler, content, metadata, metadataSetElement, metadataDefinition, defaultLocale, prefix, checkRead, editionRendering, externalizableMetadata);
288        }
289        else
290        {
291            boolean isExternalizable = editionRendering && externalizableMetadata.contains(metadataDefinition.getId());
292            if (isExternalizable)
293            {
294                _saxExternalisableMetadata(contentHandler, content, metadata, metadataSetElement, metadataDefinition, defaultLocale, prefix, checkRead, editionRendering, saxCommentsMode, externalizableMetadata);
295            }
296            else
297            {
298                _saxNoExternalisableMetadata(contentHandler, content, metadata, metadataSetElement, metadataDefinition, defaultLocale, prefix, checkRead, editionRendering, saxCommentsMode, externalizableMetadata);
299            }
300        }
301    }
302    
303    /**
304     * SAX a non-externalizable metadata
305     * @param contentHandler The content handler where to SAX into.
306     * @param content The content
307     * @param metadata The parent composite metadata.
308     * @param metadataSetElement The {@link MetadataSet} element.
309     * @param metadataDefinition The metadata definition
310     * @param defaultLocale The locale to use to resolve localized values such as multilingual string or multilingual contents. Only use if the content's language is null.
311     * @param prefix the metadata path prefix.
312     * @param checkRead true if need to check read right
313     * @param editionRendering true if the context rendering is for edition
314     * @param saxCommentsMode if true the comments will be SAXed for this metadata instead of the metadata value.
315     * @param externalizableMetadata The externalizable (with local and external value) metadata
316     * @throws AmetysRepositoryException if an error occurred
317     * @throws SAXException if an error occurred while SAXing
318     * @throws IOException if an error occurred
319     */
320    protected void _saxNoExternalisableMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadata, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, Locale defaultLocale, String prefix, boolean checkRead, boolean editionRendering, boolean saxCommentsMode, Set<String> externalizableMetadata) throws AmetysRepositoryException, SAXException, IOException
321    {
322        String metadataName = metadataDefinition.getName();
323        
324        if (metadata.hasMetadata(metadataName))
325        {
326            MetadataType type = metadataDefinition.getType();
327            switch (type)
328            {
329                case COMPOSITE:
330                    _saxCompositeMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, metadataSetElement, defaultLocale, prefix, checkRead, editionRendering, saxCommentsMode, externalizableMetadata);
331                    break;
332                case BINARY:
333                    _saxBinaryMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, editionRendering);
334                    break;
335                    
336                case FILE:
337                    _saxFileMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, editionRendering);
338                    break;
339                    
340                case RICH_TEXT:
341                    _saxRichTextMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, editionRendering);
342                    break;
343                    
344                case DATE:
345                case DATETIME:
346                    _saxSingleDateMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead);
347                    break;
348                    
349                case USER:
350                    _saxUserMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, editionRendering);
351                    break;
352                    
353                case CONTENT:
354                    _saxContentReferenceMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, metadataSetElement, defaultLocale, prefix, checkRead, editionRendering);
355                    break;
356                    
357                case SUB_CONTENT:
358                    _saxSubContentMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, metadataSetElement, defaultLocale, prefix, checkRead, editionRendering);
359                    break;
360                    
361                case GEOCODE:
362                    _saxGeocodeMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, editionRendering);
363                    break;
364                    
365                case REFERENCE:
366                    _saxReferenceMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, editionRendering);
367                    break;
368                    
369                case MULTILINGUAL_STRING:
370                    _saxMultilingualStringMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, defaultLocale, prefix, checkRead, editionRendering);
371                    break;
372                    
373                default:
374                    _saxStringMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead, metadataDefinition.getEnumerator());
375                    break;
376            }
377        }
378    }
379    
380    /**
381     * SAX a externalizable metadata
382     * @param contentHandler The content handler where to SAX into.
383     * @param content The content
384     * @param metadata The parent composite metadata.
385     * @param metadataSetElement The {@link MetadataSet} element.
386     * @param metadataDefinition The metadata definition
387     * @param prefix the metadata path prefix.
388     * @param checkRead true if need to check read right
389     * @param editionRendering true if the context rendering is for edition
390     * @param saxCommentsMode if true the comments will be SAXed for this metadata instead of the metadata value.
391     * @param externalizableMetadata The externalizable (with local and external value) metadata
392     * @param defaultLocale  The default locale for multilingual rendering
393     * @throws AmetysRepositoryException if an error occurred
394     * @throws SAXException if an error occurred while SAXing
395     * @throws IOException if an error occurred
396     */
397    protected void _saxExternalisableMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadata, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, Locale defaultLocale, String prefix, boolean checkRead, boolean editionRendering, boolean saxCommentsMode, Set<String> externalizableMetadata) throws AmetysRepositoryException, SAXException, IOException
398    {
399        String metadataName = metadataDefinition.getName();
400        
401        if (!checkRead || _canRead(content, metadataDefinition))
402        {
403            MetadataType type = metadataDefinition.getType();
404            switch (type)
405            {
406                case COMPOSITE:
407                    _saxCompositeMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, metadataSetElement, null, prefix, checkRead, editionRendering, saxCommentsMode, externalizableMetadata);
408                    break;
409                case BINARY:
410                    _saxExternalisableBinaryMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead);
411                    break;
412                    
413                case FILE:
414                    _saxExternalisableFileMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, prefix, checkRead);
415                    break;
416                    
417                case RICH_TEXT:
418                    _saxExternalisableRichTextMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead);
419                    break;
420                    
421                case DATE:
422                case DATETIME:
423                    _saxExternalizableDateMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead);
424                    break;
425                    
426                case LONG:
427                    _saxExternalizableLongMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead);
428                    break;
429                    
430                case DOUBLE:
431                    _saxExternalizableDoubleMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead);
432                    break;
433                    
434                case BOOLEAN:
435                    _saxExternalizableBooleanMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead);
436                    break;
437                    
438                case USER:
439                    _saxExternalisableUserMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead);
440                    break;
441                    
442                case SUB_CONTENT:
443                    _saxExternalisableObjectCollectionMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead);
444                    break;
445                    
446                case GEOCODE:
447                    _saxExternalisableGeocodeMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead);
448                    break;
449                    
450                case REFERENCE:
451                    _saxExternalisableReferenceMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead);
452                    break;
453                    
454                case MULTILINGUAL_STRING:
455                    _saxExternalisableMultilingualStringMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead);
456                    break;
457                    
458                case CONTENT:
459                    _saxExternalizableContentReferenceMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead, defaultLocale);
460                    break;
461                case STRING:
462                default:
463                    _saxExternalizableStringMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, checkRead);
464                    break;
465            }
466        }
467    }
468    
469    /**
470     * SAX the jsonified values of an externalizable Binary metadata 
471     * @param contentHandler The content handler 
472     * @param content The content
473     * @param parentMetadata The parent composite metadata
474     * @param metadataDef The metadata definition
475     * @param metadataName The metadata name
476     * @param prefix The prefix
477     * @param checkRead true if need to check read right
478     * @throws AmetysRepositoryException if an error occurs.
479     * @throws SAXException if an error occurs.
480     */
481    protected void _saxExternalisableBinaryMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, String prefix, boolean checkRead) throws SAXException
482    {
483        if (!checkRead || _canRead(content, metadataDef))
484        {
485            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
486            
487            Map<String, Object> localValue = null;
488            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
489            {
490                BinaryMetadata localBinary = ExternalizableMetadataHelper.getBinaryMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
491                localValue = _binaryAsJson(content, localBinary, prefix, ExternalizableMetadataHelper.getMetadataName(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL));
492            }
493            
494            Map<String, Object> extValue = null;
495            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
496            {
497                BinaryMetadata extBinary = ExternalizableMetadataHelper.getBinaryMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
498                extValue = _binaryAsJson(content, extBinary, prefix, ExternalizableMetadataHelper.getMetadataName(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL));
499            }
500            
501            _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
502        }
503    }
504    
505    /**
506     * SAX the comments of a metadata
507     * @param contentHandler The content handler where to SAX into.
508     * @param content The content
509     * @param metadata The parent composite metadata.
510     * @param metadataSetElement The {@link MetadataSet} element.
511     * @param metadataDefinition The metadata definition
512     * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. Only use if initial content's language is not null. Can be null.
513     * @param prefix the metadata path prefix.
514     * @param checkRead true if need to check read right
515     * @param editionRendering true if the context rendering is for edition
516     * @param externalizableMetadata The externalizable (with local and external value) metadata
517     * @throws AmetysRepositoryException if an error occurred
518     * @throws SAXException if an error occurred while SAXing
519     * @throws IOException if an error occurred
520     */
521    protected void _saxMetadataComments(ContentHandler contentHandler, Content content, CompositeMetadata metadata, AbstractMetadataSetElement metadataSetElement, MetadataDefinition metadataDefinition, Locale defaultLocale, String prefix, boolean checkRead, boolean editionRendering, Set<String> externalizableMetadata) throws SAXException, IOException
522    {
523        String metadataName = metadataDefinition.getName();
524        MetadataType type = metadataDefinition.getType();
525        
526        if (MetadataType.COMPOSITE.equals(type))
527        {
528            if (metadata.hasMetadata(metadataName))
529            {
530                _saxCompositeMetadata(contentHandler, content, metadata, metadataDefinition, metadataName, metadataSetElement, defaultLocale, prefix, checkRead, editionRendering, true, externalizableMetadata);
531            }
532        }
533        else
534        {
535            if (editionRendering && metadata instanceof CommentableCompositeMetadata)
536            {
537                CommentableCompositeMetadata commentableMetadata = (CommentableCompositeMetadata) metadata;
538                List<MetadataComment> comments = commentableMetadata.getComments(metadataName);
539                
540                if (!comments.isEmpty())
541                {
542                    AttributesImpl metadataAttrs = new AttributesImpl();
543                    metadataAttrs.addCDATAAttribute("path", prefix + metadataName);
544                    XMLUtils.startElement(contentHandler, "metadata", metadataAttrs);
545                    
546                    int id = 1;
547                    for (MetadataComment comment : comments)
548                    {
549                        AttributesImpl commentAttrs = new AttributesImpl();
550                        commentAttrs.addCDATAAttribute("id", String.valueOf(id++));
551                        commentAttrs.addCDATAAttribute("date", DateUtils.dateToString(comment.getDate()));
552                        commentAttrs.addCDATAAttribute("author", comment.getAuthor());
553                        XMLUtils.createElement(contentHandler, "comment", commentAttrs, comment.getComment());
554                    }
555                    
556                    XMLUtils.endElement(contentHandler, "metadata");
557                }
558            }
559        }
560    }
561
562    /**
563     * SAX a composite metadata.
564     * @param contentHandler the content handler to SAX into.
565     * @param content the content.
566     * @param metadata the parent metadata holder.
567     * @param metadataDefinition the metadata definition.
568     * @param metadataName The name of the metadata to sax
569     * @param metadataSetElement the metadata-set element.
570     * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. Only use if initial content's language is not null. Can be null.
571     * @param prefix the metadata path prefix.
572     * @param checkRead true if need to check read right.
573     * @param editionRendering true if rendering in edition mode, false in view mode.
574     * @param saxCommentsMode if true the comments will be SAXed for this metadata instead of the metadata value.
575     * @param externalizableMetadata The externalizable (with local and external value) metadata
576     * @throws SAXException if an error occurs.
577     * @throws IOException if an error occurs.
578     */
579    protected void _saxCompositeMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadata, MetadataDefinition metadataDefinition, String metadataName, AbstractMetadataSetElement metadataSetElement, Locale defaultLocale, String prefix, boolean checkRead, boolean editionRendering, boolean saxCommentsMode, Set<String> externalizableMetadata) throws SAXException, IOException
580    {
581        CompositeMetadata subMetadata = metadata.getCompositeMetadata(metadataName);
582        
583        AttributesImpl attrs = new AttributesImpl();
584        if (metadataDefinition instanceof RepeaterDefinition)
585        {
586            attrs.addCDATAAttribute("entryCount", Integer.toString(subMetadata.getMetadataNames().length));
587        }
588        
589        if (!saxCommentsMode)
590        {
591            XMLUtils.startElement(contentHandler, metadataName, attrs);
592        }
593        
594        if (metadataDefinition instanceof RepeaterDefinition)
595        {
596            String[] subMetadataNames = subMetadata.getMetadataNames();
597            Arrays.sort(subMetadataNames, REPEATER_ENTRY_COMPARATOR);
598            
599            for (String entryName : subMetadataNames)
600            {
601                org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType entryType = subMetadata.getType(entryName);
602                
603                if (entryType != org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.COMPOSITE)
604                {
605                    throw new AmetysRepositoryException("Invalid type: " + entryType + " for metadata: "
606                                                      + metadataName + " and entry of name: " + entryName);
607                }
608                
609                CompositeMetadata entry = subMetadata.getCompositeMetadata(entryName);
610                
611                if (!saxCommentsMode)
612                {
613                    AttributesImpl entryAttrs = new AttributesImpl();
614                    entryAttrs.addCDATAAttribute("name", entryName);
615                    XMLUtils.startElement(contentHandler, "entry", entryAttrs);
616                }
617
618                if (metadataSetElement != null)
619                {
620                    for (AbstractMetadataSetElement subElement : metadataSetElement.getElements())
621                    {
622                        // SAX all metadata contains in the current subElement
623                        _saxMetadataSetElementOrAll(contentHandler, content, entry, subElement, metadataDefinition, defaultLocale, prefix + metadataName + "[" + entryName + "]/", checkRead, editionRendering, saxCommentsMode, externalizableMetadata);
624                    }
625                }
626                else
627                {
628                    // SAX all metadata contains in the current MetadataSetElement or all metadata having a definition
629                    _saxMetadataSetElementOrAll(contentHandler, content, entry, metadataSetElement, metadataDefinition, defaultLocale, prefix + metadataName + "[" + entryName + "]/", checkRead, editionRendering, saxCommentsMode, externalizableMetadata);
630                }
631                
632                if (!saxCommentsMode)
633                {
634                    XMLUtils.endElement(contentHandler, "entry");
635                }
636            }
637        }
638        else if (metadataSetElement != null)
639        {
640            // SAX all metadata contains in the current MetadataSetElement or all metadata having a definition
641            for (AbstractMetadataSetElement subElement : metadataSetElement.getElements())
642            {
643                _saxMetadataSetElementOrAll(contentHandler, content, subMetadata, subElement, metadataDefinition, defaultLocale, prefix + metadataName + "/", checkRead, editionRendering, saxCommentsMode, externalizableMetadata);
644            }
645        }
646        else
647        {
648            _saxMetadataSetElementOrAll(contentHandler, content, subMetadata, null, metadataDefinition, defaultLocale, prefix + metadataName + "/", checkRead, editionRendering, saxCommentsMode, externalizableMetadata);
649        }
650
651        if (!saxCommentsMode)
652        {
653            XMLUtils.endElement(contentHandler, metadataName);
654        }
655    }
656    
657    /**
658     * SAX a metadata as a Binary.
659     * @param contentHandler the content handler where to SAX into.
660     * @param content The content
661     * @param metadata the parent composite metadata.
662     * @param metadataName the metadata name.
663     * @param metadataDef the metadata definition.
664     * @param prefix the metadata path prefix.
665     * @param checkRead true if need to check read right
666     * @param editionRendering true if rendering in edition mode, false in view mode.
667     * @throws AmetysRepositoryException if an error occurs.
668     * @throws SAXException if an error occurs.
669     */
670    protected void _saxBinaryMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadata, MetadataDefinition metadataDef,  String metadataName, String prefix, boolean checkRead, boolean editionRendering) throws AmetysRepositoryException, SAXException
671    {
672        if (!checkRead || _canRead(content, metadataDef))
673        {
674            if (editionRendering)
675            {
676                // In edition, generate the metadata as JSON.
677                _saxBinaryMetadataAsJson(contentHandler, content, metadata, metadataName, prefix);
678            }
679            else
680            {
681                _saxBinaryMetadata(contentHandler, metadata, metadataName, prefix);
682            }
683        }
684    }
685    
686    /**
687     * SAX a binary metadata as a JSON string.
688     * @param contentHandler the content handler where to SAX into.
689     * @param content the content.
690     * @param parentMetadata the parent composite metadata.
691     * @param metadataName the metadata name.
692     * @param prefix the metadata path prefix.
693     * @throws AmetysRepositoryException if an error occurs.
694     * @throws SAXException if an error occurs.
695     */
696    protected void _saxBinaryMetadataAsJson(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, String metadataName, String prefix) throws AmetysRepositoryException, SAXException
697    {
698        BinaryMetadata binaryMetadata = parentMetadata.getBinaryMetadata(metadataName);
699        
700        Map<String, Object> values = _binaryAsJson(content, binaryMetadata, prefix, metadataName);
701        String jsonString = _jsonUtils.convertObjectToJson(values);
702        
703        AttributesImpl attrs = new AttributesImpl();
704        attrs.addCDATAAttribute("json", "true");
705        XMLUtils.createElement(contentHandler, metadataName, attrs, jsonString);
706    }
707    
708    /**
709     * SAX a metadata as a File.
710     * @param contentHandler the content handler where to SAX into.
711     * @param content The content
712     * @param metadata the parent composite metadata.
713     * @param metadataName the metadata name.
714     * @param metadataDef the metadata definition
715     * @param prefix the metadata path prefix.
716     * @param checkRead true if need to check read right
717     * @param editionRendering true if rendering in edition mode, false in view mode.
718     * @throws AmetysRepositoryException if an error occurs.
719     * @throws SAXException if an error occurs.
720     */
721    protected void _saxFileMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadata, MetadataDefinition metadataDef, String metadataName, String prefix, boolean checkRead, boolean editionRendering) throws AmetysRepositoryException, SAXException
722    {
723        if (org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.BINARY.equals(metadata.getType(metadataName)))
724        {
725            _saxBinaryMetadata(contentHandler, content, metadata, metadataDef, metadataName, prefix, checkRead, editionRendering);
726        }
727        else
728        {
729            _saxResourceFileMetadata(contentHandler, content, metadata, metadataDef, metadataName, prefix, checkRead, editionRendering);
730        }
731    }
732    
733    /**
734     * Get the JSON representation of a {@link Resource}
735     * @param resource The resource
736     * @return The resource as JSON
737     */
738    protected Map<String, Object> _resourceAsJson (Resource resource)
739    {
740        Map<String, Object> json = new LinkedHashMap<>();
741        
742        String filename = resource.getName();
743        
744        json.put("type", "explorer");
745        json.put("mimeType", resource.getMimeType());
746        json.put("id", resource.getId());
747        json.put("path", resource.getId());
748        
749        if (filename != null)
750        {
751            json.put("filename", filename);
752        }
753        json.put("size", String.valueOf(resource.getLength()));
754        json.put("lastModified", DateUtils.dateToString(resource.getLastModified()));
755        
756
757        String viewUrl = ResolveURIComponent.resolveBoundedImage("explorer", resource.getId(), 100, 100);
758        String downloadUrl = ResolveURIComponent.resolve("explorer", resource.getId(), true);
759
760        json.put("viewUrl", viewUrl);
761        json.put("downloadUrl", downloadUrl);
762        
763        return json;
764    }
765    
766    /**
767     * SAX a metadata as a binary file from resource.
768     * @param contentHandler the content handler where to SAX into.
769     * @param content The content
770     * @param metadata the parent composite metadata.
771     * @param metadataName the metadata name.
772     * @param metadataDef the metadata definition
773     * @param prefix the metadata path prefix.
774     * @param checkRead true if need to check read right
775     * @param editionRendering true if rendering in edition mode, false in view mode.
776     * @throws AmetysRepositoryException if an error occurs.
777     * @throws SAXException if an error occurs.
778     */
779    protected void _saxResourceFileMetadata (ContentHandler contentHandler, Content content, CompositeMetadata metadata, MetadataDefinition metadataDef, String metadataName, String prefix, boolean checkRead, boolean editionRendering) throws AmetysRepositoryException, SAXException
780    {
781        if (!checkRead || _canRead(content, metadataDef))
782        {
783            if (editionRendering)
784            {
785                _saxResourceFileMetadataAsJson(contentHandler, metadata, metadataName);
786            }
787            else
788            {
789                _saxResourceFileMetadata(contentHandler, metadata, metadataName);
790            }
791        }
792    }
793    
794    /**
795     * SAX a metadata as a binary file from resource, as a JSON string.
796     * @param contentHandler the content handler where to SAX into.
797     * @param metadata the parent composite metadata.
798     * @param metadataName the metadata name.
799     * @throws AmetysRepositoryException if an error occurs.
800     * @throws SAXException if an error occurs.
801     */
802    protected void _saxResourceFileMetadata(ContentHandler contentHandler, CompositeMetadata metadata, String metadataName) throws AmetysRepositoryException, SAXException
803    {
804        String value = metadata.getString(metadataName);
805        
806        try
807        {
808            Resource resource = (Resource) _resolver.resolveById(value);
809            
810            String filename = resource.getName();
811            AttributesImpl attrs = new AttributesImpl();
812            
813            attrs.addCDATAAttribute("type", "explorer");
814            attrs.addCDATAAttribute("id", resource.getId());
815            attrs.addCDATAAttribute("path", resource.getId());
816            attrs.addCDATAAttribute("mime-type", resource.getMimeType());
817            
818            if (filename != null)
819            {
820                attrs.addCDATAAttribute("filename", filename);
821            }
822            attrs.addCDATAAttribute("size", String.valueOf(resource.getLength()));
823            attrs.addCDATAAttribute("lastModified", DateUtils.dateToString(resource.getLastModified()));
824            
825            XMLUtils.createElement(contentHandler, metadataName, attrs);
826        }
827        catch (AmetysRepositoryException e)
828        {
829            getLogger().warn("The resource of id '" + value + "' does not exist.", e);
830        }
831    }
832    
833    /**
834     * SAX a metadata as a binary file from resource, as a JSON string.
835     * @param contentHandler the content handler where to SAX into.
836     * @param metadata the parent composite metadata.
837     * @param metadataName the metadata name.
838     * @throws AmetysRepositoryException if an error occurs.
839     * @throws SAXException if an error occurs.
840     */
841    protected void _saxResourceFileMetadataAsJson(ContentHandler contentHandler, CompositeMetadata metadata, String metadataName) throws AmetysRepositoryException, SAXException
842    {
843        Map<String, Object> values = new LinkedHashMap<>();
844        
845        String value = metadata.getString(metadataName);
846                
847        try
848        {
849            Resource resource = (Resource) _resolver.resolveById(value);
850            
851            values = _resourceAsJson(resource);
852            
853            String jsonString = _jsonUtils.convertObjectToJson(values);
854            
855            AttributesImpl attrs = new AttributesImpl();
856            attrs.addCDATAAttribute("json", "true");
857            XMLUtils.createElement(contentHandler, metadataName, attrs, jsonString);
858        }
859        catch (AmetysRepositoryException e)
860        {
861            getLogger().warn("The resource of id '" + value + "' does not exist.", e);
862        }
863    }
864    
865    /**
866     * SAX a metadata as a User.
867     * @param contentHandler the content handler where to SAX into.
868     * @param content The content
869     * @param metadata the parent composite metadata.
870     * @param metadataName the metadata name.
871     * @param metadataDef the metadata definition
872     * @param prefix the metadata path prefix.
873     * @param checkRead true if need to check read right
874     * @param editionRendering true if rendering in edition mode, false in view mode.
875     * @throws AmetysRepositoryException if an error occurs.
876     * @throws SAXException if an error occurs.
877     */
878    protected void _saxUserMetadata (ContentHandler contentHandler, Content content, CompositeMetadata metadata, MetadataDefinition metadataDef, String metadataName, String prefix, boolean checkRead, boolean editionRendering) throws AmetysRepositoryException, SAXException
879    {
880        if (!checkRead || _canRead(content, metadataDef))
881        {
882            if (editionRendering)
883            {
884                if (metadataDef.isMultiple())
885                {
886                    _saxMultipleUserMetadataAsJson(contentHandler, metadata, metadataName);
887                }
888                else
889                {
890                    _saxSingleUserMetadataAsJson(contentHandler, metadata, metadataName);
891                }
892            }
893            else
894            {
895                _saxUserMetadata(contentHandler, metadata, metadataName);
896            }
897        }
898    }
899    
900    /**
901     * SAX a "content" metadata.
902     * @param contentHandler the content handler to SAX into.
903     * @param content The currently saxed content.
904     * @param metadatas the parent composite metadata.
905     * @param metadataDefinition the metadata definition.
906     * @param metadataName the metadata name.
907     * @param metadataSetElement the metadata-set element.
908     * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. Only use if initial content's language is not null. Can be null.
909     * @param prefix the metadata path prefix.
910     * @param checkRead true if need to check read right.
911     * @param editionRendering true if rendering in edition mode, false in view mode.
912     * @throws SAXException if an error occurs.
913     * @throws IOException if an error occurs.
914     */
915    protected void _saxContentReferenceMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadatas, MetadataDefinition metadataDefinition, String metadataName, AbstractMetadataSetElement metadataSetElement, Locale defaultLocale, String prefix, boolean checkRead, boolean editionRendering) throws SAXException, IOException
916    {
917        if (!checkRead || _canRead(content, metadataDefinition))
918        {
919            String[] values = metadatas.getStringArray(metadataName);
920            
921            if (values != null)
922            {
923                if (editionRendering)
924                {
925                    _saxContentReferenceMetadataForEdition(contentHandler, content, metadataDefinition, metadataName, defaultLocale, values);
926                }
927                else
928                {
929                    for (String value : values)
930                    {
931                        try
932                        {
933                            // In view mode, render the contents 
934                            Content refContent = _resolver.resolveById(value);
935                            Locale locale = content.getLanguage() != null ? new Locale(content.getLanguage()) : defaultLocale;
936                            _saxNonEditionRenderingContent(contentHandler, refContent, locale, metadataName, metadataSetElement, prefix, checkRead);
937                        }
938                        catch (AmetysRepositoryException e)
939                        {
940                            if (getLogger().isWarnEnabled())
941                            {
942                                getLogger().warn(String.format("The content of id '%s' references a non-existing content with value '%s' for metadata %s", content.getId(), value, metadataDefinition.getId()), e);
943                            }
944                        }
945                    }
946                }
947            }      
948        }        
949    }
950
951    private void _saxContentReferenceMetadataForEdition(ContentHandler contentHandler, Content content, MetadataDefinition metadataDefinition, String metadataName, Locale defaultLocale, String[] values) throws SAXException
952    {
953        String jsonString = "";
954        
955        if (metadataDefinition.isMultiple())
956        {
957            List<Map<String, Object>> contentsAsJson = new ArrayList<>();
958            
959            for (String value : values)
960            {
961                try
962                {
963                    Content refContent = _resolver.resolveById(value);
964                    contentsAsJson.add(_contentAsJson(refContent, content, defaultLocale));
965                }
966                catch (AmetysRepositoryException e)
967                {
968                    if (getLogger().isWarnEnabled())
969                    {
970                        getLogger().warn(String.format("The content of id '%s' references a non-existing content with value '%s' for metadata %s", content.getId(), value, metadataDefinition.getId()), e);
971                    }
972                }
973            }
974            
975            jsonString = _jsonUtils.convertObjectToJson(contentsAsJson);
976        }
977        else if (values.length > 0)
978        {
979            String value = values[0];
980            try
981            {
982                Content refContent = _resolver.resolveById(value);
983                jsonString = _jsonUtils.convertObjectToJson(_contentAsJson(refContent, content, defaultLocale));
984            }
985            catch (AmetysRepositoryException e)
986            {
987                if (getLogger().isWarnEnabled())
988                {
989                    getLogger().warn(String.format("The content of id '%s' references a non-existing content with value '%s' for metadata %s", content.getId(), value, metadataDefinition.getId()), e);
990                }
991            }
992        }
993        
994        AttributesImpl attrs = new AttributesImpl();
995        attrs.addCDATAAttribute("json", "true");
996        XMLUtils.createElement(contentHandler, metadataName, attrs, jsonString);
997    }
998    
999    /**
1000     * Get the JSON representation of content value
1001     * @param refContent The content to sax
1002     * @param parentContent The owning content
1003     * @param defaultLocale The default local to resolve multilingual titles
1004     * @return The json properties of the content of null
1005     */
1006    protected Map<String, Object> _contentAsJson(Content refContent, Content parentContent, Locale defaultLocale)
1007    {
1008        Locale locale = parentContent.getLanguage() != null ? new Locale(parentContent.getLanguage()) : defaultLocale;
1009
1010        Map<String, Object> contentProperties = new HashMap<>();
1011        contentProperties.put("id", refContent.getId());
1012        contentProperties.put("title", refContent.getTitle(locale));
1013        contentProperties.put("iconGlyph", StringUtils.defaultString(_contentTypesHelper.getIconGlyph(refContent)));
1014        contentProperties.put("iconDecorator", StringUtils.defaultString(_contentTypesHelper.getIconDecorator(refContent)));
1015        contentProperties.put("smallIcon", StringUtils.defaultString(_contentTypesHelper.getSmallIcon(refContent)));
1016        contentProperties.put("mediumIcon", StringUtils.defaultString(_contentTypesHelper.getMediumIcon(refContent)));
1017        contentProperties.put("largeIcon", StringUtils.defaultString(_contentTypesHelper.getLargeIcon(refContent)));
1018        contentProperties.put("contentTypes", refContent.getTypes());
1019        contentProperties.put("mixins", refContent.getMixinTypes());
1020        
1021        return contentProperties;
1022    }
1023
1024    /**
1025     * SAX a "content" metadata.
1026     * @param contentHandler the content handler to SAX into.
1027     * @param content The currently saxed content.
1028     * @param metadatas the parent composite metadata.
1029     * @param metadataDefinition the metadata definition.
1030     * @param metadataName the metadata name.
1031     * @param metadataSetElement the metadata-set element.
1032     * @param defaultLocale The locale to use to sax localized values such as multilingual content or multilingual string. Only use if initial content's language is not null. Can be null.
1033     * @param prefix the metadata path prefix.
1034     * @param checkRead true if need to check read right.
1035     * @param editionRendering true if rendering in edition mode, false in view mode.
1036     * @throws SAXException if an error occurs.
1037     * @throws IOException if an error occurs.
1038     */
1039    protected void _saxSubContentMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadatas, MetadataDefinition metadataDefinition, String metadataName, AbstractMetadataSetElement metadataSetElement, Locale defaultLocale, String prefix, boolean checkRead, boolean editionRendering) throws SAXException, IOException
1040    {
1041        if (!checkRead || _canRead(content, metadataDefinition))
1042        {
1043            TraversableAmetysObject contentMeta = metadatas.getObjectCollection(metadataName);
1044            
1045            try (AmetysObjectIterable<Content> contents = contentMeta.getChildren())
1046            {
1047                for (Content refContent : contents)
1048                {
1049                    if (editionRendering)
1050                    {
1051                        // In edition, just SAX the content ID.
1052                        XMLUtils.createElement(contentHandler, metadataName, refContent.getId());
1053                    }
1054                    else
1055                    {
1056                        // In view mode, render the contents 
1057                        Locale locale = content.getLanguage() != null ? new Locale(content.getLanguage()) : defaultLocale;
1058                        _saxNonEditionRenderingContent(contentHandler, refContent, locale, metadataName, metadataSetElement, prefix, checkRead);
1059                    }
1060                }
1061            }
1062        }        
1063    }
1064
1065    /**
1066     * SAX a content (referenced or sub-content) in view mode.
1067     * @param contentHandler the content handler to SAX into.
1068     * @param content The referenced or sub-content to SAX.
1069     * @param defaultLocale The locale to use to sax the localized values of this content. Only use if content 's language is null.
1070     * @param metadataName the metadata name.
1071     * @param metadataSetElement the metadata-set element.
1072     * @param prefix the metadata path prefix.
1073     * @param checkRead true if need to check read right.
1074     * @throws SAXException if an error occurs.
1075     * @throws IOException if an error occurs.
1076     */
1077    protected void _saxNonEditionRenderingContent(ContentHandler contentHandler, Content content, Locale defaultLocale, String metadataName, AbstractMetadataSetElement metadataSetElement, String prefix, boolean checkRead) throws SAXException, IOException
1078    {
1079        MetadataSet metadataSet = _getContentMetadataSet(content, metadataSetElement, false);
1080        
1081        AttributesImpl attrs = new AttributesImpl();
1082        attrs.addCDATAAttribute("id", content.getId());
1083        attrs.addCDATAAttribute("name", content.getName());
1084        attrs.addCDATAAttribute("title", content.getTitle(defaultLocale));
1085        if (content.getLanguage() != null)
1086        {
1087            attrs.addCDATAAttribute("language", content.getLanguage());
1088        }
1089        attrs.addCDATAAttribute("createdAt", DateUtils.dateToString(content.getCreationDate()));
1090        attrs.addCDATAAttribute("creator", content.getCreator().getLogin());
1091        attrs.addCDATAAttribute("lastModifiedAt", DateUtils.dateToString(content.getLastModified()));
1092        XMLUtils.startElement(contentHandler, metadataName, attrs);
1093        
1094        if (metadataSet != null)
1095        {
1096            _saxMetadataSetElementOrAll(contentHandler, content, content.getMetadataHolder(), metadataSet, null, defaultLocale, prefix, checkRead, false, false, getExternalizableMetadata(content));
1097        }
1098        
1099        XMLUtils.endElement(contentHandler, metadataName);
1100    }
1101    
1102    /**
1103     * SAX "geocode" metadata
1104     * @param contentHandler the content handler where to SAX into.
1105     * @param content The content
1106     * @param metadatas the parent composite metadata.
1107     * @param metadataDefinition the metadata definition.
1108     * @param metadataName the metadata name.
1109     * @param prefix the metadata path prefix.
1110     * @param checkRead true if need to check read right.
1111     * @param editionRendering true if rendering in edition mode, false in view mode.
1112     * @throws AmetysRepositoryException if an error occurs.
1113     * @throws SAXException if an error occurs.
1114     */
1115    protected void _saxGeocodeMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadatas, MetadataDefinition metadataDefinition, String metadataName, String prefix, boolean checkRead, boolean editionRendering) throws SAXException
1116    {
1117        if (!checkRead || _canRead(content, metadataDefinition))
1118        {
1119            if (editionRendering)
1120            {
1121                _saxSingleGeocodeMetadataAsJson(contentHandler, metadatas, metadataName);
1122            }
1123            else
1124            {
1125                _saxSingleGeocodeMetadata(contentHandler, metadatas, metadataName);
1126            }
1127        }
1128    }
1129    
1130    /**
1131     * SAX a geocode metadata
1132     * @param contentHandler the content handler where to SAX into.
1133     * @param parentMetadata the parent composite metadata.
1134     * @param metadataName the metadata name.
1135     * @throws SAXException if an error occurs
1136     */
1137    protected void _saxSingleGeocodeMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName) throws SAXException
1138    {
1139        CompositeMetadata geoCode = parentMetadata.getCompositeMetadata(metadataName);
1140
1141        AttributesImpl attrs = new AttributesImpl();
1142        attrs.addCDATAAttribute("longitude", String.valueOf(geoCode.getDouble("longitude")));
1143        attrs.addCDATAAttribute("latitude", String.valueOf(geoCode.getDouble("latitude")));
1144
1145        XMLUtils.createElement(contentHandler, metadataName, attrs);
1146    }
1147
1148    /**
1149     * SAX values of a GeoCode metadata as a JSON String
1150     * @param contentHandler the content handler where to SAX into.
1151     * @param parentMetadata the parent composite metadata.
1152     * @param metadataName the metadata name.
1153     * @throws SAXException if an error occurs
1154     */
1155    protected void _saxSingleGeocodeMetadataAsJson(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName) throws SAXException
1156    {
1157        CompositeMetadata geoCode = parentMetadata.getCompositeMetadata(metadataName);
1158        
1159        String jsonString = _jsonUtils.convertObjectToJson(_geocodeAsJson(geoCode));
1160
1161        AttributesImpl attrs = new AttributesImpl();
1162        attrs.addCDATAAttribute("json", "true");
1163        XMLUtils.createElement(contentHandler, metadataName, attrs, jsonString);
1164    }
1165    
1166    /**
1167     * Get the JSON representation of geocode values
1168     * @param geoCode The composite metadata
1169     * @return The values as json
1170     */
1171    protected Map<String, Object> _geocodeAsJson (CompositeMetadata geoCode)
1172    {
1173        Map<String, Object> values = new LinkedHashMap<>();
1174        values.put("longitude", geoCode.getDouble("longitude"));
1175        values.put("latitude", geoCode.getDouble("latitude"));
1176        return values;
1177    }
1178    
1179    /**
1180     * SAX "geocode" metadata
1181     * @param contentHandler the content handler where to SAX into.
1182     * @param content The content
1183     * @param metadatas the parent composite metadata.
1184     * @param metadataDefinition the metadata definition.
1185     * @param metadataName the metadata name.
1186     * @param defaultLocale The default locale to use to sax multilingual value if content's language is null.
1187     * @param prefix the metadata path prefix.
1188     * @param checkRead true if need to check read right.
1189     * @param editionRendering true if rendering in edition mode, false in view mode.
1190     * @throws AmetysRepositoryException if an error occurs.
1191     * @throws SAXException if an error occurs.
1192     */
1193    protected void _saxMultilingualStringMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadatas, MetadataDefinition metadataDefinition, String metadataName, Locale defaultLocale, String prefix, boolean checkRead, boolean editionRendering) throws SAXException
1194    {
1195        if (!checkRead || _canRead(content, metadataDefinition))
1196        {
1197            if (editionRendering)
1198            {
1199                _saxMultilingualStringAsJson(contentHandler, metadatas, metadataName);
1200            }
1201            else
1202            {
1203                _saxMultilingualStringMetadata(contentHandler, metadatas, metadataName, prefix, content.getLanguage() != null ? new Locale(content.getLanguage()) : defaultLocale);
1204            }
1205        }
1206    }
1207    
1208    /**
1209     * SAX values of a GeoCode metadata as a JSON String
1210     * @param contentHandler the content handler where to SAX into.
1211     * @param parentMetadata the parent composite metadata.
1212     * @param metadataName the metadata name.
1213     * @throws SAXException if an error occurs
1214     */
1215    protected void _saxMultilingualStringAsJson(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName) throws SAXException
1216    {
1217        MultilingualString multilingualString = parentMetadata.getMultilingualString(metadataName);
1218        
1219        String jsonString = _jsonUtils.convertObjectToJson(_multilingualStringAsJson(multilingualString));
1220
1221        AttributesImpl attrs = new AttributesImpl();
1222        attrs.addCDATAAttribute("json", "true");
1223        XMLUtils.createElement(contentHandler, metadataName, attrs, jsonString);
1224    }
1225    
1226    /**
1227     * SAX "reference" metadata
1228     * @param contentHandler the content handler where to SAX into.
1229     * @param content The content
1230     * @param metadatas the parent composite metadata.
1231     * @param metadataDefinition the metadata definition.
1232     * @param metadataName the metadata name.
1233     * @param prefix the metadata path prefix.
1234     * @param checkRead true if need to check read right.
1235     * @param editionRendering true if rendering in edition mode, false in view mode.
1236     * @throws AmetysRepositoryException if an error occurs.
1237     * @throws SAXException if an error occurs.
1238     */
1239    protected void _saxReferenceMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadatas, MetadataDefinition metadataDefinition, String metadataName, String prefix, boolean checkRead, boolean editionRendering) throws SAXException
1240    {
1241        if (!checkRead || _canRead(content, metadataDefinition))
1242        {
1243            if (editionRendering)
1244            {
1245                _saxSingleReferenceMetadataAsJson(contentHandler, metadatas, metadataName);
1246            }
1247            else
1248            {
1249                _saxSingleReferenceMetadata(contentHandler, metadatas, metadataName);
1250            }
1251        }
1252    }
1253    
1254    /**
1255     * SAX values of a Reference metadata
1256     *@param contentHandler the content handler where to SAX into.
1257     * @param parentMetadata the parent composite metadata.
1258     * @param metadataName the metadata name.
1259     * @throws SAXException if an error occurs
1260     */
1261    protected void _saxSingleReferenceMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName) throws SAXException
1262    {
1263        CompositeMetadata refMetadata = parentMetadata.getCompositeMetadata(metadataName);
1264        
1265        AttributesImpl attrs = new AttributesImpl();
1266        attrs.addCDATAAttribute("type", refMetadata.getString("type"));
1267        
1268        XMLUtils.createElement(contentHandler, metadataName, attrs, refMetadata.getString("value"));
1269    }
1270
1271    /**
1272     * SAX values of a Reference metadata to be used as a JSON String.
1273     * @param contentHandler the content handler where to SAX into.
1274     * @param parentMetadata the parent composite metadata.
1275     * @param metadataName the metadata name.
1276     * @throws SAXException if an error occurs
1277     */
1278    protected void _saxSingleReferenceMetadataAsJson(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName) throws SAXException
1279    {
1280        CompositeMetadata refMetadata = parentMetadata.getCompositeMetadata(metadataName);
1281        
1282        Map<String, Object> refValue = _referenceAsJson(refMetadata);
1283        String jsonString = _jsonUtils.convertObjectToJson(refValue);
1284
1285        AttributesImpl attrs = new AttributesImpl();
1286        attrs.addCDATAAttribute("json", "true");
1287        XMLUtils.createElement(contentHandler, metadataName, attrs, jsonString);
1288    }
1289
1290    /**
1291     * Get the JSON representation of a reference metadata
1292     * @param refMetadata The composite metadata
1293     * @return The value as JSON
1294     */
1295    protected Map<String, Object> _referenceAsJson (CompositeMetadata refMetadata)
1296    {
1297        Map<String, Object> value = new LinkedHashMap<>();
1298        value.put("type", refMetadata.getString("type"));
1299        value.put("value", refMetadata.getString("value"));
1300        return value;
1301    }
1302    
1303    private MetadataSet _getContentMetadataSet(Content content, AbstractMetadataSetElement metadataSetElement, boolean editionRendering) throws AmetysRepositoryException
1304    {
1305        MetadataSet metadataSet = null;
1306        
1307        String metadataSetName = null;
1308        if (metadataSetElement != null && metadataSetElement instanceof MetadataDefinitionReference)
1309        {
1310            metadataSetName = ((MetadataDefinitionReference) metadataSetElement).getMetadataSetName();
1311        }
1312        
1313        if (metadataSetName != null)
1314        {
1315            if (editionRendering)
1316            {
1317                metadataSet = _contentTypesHelper.getMetadataSetForEdition(metadataSetName, content.getTypes(), content.getMixinTypes());
1318            }
1319            else
1320            {
1321                metadataSet = _contentTypesHelper.getMetadataSetForView(metadataSetName, content.getTypes(), content.getMixinTypes());
1322            }
1323        }
1324        else if (metadataSetElement != null)
1325        {
1326            metadataSet = new MetadataSet();
1327            for (AbstractMetadataSetElement element : metadataSetElement.getElements())
1328            {
1329                metadataSet.addElement(element);
1330            }
1331        }
1332        
1333        return metadataSet;
1334    }
1335
1336    /**
1337     * SAX a metadata as a RichText.
1338     * @param contentHandler the content handler where to SAX into.
1339     * @param content The content
1340     * @param metadata the parent composite metadata.
1341     * @param metadataDef The metadata definition
1342     * @param metadataName the metadata name.
1343     * @param prefix the metadata path prefix.
1344     * @param checkRead true if need to check read right
1345     * @param editionRendering true if the context rendering is for edition
1346     * @throws AmetysRepositoryException if an error occurs.
1347     * @throws SAXException if an error occurs.
1348     * @throws IOException if an error occurs.
1349     */
1350    protected void _saxRichTextMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadata, MetadataDefinition metadataDef, String metadataName, String prefix, boolean checkRead, boolean editionRendering) throws AmetysRepositoryException, SAXException, IOException
1351    {
1352        if (!checkRead || _canRead(content, metadataDef))
1353        {
1354            RichText richText = metadata.getRichText(metadataName);
1355            AttributesImpl attrs = new AttributesImpl();
1356            String mimeType = richText.getMimeType();
1357            
1358            attrs.addCDATAAttribute("mime-type", richText.getMimeType());
1359            attrs.addCDATAAttribute("lastModified", DateUtils.dateToString(richText.getLastModified()));
1360            
1361            XMLUtils.startElement(contentHandler, metadataName, attrs);
1362            
1363            if (editionRendering)
1364            {
1365                if (!(richText instanceof ModifiableRichText))
1366                {
1367                    throw new SAXException("");
1368                }   
1369                
1370                RichTextTransformer richTextTransformer = metadataDef.getRichTextTransformer();
1371                StringBuilder result = new StringBuilder(2048);
1372                
1373                try
1374                {
1375                    richTextTransformer.transformForEditing((ModifiableRichText) richText, result);
1376                }
1377                catch (IOException e)
1378                {
1379                    throw new AmetysRepositoryException("Unable to transform a rich text into a string", e);
1380                }
1381                
1382                XMLUtils.data(contentHandler, result.toString());
1383            }
1384            else
1385            {
1386                if (mimeType.equals("text/xml") || mimeType.equals("application/xml"))
1387                {
1388                    RichTextTransformer richTextTransformer = metadataDef.getRichTextTransformer();
1389                    richTextTransformer.transformForRendering(richText, new IgnoreRootHandler(contentHandler));
1390                }
1391                else if (mimeType.equals("text/plain"))
1392                {
1393                    String encoding = richText.getEncoding();
1394                    
1395                    try (InputStream is = richText.getInputStream())
1396                    {
1397                        XMLUtils.data(contentHandler, IOUtils.toString(is, encoding));
1398                    }
1399                }
1400                else
1401                {
1402                    throw new AmetysRepositoryException("Mime-type " + mimeType + " is not supported for rich text rendering");
1403                }
1404            }
1405            
1406            XMLUtils.endElement(contentHandler, metadataName);
1407        }
1408    }
1409    
1410    
1411
1412    /**
1413     * SAX a metadata as a String.
1414     * @param contentHandler the content handler where to SAX into
1415     * @param content The content
1416     * @param metadatas the parent composite metadata.
1417     * @param metadataName the metadata name.
1418     * @param metadataDef the metadata definition
1419     * @param prefix the metadata path prefix.
1420     * @param checkRead true if need to check read right
1421     * @param enumerator The enumerator for values
1422     * @throws AmetysRepositoryException if an error occurs.
1423     * @throws SAXException if an error occurs.
1424     */
1425    protected void _saxStringMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadatas,  MetadataDefinition metadataDef, String metadataName, String prefix, boolean checkRead, Enumerator enumerator) throws AmetysRepositoryException, SAXException
1426    {
1427        if (!checkRead || _canRead(content, metadataDef))
1428        {
1429            if (enumerator != null)
1430            {
1431                _saxEnumeratedStringMetadata(contentHandler, metadatas, metadataName, enumerator);
1432            }
1433            else
1434            {
1435                _saxStringMetadata(contentHandler, metadatas, metadataName, prefix);
1436            }     
1437        }
1438    }
1439
1440    /**
1441     * SAX a metadata as a Date.
1442     * @param contentHandler the content handler where to SAX into.
1443     * @param content The content
1444     * @param metadatas the parent composite metadata.
1445     * @param metadataName the metadata name.
1446     * @param metadataDef the metadata definition.
1447     * @param prefix the metadata path prefix.
1448     * @param checkRead true if need to check read right
1449     * @throws AmetysRepositoryException if an error occurs.
1450     * @throws SAXException if an error occurs.
1451     */
1452    protected void _saxSingleDateMetadata(ContentHandler contentHandler, Content content, CompositeMetadata metadatas, MetadataDefinition metadataDef, String metadataName, String prefix, boolean checkRead) throws AmetysRepositoryException, SAXException
1453    {
1454        if (!checkRead || _canRead(content, metadataDef))
1455        {
1456            _saxDateMetadata(contentHandler, metadatas, metadataName, prefix);
1457        }
1458    }
1459    
1460    /**
1461     * Get the JSON representation of a {@link BinaryMetadata}
1462     * @param content The content
1463     * @param binaryMetadata The metadata
1464     * @param prefix The prefix
1465     * @param metadataName The metadata name
1466     * @return The binary as JSON
1467     */
1468    protected Map<String, Object> _binaryAsJson (Content content, BinaryMetadata binaryMetadata, String prefix, String metadataName)
1469    {
1470        Map<String, Object> values = _binaryAsJson(binaryMetadata, prefix, metadataName);
1471        
1472        StringBuffer sb = new StringBuffer();
1473        sb.append(prefix)
1474            .append(metadataName)
1475            .append("?contentId=")
1476            .append(content.getId());
1477        
1478        String viewUrl = ResolveURIComponent.resolveBoundedImage("attribute", sb.toString(), 100, 100);
1479        String downloadUrl = ResolveURIComponent.resolve("attribute", sb.toString(), true);
1480        
1481        values.put("viewUrl", viewUrl);
1482        values.put("downloadUrl", downloadUrl);
1483        
1484        return values;
1485    }
1486    
1487    /**
1488     * SAX the jsonified values of an externalizable Content metadata 
1489     * @param contentHandler The content handler 
1490     * @param content The content
1491     * @param parentMetadata The parent composite metadata
1492     * @param metadataDef The metadata definition
1493     * @param metadataName The metadata name
1494     * @param checkRead true if need to check read right
1495     * @param defaultLocale The default locale for multilingual title rendering
1496     * @throws AmetysRepositoryException if an error occurs.
1497     * @throws SAXException if an error occurs.
1498     */
1499    protected void _saxExternalizableContentReferenceMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead, Locale defaultLocale) throws AmetysRepositoryException, SAXException
1500    {
1501        if (!checkRead || _canRead(content, metadataDef))
1502        {
1503            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
1504            
1505            if (metadataDef.isMultiple())
1506            {
1507                _saxExternalizableContentReferenceMultipleMetadata(contentHandler, content, parentMetadata, metadataDef, metadataName, defaultLocale, status);
1508            }
1509            else
1510            {
1511                _saxExternalizableContentReferenceSimpleMetadata(contentHandler, content, parentMetadata, metadataDef, metadataName, defaultLocale, status);
1512            }
1513        }
1514    }
1515    
1516    private void _saxExternalizableContentReferenceMultipleMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, Locale defaultLocale, ExternalizableMetadataStatus status) throws AmetysRepositoryException, SAXException
1517    {
1518        List<Object> localValues = new ArrayList<>();
1519        if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1520        {
1521            String[] values = ExternalizableMetadataHelper.getStringArray(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1522            for (String value : values)
1523            {
1524                try
1525                {
1526                    // In view mode, render the contents 
1527                    Content refContent = _resolver.resolveById(value);
1528                    localValues.add(_contentAsJson(refContent, content, defaultLocale));
1529                }
1530                catch (AmetysRepositoryException e)
1531                {
1532                    if (getLogger().isWarnEnabled())
1533                    {
1534                        getLogger().warn(String.format("The content of id '%s' references a non-existing content with value '%s' for metadata %s", content.getId(), value, metadataDef.getId()), e);
1535                    }
1536                }
1537            }
1538        }
1539        
1540        List<Object> extValues = new ArrayList<>();
1541        if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1542        {
1543            String[] values = ExternalizableMetadataHelper.getStringArray(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1544            for (String value : values)
1545            {
1546                try
1547                {
1548                    // In view mode, render the contents 
1549                    Content refContent = _resolver.resolveById(value);
1550                    extValues.add(_contentAsJson(refContent, content, defaultLocale));
1551                }
1552                catch (AmetysRepositoryException e)
1553                {
1554                    if (getLogger().isWarnEnabled())
1555                    {
1556                        getLogger().warn(String.format("The content of id '%s' references a non-existing content with value '%s' for metadata %s", content.getId(), value, metadataDef.getId()), e);
1557                    }
1558                }
1559            }
1560        }
1561        
1562        _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValues, extValues);
1563    }
1564    
1565    private void _saxExternalizableContentReferenceSimpleMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, Locale defaultLocale, ExternalizableMetadataStatus status) throws AmetysRepositoryException, SAXException
1566    {
1567        Map<String, Object> localValue = null;
1568        if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1569        {
1570            String value = ExternalizableMetadataHelper.getString(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1571            try
1572            {
1573                // In view mode, render the contents 
1574                Content refContent = _resolver.resolveById(value);
1575                localValue = _contentAsJson(refContent, content, defaultLocale);
1576            }
1577            catch (AmetysRepositoryException e)
1578            {
1579                if (getLogger().isWarnEnabled())
1580                {
1581                    getLogger().warn(String.format("The content of id '%s' references a non-existing content with value '%s' for metadata %s", content.getId(), value, metadataDef.getId()), e);
1582                }
1583            }
1584        }
1585        
1586        Map<String, Object> extValue = null;
1587        if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1588        {
1589            String value = ExternalizableMetadataHelper.getString(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1590            try
1591            {
1592                // In view mode, render the contents 
1593                Content refContent = _resolver.resolveById(value);
1594                extValue = _contentAsJson(refContent, content, defaultLocale);
1595            }
1596            catch (AmetysRepositoryException e)
1597            {
1598                if (getLogger().isWarnEnabled())
1599                {
1600                    getLogger().warn(String.format("The content of id '%s' references a non-existing content with value '%s' for metadata %s", content.getId(), value, metadataDef.getId()), e);
1601                }
1602            }
1603        }
1604        _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
1605    }
1606    
1607    /**
1608     * SAX the jsonified values of an externalizable String metadata 
1609     * @param contentHandler The content handler 
1610     * @param content The content
1611     * @param parentMetadata The parent composite metadata
1612     * @param metadataDef The metadata definition
1613     * @param metadataName The metadata name
1614     * @param checkRead true if need to check read right
1615     * @throws AmetysRepositoryException if an error occurs.
1616     * @throws SAXException if an error occurs.
1617     */
1618    protected void _saxExternalizableStringMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws AmetysRepositoryException, SAXException
1619    {
1620        if (!checkRead || _canRead(content, metadataDef))
1621        {
1622            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
1623            
1624            if (metadataDef.isMultiple())
1625            {
1626                List<String> localValues = new ArrayList<>();
1627                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1628                {
1629                    String[] values = ExternalizableMetadataHelper.getStringArray(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1630                    localValues = Arrays.asList(values);
1631                }
1632                
1633                List<String> extValues = new ArrayList<>();
1634                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1635                {
1636                    String[] values = ExternalizableMetadataHelper.getStringArray(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1637                    extValues = Arrays.asList(values);
1638                }
1639                
1640                _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValues, extValues);
1641            }
1642            else
1643            {
1644                String localValue = null;
1645                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1646                {
1647                    localValue = ExternalizableMetadataHelper.getString(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1648                }
1649                
1650                String extValue = null;
1651                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1652                {
1653                    extValue = ExternalizableMetadataHelper.getString(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1654                }
1655                _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
1656            }
1657        }
1658    }
1659    
1660    /**
1661     * SAX the jsonified values of an externalizable multilingual String metadata 
1662     * @param contentHandler The content handler 
1663     * @param content The content
1664     * @param parentMetadata The parent composite metadata
1665     * @param metadataDef The metadata definition
1666     * @param metadataName The metadata name
1667     * @param checkRead true if need to check read right
1668     * @throws AmetysRepositoryException if an error occurs.
1669     * @throws SAXException if an error occurs.
1670     */
1671    protected void _saxExternalisableMultilingualStringMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws AmetysRepositoryException, SAXException
1672    {
1673        if (!checkRead || _canRead(content, metadataDef))
1674        {
1675            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
1676            
1677            Map<String, Object> localValue = null;
1678            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1679            {
1680                localValue = _multilingualStringAsJson(ExternalizableMetadataHelper.getMultilingualString(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL));
1681            }
1682            
1683            Map<String, Object> extValue = null;
1684            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1685            {
1686                extValue = _multilingualStringAsJson(ExternalizableMetadataHelper.getMultilingualString(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL));
1687            }
1688            
1689            _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
1690        }
1691    }
1692    
1693    /**
1694     * Get the JSON representation of a {@link MultilingualString}
1695     * @param multilingualString The multilingual string
1696     * @return The MultilingualString as JSON
1697     */
1698    protected Map<String, Object> _multilingualStringAsJson (MultilingualString multilingualString)
1699    {
1700        return MultilingualStringHelper.toJson(multilingualString);
1701    }
1702    
1703    
1704    /**
1705     * SAX the jsonified values of an externalizable Date metadata 
1706     * @param contentHandler The content handler 
1707     * @param content The content
1708     * @param parentMetadata The parent composite metadata
1709     * @param metadataDef The metadata definition
1710     * @param metadataName The metadata name
1711     * @param checkRead true if need to check read right
1712     * @throws AmetysRepositoryException if an error occurs.
1713     * @throws SAXException if an error occurs.
1714     */
1715    protected void _saxExternalizableDateMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws AmetysRepositoryException, SAXException
1716    {
1717        if (!checkRead || _canRead(content, metadataDef))
1718        {
1719            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
1720            
1721            if (metadataDef.isMultiple())
1722            {
1723                List<String> localValuesAsStr = new ArrayList<>();
1724                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1725                {
1726                    Date[] localValues = ExternalizableMetadataHelper.getDateArray(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1727                    localValuesAsStr = Arrays.asList(localValues).stream().map(date -> DateUtils.dateToString(date)).collect(Collectors.toList());
1728                }
1729                
1730                List<String> extValuesAsStr = new ArrayList<>();
1731                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1732                {
1733                    Date[] extValues = ExternalizableMetadataHelper.getDateArray(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1734                    extValuesAsStr = Arrays.asList(extValues).stream().map(date -> DateUtils.dateToString(date)).collect(Collectors.toList());
1735                }
1736                _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValuesAsStr, extValuesAsStr);
1737            }
1738            else
1739            {
1740                Date localValue = null;
1741                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1742                {
1743                    localValue = ExternalizableMetadataHelper.getDate(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1744                }
1745                Date extValue = null;
1746                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1747                {
1748                    extValue = ExternalizableMetadataHelper.getDate(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1749                }
1750                _saxExternalizableValuesAsJson(contentHandler, metadataName, status, DateUtils.dateToString(localValue), DateUtils.dateToString(extValue));
1751            }
1752        }
1753    }
1754    
1755    /**
1756     * SAX the jsonified values of an externalizable Long metadata 
1757     * @param contentHandler The content handler 
1758     * @param content The content
1759     * @param parentMetadata The parent composite metadata
1760     * @param metadataDef The metadata definition
1761     * @param metadataName The metadata name
1762     * @param checkRead true if need to check read right
1763     * @throws AmetysRepositoryException if an error occurs.
1764     * @throws SAXException if an error occurs.
1765     */
1766    protected void _saxExternalizableLongMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws AmetysRepositoryException, SAXException
1767    {
1768        if (!checkRead || _canRead(content, metadataDef))
1769        {
1770            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
1771            
1772            if (metadataDef.isMultiple())
1773            {
1774                long[] localValues = null;
1775                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1776                {
1777                    localValues = ExternalizableMetadataHelper.getLongArray(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1778                }
1779                long[] extValues = null;
1780                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1781                {
1782                    extValues = ExternalizableMetadataHelper.getLongArray(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1783                }
1784                _saxExternalizableValuesAsJson(contentHandler, metadataName, status, Arrays.asList(localValues), Arrays.asList(extValues));
1785            }
1786            else
1787            {
1788                Long localValue = null;
1789                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1790                {
1791                    localValue = ExternalizableMetadataHelper.getLong(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1792                }
1793                Long extValue = null;
1794                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1795                {
1796                    extValue = ExternalizableMetadataHelper.getLong(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1797                }
1798                _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
1799            }
1800        }
1801    }
1802    
1803    /**
1804     * SAX the jsonified values of an externalizable Double metadata 
1805     * @param contentHandler The content handler 
1806     * @param content The content
1807     * @param parentMetadata The parent composite metadata
1808     * @param metadataDef The metadata definition
1809     * @param metadataName The metadata name
1810     * @param checkRead true if need to check read right
1811     * @throws AmetysRepositoryException if an error occurs.
1812     * @throws SAXException if an error occurs.
1813     */
1814    protected void _saxExternalizableDoubleMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws AmetysRepositoryException, SAXException
1815    {
1816        if (!checkRead || _canRead(content, metadataDef))
1817        {
1818            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
1819    
1820            if (metadataDef.isMultiple())
1821            {
1822                double[] localValues = null;
1823                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1824                {
1825                    localValues = ExternalizableMetadataHelper.getDoubleArray(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1826                }
1827                double[] extValues = null;
1828                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1829                {
1830                    extValues = ExternalizableMetadataHelper.getDoubleArray(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1831                }
1832                _saxExternalizableValuesAsJson(contentHandler, metadataName, status, Arrays.asList(localValues), Arrays.asList(extValues));
1833            }
1834            else
1835            {
1836                Double localValue = null;
1837                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1838                {
1839                    localValue = ExternalizableMetadataHelper.getDouble(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1840                }
1841                Double extValue = null;
1842                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1843                {
1844                    extValue = ExternalizableMetadataHelper.getDouble(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1845                }
1846                _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
1847            }
1848        }
1849    }
1850    
1851    /**
1852     * SAX the jsonified values of an externalizable Boolean metadata 
1853     * @param contentHandler The content handler 
1854     * @param content The content
1855     * @param parentMetadata The parent composite metadata
1856     * @param metadataDef The metadata definition
1857     * @param metadataName The metadata name
1858     * @param checkRead true if need to check read right
1859     * @throws AmetysRepositoryException if an error occurs.
1860     * @throws SAXException if an error occurs.
1861     */
1862    protected void _saxExternalizableBooleanMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws AmetysRepositoryException, SAXException
1863    {
1864        if (!checkRead || _canRead(content, metadataDef))
1865        {
1866            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
1867            
1868            if (metadataDef.isMultiple())
1869            {
1870                boolean[] localValues = null;
1871                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1872                {
1873                    localValues = ExternalizableMetadataHelper.getBooleanArray(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1874                }
1875                
1876                boolean[] extValues = null;
1877                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1878                {
1879                    extValues = ExternalizableMetadataHelper.getBooleanArray(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1880                    _saxExternalizableValuesAsJson(contentHandler, metadataName, status, Arrays.asList(localValues), Arrays.asList(extValues));
1881                }
1882            }
1883            else
1884            {
1885                Boolean localValue = null;
1886                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1887                {
1888                    localValue = ExternalizableMetadataHelper.getBoolean(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1889                }
1890                Boolean extValue = null;
1891                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1892                {
1893                    extValue = ExternalizableMetadataHelper.getBoolean(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1894                }
1895                
1896                _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
1897            }
1898        }
1899    }
1900    
1901    /**
1902     * SAX the jsonified values of an externalizable Reference metadata 
1903     * @param contentHandler The content handler 
1904     * @param content The content
1905     * @param parentMetadata The parent composite metadata
1906     * @param metadataDef The metadata definition
1907     * @param metadataName The metadata name
1908     * @param checkRead true if need to check read right
1909     * @throws AmetysRepositoryException if an error occurs.
1910     * @throws SAXException if an error occurs.
1911     */
1912    protected void _saxExternalisableReferenceMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws SAXException
1913    {
1914        if (!checkRead || _canRead(content, metadataDef))
1915        {
1916            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
1917            
1918            Map<String, Object> localValue = null;
1919            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1920            {
1921                CompositeMetadata localRef = ExternalizableMetadataHelper.getCompositeMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1922                localValue = _referenceAsJson(localRef);
1923            }
1924            
1925            Map<String, Object> extValue = null;
1926            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1927            {
1928                CompositeMetadata extRef = ExternalizableMetadataHelper.getCompositeMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1929                extValue = _referenceAsJson(extRef);
1930            }
1931            
1932            _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
1933        }
1934    }
1935    
1936    /**
1937     * SAX the jsonified values of an externalizable geocode metadata 
1938     * @param contentHandler The content handler 
1939     * @param content The content
1940     * @param parentMetadata The parent composite metadata
1941     * @param metadataDef The metadata definition
1942     * @param metadataName The metadata name
1943     * @param checkRead true if need to check read right
1944     * @throws AmetysRepositoryException if an error occurs.
1945     * @throws SAXException if an error occurs.
1946     */
1947    protected void _saxExternalisableGeocodeMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws SAXException
1948    {
1949        if (!checkRead || _canRead(content, metadataDef))
1950        {
1951            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
1952            
1953            Map<String, Object> localValues = null;
1954            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1955            {
1956                CompositeMetadata localGeoCode = ExternalizableMetadataHelper.getCompositeMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1957                localValues = _geocodeAsJson(localGeoCode);
1958            }
1959            
1960            Map<String, Object> extValues = null;
1961            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1962            {
1963                CompositeMetadata extGeoCode = ExternalizableMetadataHelper.getCompositeMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1964                extValues = _geocodeAsJson(extGeoCode);
1965            }
1966            
1967            _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValues, extValues);
1968        }
1969    }
1970    
1971    /**
1972     * SAX the jsonified values of an externalizable geocode metadata 
1973     * @param contentHandler The content handler 
1974     * @param content The content
1975     * @param parentMetadata The parent composite metadata
1976     * @param metadataDef The metadata definition
1977     * @param metadataName The metadata name
1978     * @param checkRead true if need to check read right
1979     * @throws AmetysRepositoryException if an error occurs.
1980     * @throws SAXException if an error occurs.
1981     */
1982    protected void _saxExternalisableObjectCollectionMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws SAXException
1983    {
1984        if (!checkRead || _canRead(content, metadataDef))
1985        {
1986            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
1987            
1988            List<String> localValues = new ArrayList<>();
1989            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
1990            {
1991                TraversableAmetysObject localCollection = ExternalizableMetadataHelper.getObjectCollection(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
1992                localValues = _getRefAmetysObjectIds(localCollection);
1993            }
1994            
1995            List<String> extValues = new ArrayList<>();
1996            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
1997            {
1998                TraversableAmetysObject extCollection = ExternalizableMetadataHelper.getObjectCollection(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
1999                extValues = _getRefAmetysObjectIds(extCollection);
2000            }
2001            
2002            _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValues, extValues);
2003        }
2004    }
2005    
2006    /**
2007     * SAX the jsonified values of an externalizable File metadata 
2008     * @param contentHandler The content handler 
2009     * @param content The content
2010     * @param parentMetadata The parent composite metadata
2011     * @param metadataDef The metadata definition
2012     * @param metadataName The metadata name
2013     * @param prefix The prefix
2014     * @param checkRead true if need to check read right
2015     * @throws AmetysRepositoryException if an error occurs.
2016     * @throws SAXException if an error occurs.
2017     */
2018    protected void _saxExternalisableFileMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, String prefix, boolean checkRead) throws AmetysRepositoryException, SAXException
2019    {
2020        if (!checkRead || _canRead(content, metadataDef))
2021        {
2022            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
2023            
2024            Map<String, Object> localValue = new HashMap<>();
2025            Map<String, Object> extValue = new HashMap<>();
2026            
2027            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
2028            {
2029                if (org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.BINARY.equals(ExternalizableMetadataHelper.getType(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL)))
2030                {
2031                    BinaryMetadata localBinary = ExternalizableMetadataHelper.getBinaryMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
2032                    localValue = _binaryAsJson(content, localBinary, prefix, ExternalizableMetadataHelper.getMetadataName(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL));
2033                }
2034                else
2035                {
2036                    String id = ExternalizableMetadataHelper.getString(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
2037                    try
2038                    {
2039                        Resource resource = (Resource) _resolver.resolveById(id);
2040                        localValue = _resourceAsJson(resource);
2041                    }
2042                    catch (AmetysRepositoryException e)
2043                    {
2044                        getLogger().warn("The resource of id '" + id + "' does not exist.", e);
2045                    }
2046                }
2047                
2048            }
2049            
2050            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
2051            {
2052                if (org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType.BINARY.equals(ExternalizableMetadataHelper.getType(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL)))
2053                {
2054                    BinaryMetadata localBinary = ExternalizableMetadataHelper.getBinaryMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
2055                    extValue = _binaryAsJson(content, localBinary, prefix, ExternalizableMetadataHelper.getMetadataName(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL));
2056                }
2057                else
2058                {
2059                    String id = ExternalizableMetadataHelper.getString(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
2060                    try
2061                    {
2062                        Resource resource = (Resource) _resolver.resolveById(id);
2063                        extValue = _resourceAsJson(resource);
2064                    }
2065                    catch (AmetysRepositoryException e)
2066                    {
2067                        getLogger().warn("The resource of id '" + id + "' does not exist.", e);
2068                    }
2069                }
2070            }
2071            
2072            _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
2073        }
2074        
2075    }
2076    
2077    /**
2078     * SAX the jsonified values of an externalizable resource file metadata 
2079     * @param contentHandler The content handler 
2080     * @param content The content
2081     * @param parentMetadata The parent composite metadata
2082     * @param metadataDef The metadata definition
2083     * @param metadataName The metadata name
2084     * @param checkRead true if need to check read right
2085     * @throws AmetysRepositoryException if an error occurs.
2086     * @throws SAXException if an error occurs.
2087     */
2088    protected void _saxExternalisableResourceFileMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws AmetysRepositoryException, SAXException
2089    {
2090        if (!checkRead || _canRead(content, metadataDef))
2091        {
2092            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
2093            
2094            Map<String, Object> localValue = new HashMap<>();
2095            Map<String, Object> extValue = new HashMap<>();
2096            
2097            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
2098            {
2099                String id = ExternalizableMetadataHelper.getString(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
2100                try
2101                {
2102                    Resource resource = (Resource) _resolver.resolveById(id);
2103                    localValue = _resourceAsJson(resource);
2104                }
2105                catch (AmetysRepositoryException e)
2106                {
2107                    getLogger().warn("The resource of id '" + id + "' does not exist.", e);
2108                }
2109            }
2110            
2111            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
2112            {
2113                String id = ExternalizableMetadataHelper.getString(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
2114                try
2115                {
2116                    Resource resource = (Resource) _resolver.resolveById(id);
2117                    extValue = _resourceAsJson(resource);
2118                }
2119                catch (AmetysRepositoryException e)
2120                {
2121                    getLogger().warn("The resource of id '" + id + "' does not exist.", e);
2122                }
2123            }
2124            
2125            _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
2126        }
2127    }
2128    
2129    /**
2130     * SAX the jsonified values of an externalizable resource file metadata 
2131     * @param contentHandler The content handler 
2132     * @param content The content
2133     * @param parentMetadata The parent composite metadata
2134     * @param metadataDef The metadata definition
2135     * @param metadataName The metadata name
2136     * @param checkRead true if need to check read right
2137     * @throws AmetysRepositoryException if an error occurs.
2138     * @throws SAXException if an error occurs.
2139     */
2140    protected void _saxExternalisableRichTextMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws AmetysRepositoryException, SAXException
2141    {
2142        if (!checkRead || _canRead(content, metadataDef))
2143        {
2144            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
2145            
2146            String localValue = null;
2147            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
2148            {
2149                RichText localRichText = ExternalizableMetadataHelper.getRichText(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
2150                if (!(localRichText instanceof ModifiableRichText))
2151                {
2152                    throw new SAXException("The rich text '" + metadataDef.getId() + "' is not a modifiable rich text");
2153                } 
2154                localValue = _richTextAsString((ModifiableRichText) localRichText, metadataDef);
2155            }
2156            
2157            String extValue = null;
2158            if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
2159            {
2160                RichText extRichText = ExternalizableMetadataHelper.getRichText(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
2161                if (!(extRichText instanceof ModifiableRichText))
2162                {
2163                    throw new SAXException("The rich text '" + metadataDef.getId() + "' is not a modifiable rich text");
2164                }
2165                extValue = _richTextAsString((ModifiableRichText) extRichText, metadataDef);
2166            }
2167            
2168            _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
2169        }
2170    }
2171    
2172    /**
2173     * SAX the jsonified values of an externalizable geocode metadata 
2174     * @param contentHandler The content handler 
2175     * @param content The content
2176     * @param parentMetadata The parent composite metadata
2177     * @param metadataDef The metadata definition
2178     * @param metadataName The metadata name
2179     * @param checkRead true if need to check read right
2180     * @throws AmetysRepositoryException if an error occurs.
2181     * @throws SAXException if an error occurs.
2182     */
2183    protected void _saxExternalisableUserMetadata(ContentHandler contentHandler, Content content, CompositeMetadata parentMetadata, MetadataDefinition metadataDef, String metadataName, boolean checkRead) throws SAXException
2184    {
2185        if (!checkRead || _canRead(content, metadataDef))
2186        {
2187            ExternalizableMetadataStatus status = ExternalizableMetadataHelper.getStatus(parentMetadata, metadataName);
2188            
2189            if (metadataDef.isMultiple())
2190            {
2191                List<Map<String, Object>> localValues = new ArrayList<>();
2192                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
2193                {
2194                    UserIdentity[] localUsers = ExternalizableMetadataHelper.getUserArray(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
2195                    for (UserIdentity userIdentity : localUsers)
2196                    {
2197                        localValues.add(_userAsJson(userIdentity));
2198                    }
2199                }
2200                
2201                List<Map<String, Object>> extValues = new ArrayList<>();
2202                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
2203                {
2204                    UserIdentity[] extUsers = ExternalizableMetadataHelper.getUserArray(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
2205                    for (UserIdentity userIdentity : extUsers)
2206                    {
2207                        extValues.add(_userAsJson(userIdentity));
2208                    }
2209                }
2210                _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValues, extValues);
2211            }
2212            else
2213            {
2214                Map<String, Object> localValue = null;
2215                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL))
2216                {
2217                    UserIdentity localUser = ExternalizableMetadataHelper.getUser(parentMetadata, metadataName, ExternalizableMetadataStatus.LOCAL);
2218                    localValue = _userAsJson(localUser);
2219                }
2220                
2221                Map<String, Object> extValue = null;
2222                if (ExternalizableMetadataHelper.hasMetadata(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL))
2223                {
2224                    UserIdentity extUser = ExternalizableMetadataHelper.getUser(parentMetadata, metadataName, ExternalizableMetadataStatus.EXTERNAL);
2225                    extValue = _userAsJson(extUser);
2226                }
2227                _saxExternalizableValuesAsJson(contentHandler, metadataName, status, localValue, extValue);
2228            }
2229        }       
2230    }
2231    
2232    /**
2233     * SAX externalizable values as JSON
2234     * @param contentHandler The content handler
2235     * @param metadataName The metadata name
2236     * @param status The status
2237     * @param localValue The local value
2238     * @param extValue The external value
2239     * @throws SAXException if an error occurs.
2240     */
2241    protected void _saxExternalizableValuesAsJson (ContentHandler contentHandler, String metadataName, ExternalizableMetadataStatus status, Object localValue, Object extValue) throws SAXException
2242    {
2243        Map<String, Object> values = new LinkedHashMap<>();
2244
2245        values.put("status", status.name().toLowerCase());
2246        if (localValue != null)
2247        {
2248            values.put("local", localValue);
2249        }
2250        if (extValue != null)
2251        {
2252            values.put("external", extValue);
2253        }
2254        
2255        String jsonString = _jsonUtils.convertObjectToJson(values);
2256
2257        AttributesImpl attrs = new AttributesImpl();
2258        attrs.addCDATAAttribute("json", "true");
2259        XMLUtils.createElement(contentHandler, metadataName, attrs, jsonString);
2260    }
2261    
2262    /**
2263     * Get the JSON representation of a {@link ModifiableRichText}
2264     * @param richText The rich text
2265     * @param metadataDef The metadata definition
2266     * @return The resource as JSON
2267     */
2268    protected String _richTextAsString (ModifiableRichText richText, MetadataDefinition metadataDef)
2269    {
2270        RichTextTransformer richTextTransformer = metadataDef.getRichTextTransformer();
2271        StringBuilder result = new StringBuilder(2048);
2272        
2273        try
2274        {
2275            richTextTransformer.transformForEditing(richText, result);
2276        }
2277        catch (IOException e)
2278        {
2279            throw new AmetysRepositoryException("Unable to transform a rich text into a string", e);
2280        }
2281        
2282        return result.toString();
2283    }
2284    
2285    /**
2286     * Determines if a metadata can be read
2287     * @param content the content
2288     * @param metadataDef the metadata definition.
2289     * @return <code>true</code> if the metadata can be read
2290     * @throws AmetysRepositoryException If an error occured
2291     */
2292    protected boolean _canRead(Content content, MetadataDefinition metadataDef) throws AmetysRepositoryException
2293    {
2294        return _contentTypesHelper.canRead(content, metadataDef);
2295    }
2296    
2297    /**
2298     * Compares repeater entry names to sort them.
2299     */
2300    protected static class RepeaterEntryComparator implements Comparator<String>
2301    {
2302        @Override
2303        public int compare(String s1, String s2)
2304        {
2305            try
2306            {
2307                Integer i1 = new Integer(s1);
2308                Integer i2 = new Integer(s2);
2309                
2310                return i1.compareTo(i2);
2311            }
2312            catch (NumberFormatException e) 
2313            {
2314                return s1.compareTo(s2);
2315            }
2316        }
2317    }
2318    
2319}