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