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.plugins.repository.metadata;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.util.ArrayList;
021import java.util.Date;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Locale;
025import java.util.Map;
026
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.ContextException;
030import org.apache.avalon.framework.context.Contextualizable;
031import org.apache.avalon.framework.logger.AbstractLogEnabled;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035import org.apache.cocoon.xml.AttributesImpl;
036import org.apache.cocoon.xml.XMLUtils;
037import org.apache.commons.io.IOUtils;
038import org.apache.commons.lang.StringUtils;
039import org.xml.sax.ContentHandler;
040import org.xml.sax.SAXException;
041
042import org.ametys.core.user.User;
043import org.ametys.core.user.UserIdentity;
044import org.ametys.core.user.UserManager;
045import org.ametys.core.util.DateUtils;
046import org.ametys.core.util.JSONUtils;
047import org.ametys.plugins.repository.AmetysObject;
048import org.ametys.plugins.repository.AmetysObjectIterable;
049import org.ametys.plugins.repository.AmetysObjectResolver;
050import org.ametys.plugins.repository.AmetysRepositoryException;
051import org.ametys.plugins.repository.TraversableAmetysObject;
052import org.ametys.plugins.repository.data.holder.DataHolder;
053import org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType;
054import org.ametys.runtime.i18n.I18nizableText;
055import org.ametys.runtime.parameter.Enumerator;
056
057/**
058 * Component for helping SAXing metadata.
059 * @deprecated Use {@link DataHolder#dataToSAX} methods instead of this helper
060 */
061@Deprecated
062public class MetadataSaxer extends AbstractLogEnabled implements Component, Serviceable, Contextualizable
063{
064    /** The Avalon Role. */
065    public static final String ROLE = MetadataSaxer.class.getName();
066    
067    /** The JSON conversion utilities. */
068    protected JSONUtils _jsonUtils;
069    /** Ametys object resolver */
070    protected AmetysObjectResolver _resolver;
071    /** The avalon context. */
072    protected Context _context;
073    /** The user manager */
074    protected UserManager _userManager;
075    
076    @Override
077    public void contextualize(Context context) throws ContextException
078    {
079        _context = context;
080    }
081    
082    @Override
083    public void service(ServiceManager manager) throws ServiceException
084    {
085        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
086        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
087        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
088    }
089    
090    /**
091     * SAXes a composite metadata.
092     * @param contentHandler the content handler where to SAX into.
093     * @param compositeMetadata the composite metadata.
094     * @throws AmetysRepositoryException if an error occurs.
095     * @throws SAXException if an error occurs.
096     * @throws IOException if an error occurs.
097     */
098    public void saxMetadata(ContentHandler contentHandler, CompositeMetadata compositeMetadata) throws AmetysRepositoryException, SAXException, IOException
099    {
100        _saxAllMetadata(contentHandler, compositeMetadata, "", null);
101    }
102    
103    /**
104     * SAXes a composite metadata.
105     * @param contentHandler the content handler where to SAX into.
106     * @param compositeMetadata the composite metadata.
107     * @param locale The locale to use for localized metadata, such as {@link MultilingualString}. Can be null to sax all existing locales.
108     * @throws AmetysRepositoryException if an error occurs.
109     * @throws SAXException if an error occurs.
110     * @throws IOException if an error occurs.
111     */
112    public void saxMetadata(ContentHandler contentHandler, CompositeMetadata compositeMetadata, Locale locale) throws AmetysRepositoryException, SAXException, IOException
113    {
114        _saxAllMetadata(contentHandler, compositeMetadata, "", locale);
115    }
116    
117    /**
118     * SAX all the metadata of a given composite metadata, with the given prefix.
119     * @param contentHandler the content handler where to SAX into.
120     * @param metadata the composite metadata.
121     * @param prefix the metadata path prefix.
122     * @param locale The locale to use for localized metadata, such as {@link MultilingualString}. Can be null to sax all existing locales.
123     * @throws AmetysRepositoryException if an error occurs.
124     * @throws SAXException if an error occurs.
125     * @throws IOException if an error occurs.
126     */
127    protected void _saxAllMetadata(ContentHandler contentHandler, CompositeMetadata metadata, String prefix, Locale locale) throws AmetysRepositoryException, SAXException, IOException
128    {
129        for (String metadataName : metadata.getMetadataNames())
130        {
131            if (metadata.hasMetadata(metadataName))
132            {
133                _saxMetadata(contentHandler, metadata, metadataName, prefix, locale);
134            }
135        }
136    }
137    
138    /**
139     * SAX a single metadata.
140     * @param contentHandler the content handler where to SAX into.
141     * @param parentMetadata the parent composite metadata.
142     * @param metadataName the metadata name.
143     * @param prefix the metadata path prefix.
144     * @param locale The locale to use for localized metadata, such as {@link MultilingualString}. Can be null to sax all existing locales.
145     * @throws AmetysRepositoryException if an error occurs.
146     * @throws SAXException if an error occurs.
147     * @throws IOException if an error occurs.
148     */
149    protected void _saxMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix, Locale locale) throws AmetysRepositoryException, SAXException, IOException
150    {
151        if (parentMetadata.hasMetadata(metadataName))
152        {
153            MetadataType metadataType = parentMetadata.getType(metadataName);
154            
155            switch (metadataType)
156            {
157                case COMPOSITE:
158                    CompositeMetadata subMetadata = parentMetadata.getCompositeMetadata(metadataName);
159                    
160                    XMLUtils.startElement(contentHandler, metadataName);
161                    
162                    // SAX all metadata contains in the current CompositeMetadata.
163                    _saxAllMetadata(contentHandler, subMetadata, prefix + metadataName + "/", locale);
164                    
165                    XMLUtils.endElement(contentHandler, metadataName);
166                    break;
167                case USER:
168                    _saxUserMetadata(contentHandler, parentMetadata, metadataName);
169                    break;
170                case BINARY:
171                    _saxBinaryMetadata(contentHandler, parentMetadata, metadataName, prefix);
172                    break;
173                    
174                case RICHTEXT:
175                    _saxRichTextMetadata(contentHandler, parentMetadata, metadataName, prefix);
176                    break;
177                    
178                case DATE:
179                    _saxDateMetadata(contentHandler, parentMetadata, metadataName, prefix);
180                    break;
181                    
182                case OBJECT_COLLECTION:
183                    TraversableAmetysObject objectCollection = parentMetadata.getObjectCollection(metadataName);
184                    
185                    XMLUtils.startElement(contentHandler, metadataName);
186                    
187                    // SAX all sub-objects.
188                    for (AmetysObject subObject : objectCollection.getChildren())
189                    {
190                        _saxObject(contentHandler, subObject, prefix + metadataName + "/", false, locale);
191                    }
192                    
193                    XMLUtils.endElement(contentHandler, metadataName);
194                    break;
195                    
196                case MULTILINGUAL_STRING:
197                    _saxMultilingualStringMetadata(contentHandler, parentMetadata, metadataName, prefix, locale);
198                    break;
199                default:
200                    _saxStringMetadata(contentHandler, parentMetadata, metadataName, prefix);
201                    break;
202            }
203        }
204    }
205    
206    /**
207     * SAX a binary metadata.
208     * @param contentHandler the content handler where to SAX into.
209     * @param parentMetadata the parent composite metadata.
210     * @param metadataName the metadata name.
211     * @param prefix the metadata path prefix.
212     * @throws AmetysRepositoryException if an error occurs.
213     * @throws SAXException if an error occurs.
214     */
215    protected void _saxBinaryMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix) throws AmetysRepositoryException, SAXException
216    {
217        BinaryMetadata value = parentMetadata.getBinaryMetadata(metadataName);
218        String filename = value.getFilename();
219        AttributesImpl attrs = new AttributesImpl();
220        attrs.addCDATAAttribute("type", "attribute");
221        attrs.addCDATAAttribute("mime-type", value.getMimeType());
222        attrs.addCDATAAttribute("path", prefix + metadataName);
223        
224        if (filename != null)
225        {
226            attrs.addCDATAAttribute("filename", filename);
227        }
228        attrs.addCDATAAttribute("size", String.valueOf(value.getLength()));
229        attrs.addCDATAAttribute("lastModified", DateUtils.dateToString(value.getLastModified()));
230        
231        XMLUtils.createElement(contentHandler, metadataName, attrs);
232    }
233    
234    /**
235     * SAX a rich-text metadata.
236     * @param contentHandler the content handler where to SAX into.
237     * @param parentMetadata the parent composite metadata.
238     * @param metadataName the metadata name.
239     * @param prefix the metadata path prefix.
240     * @throws AmetysRepositoryException if an error occurs.
241     * @throws SAXException if an error occurs.
242     * @throws IOException if an error occurs.
243     */
244    protected void _saxRichTextMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix) throws AmetysRepositoryException, SAXException, IOException
245    {
246        RichText richText = parentMetadata.getRichText(metadataName);
247        AttributesImpl attrs = new AttributesImpl();
248        
249        attrs.addCDATAAttribute("mime-type", richText.getMimeType());
250        attrs.addCDATAAttribute("lastModified", DateUtils.dateToString(richText.getLastModified()));
251        
252        XMLUtils.startElement(contentHandler, metadataName, attrs);
253        
254        String encoding = richText.getEncoding();
255        
256        try (InputStream is = richText.getInputStream())
257        {
258            XMLUtils.data(contentHandler, IOUtils.toString(is, encoding));
259        }
260        
261        XMLUtils.endElement(contentHandler, metadataName);
262    }
263    
264    /**
265     * SAX a string metadata.
266     * @param contentHandler the content handler where to SAX into.
267     * @param parentMetadata the parent composite metadata.
268     * @param metadataName the metadata name.
269     * @param prefix the metadata path prefix.
270     * @throws AmetysRepositoryException if an error occurs.
271     * @throws SAXException if an error occurs.
272     */
273    protected void _saxStringMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix) throws AmetysRepositoryException, SAXException
274    {
275        String[] values = parentMetadata.getStringArray(metadataName, new String[0]);
276        
277        for (String value : values)
278        {
279            XMLUtils.createElement(contentHandler, metadataName, value);
280        }
281    }
282    
283    /**
284     * SAX a multilingual string metadata.
285     * @param contentHandler the content handler where to SAX into.
286     * @param parentMetadata the parent composite metadata.
287     * @param metadataName the metadata name.
288     * @param prefix the metadata path prefix
289     * @param currentLocale The current locale. Can be null.
290     * @throws AmetysRepositoryException if an error occurs.
291     * @throws SAXException if an error occurs.
292     */
293    protected void _saxMultilingualStringMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix, Locale currentLocale) throws AmetysRepositoryException, SAXException
294    {
295        if (parentMetadata.hasMetadata(metadataName))
296        {
297            MultilingualString multilingualString = parentMetadata.getMultilingualString(metadataName);
298            MultilingualStringHelper.sax(contentHandler, metadataName, multilingualString, currentLocale);
299        }
300    }
301    
302    /**
303     * SAX a string metadata.
304     * @param contentHandler the content handler where to SAX into.
305     * @param parentMetadata the parent composite metadata.
306     * @param metadataName the metadata name.
307     * @param enumerator The enumerator for labelisable values
308     * @throws AmetysRepositoryException if an error occurs.
309     * @throws SAXException if an error occurs.
310     */
311    protected void _saxEnumeratedStringMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, Enumerator enumerator) throws AmetysRepositoryException, SAXException
312    {
313        String[] values = parentMetadata.getStringArray(metadataName, new String[0]);
314        
315        for (String value : values)
316        {
317            if (StringUtils.isEmpty(value))
318            {
319                XMLUtils.createElement(contentHandler, metadataName);
320            }
321            else
322            {
323                AttributesImpl attrs = new AttributesImpl();
324                attrs.addCDATAAttribute("value", value);
325                
326                try
327                {
328                    I18nizableText i18n = enumerator.getEntry(value);
329                    if (i18n != null)
330                    {
331                        XMLUtils.startElement(contentHandler, metadataName, attrs);
332                        i18n.toSAX(contentHandler);
333                        XMLUtils.endElement(contentHandler, metadataName);
334                    }
335                    else
336                    {
337                        XMLUtils.createElement(contentHandler, metadataName, attrs, value);
338                    }
339                }
340                catch (Exception e)
341                {
342                    getLogger().warn("Saxing enumerated String metadata '" + metadataName + "' required a label for enumerated value", e);
343                    XMLUtils.createElement(contentHandler, metadataName, attrs, value);
344                }
345            }
346            
347        }
348    }
349    
350    /**
351     * SAX a metadata as a Date.
352     * @param contentHandler the content handler where to SAX into.
353     * @param parentMetadata the parent composite metadata.
354     * @param metadataName the metadata name.
355     * @param prefix the metadata path prefix.
356     * @throws AmetysRepositoryException if an error occurs.
357     * @throws SAXException if an error occurs.
358     */
359    protected void _saxDateMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName, String prefix) throws AmetysRepositoryException, SAXException
360    {
361        Date[] values = parentMetadata.getDateArray(metadataName, new Date[0]);
362        
363        for (Date value : values)
364        {
365            XMLUtils.createElement(contentHandler, metadataName, DateUtils.dateToString(value));
366        }
367    }
368    
369    /**
370     * SAX a metadata as a User.
371     * @param contentHandler the content handler where to SAX into.
372     * @param parentMetadata the parent composite metadata.
373     * @param metadataName the metadata name.
374     * @throws SAXException if an error occurs
375     */
376    protected void _saxUserMetadata(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName) throws SAXException
377    {
378        UserIdentity[] values = parentMetadata.getUserArray(metadataName);
379        
380        for (UserIdentity userIdentity : values)
381        {
382            User user = _userManager.getUser(userIdentity);
383            if (user != null)
384            {
385                AttributesImpl attrs = new AttributesImpl();
386                attrs.addCDATAAttribute("login", userIdentity.getLogin());
387                attrs.addCDATAAttribute("populationId", userIdentity.getPopulationId());
388                attrs.addCDATAAttribute("email", user.getEmail());
389                XMLUtils.createElement(contentHandler, metadataName, attrs, user.getFullName());
390            }
391        }
392    }
393
394    /**
395     * SAX a metadata as a User as JSON.
396     * @param contentHandler the content handler where to SAX into.
397     * @param parentMetadata the parent composite metadata.
398     * @param metadataName the metadata name.
399     * @throws SAXException if an error occurs
400     */
401    protected void _saxSingleUserMetadataAsJson(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName) throws SAXException
402    {
403        UserIdentity userIdentity = parentMetadata.getUser(metadataName);
404        
405        Map<String, Object> values = _userAsJson(userIdentity);
406        AttributesImpl attrs = new AttributesImpl();
407        attrs.addCDATAAttribute("json", "true");
408        
409        String jsonString = _jsonUtils.convertObjectToJson(values);
410        XMLUtils.createElement(contentHandler, metadataName, attrs, jsonString);
411    }
412    
413    /**
414     * SAX a metadata as a User as JSON.
415     * @param contentHandler the content handler where to SAX into.
416     * @param parentMetadata the parent composite metadata.
417     * @param metadataName the metadata name.
418     * @throws SAXException if an error occurs
419     */
420    protected void _saxMultipleUserMetadataAsJson(ContentHandler contentHandler, CompositeMetadata parentMetadata, String metadataName) throws SAXException
421    {
422        UserIdentity[] userIdentities = parentMetadata.getUserArray(metadataName);
423        
424        List<Map<String, Object>> values = new ArrayList<>();
425        for (UserIdentity userIdentity : userIdentities)
426        {
427            values.add(_userAsJson(userIdentity));
428        }
429        
430        AttributesImpl attrs = new AttributesImpl();
431        attrs.addCDATAAttribute("json", "true");
432        
433        String jsonString = _jsonUtils.convertObjectToJson(values);
434        XMLUtils.createElement(contentHandler, metadataName, attrs, jsonString);
435    }
436    
437    /**
438     * SAX an Object, i.e. its ID and name, and optionally its metadata. 
439     * @param contentHandler the content handler where to SAX into.
440     * @param object the {@link AmetysObject} to sax.
441     * @param prefix the metadata path prefix.
442     * @param isDeep If <code>true</code> and the AmetysObject is a {@link MetadataAwareAmetysObject}, its metadata will be saxed.
443     * @param locale The locale to use for localized metadata, such as {@link MultilingualString}. Can be null to sax all existing locales.
444     * @throws AmetysRepositoryException if an error occurs.
445     * @throws SAXException if an error occurs.
446     * @throws IOException if an error occurs.
447     */
448    protected void _saxObject(ContentHandler contentHandler, AmetysObject object, String prefix, boolean isDeep, Locale locale)  throws AmetysRepositoryException, IOException, SAXException
449    {
450        AttributesImpl attrs = new AttributesImpl();
451        attrs.addCDATAAttribute("id", object.getId());
452        attrs.addCDATAAttribute("name", object.getName());
453        
454        XMLUtils.startElement(contentHandler, "object", attrs);
455        
456        if (object instanceof MetadataAwareAmetysObject && isDeep)
457        {
458            CompositeMetadata metaHolder = ((MetadataAwareAmetysObject) object).getMetadataHolder();
459            
460            _saxAllMetadata(contentHandler, metaHolder, prefix, locale);
461        }
462        
463        XMLUtils.endElement(contentHandler, "object");
464    }
465    
466    /**
467     * Get the id of child Ametys objects
468     * @param objectCollection The parent object collection
469     * @return The id of child Ametys objects
470     */
471    protected List<String> _getRefAmetysObjectIds (TraversableAmetysObject objectCollection)
472    {
473        List<String> refAOs = new ArrayList<>();
474        
475        try (AmetysObjectIterable<AmetysObject> children = objectCollection.getChildren())
476        {
477            for (AmetysObject refAO : children)
478            {
479                refAOs.add(refAO.getId());
480            }
481        }
482        
483        return refAOs;
484    }
485    
486    /**
487     * Get the JSON representation of a User metadata
488     * @param userIdentity The user identity
489     * @return The user as JSON
490     */
491    protected Map<String, Object> _userAsJson (UserIdentity userIdentity)
492    {
493        Map<String, Object> json = new LinkedHashMap<>();
494        
495        json.put("login", userIdentity.getLogin());
496        json.put("populationId", userIdentity.getPopulationId());
497        
498        return json;
499    }
500    
501    /**
502     * Get the JSON representation of a {@link BinaryMetadata}
503     * @param binaryMetadata The metadata
504     * @param prefix The prefix
505     * @param metadataName The metadata name
506     * @return The binary as JSON
507     */
508    protected Map<String, Object> _binaryAsJson (BinaryMetadata binaryMetadata, String prefix, String metadataName)
509    {
510        Map<String, Object> json = new LinkedHashMap<>();
511        
512        String filename = binaryMetadata.getFilename();
513            
514        json.put("type", "attribute");
515        json.put("mimeType", binaryMetadata.getMimeType());
516        json.put("path", prefix + metadataName);
517            
518        if (filename != null)
519        {
520            json.put("filename", filename);
521        }
522        json.put("size", String.valueOf(binaryMetadata.getLength()));
523        json.put("lastModified", DateUtils.dateToString(binaryMetadata.getLastModified()));
524            
525        return json;
526    }
527}