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