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.content;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.io.Serializable;
021import java.net.URLDecoder;
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.avalon.framework.parameters.ParameterException;
029import org.apache.avalon.framework.parameters.Parameters;
030import org.apache.cocoon.ProcessingException;
031import org.apache.cocoon.caching.CacheableProcessingComponent;
032import org.apache.cocoon.environment.ObjectModelHelper;
033import org.apache.cocoon.environment.Request;
034import org.apache.cocoon.environment.Response;
035import org.apache.cocoon.environment.SourceResolver;
036import org.apache.cocoon.reading.AbstractReader;
037import org.apache.commons.io.IOUtils;
038import org.apache.excalibur.source.SourceValidity;
039import org.apache.excalibur.source.impl.validity.TimeStampValidity;
040import org.xml.sax.SAXException;
041
042import org.ametys.cms.repository.Content;
043import org.ametys.core.util.ImageHelper;
044import org.ametys.plugins.repository.metadata.CompositeMetadata;
045import org.ametys.plugins.repository.metadata.File;
046import org.ametys.plugins.repository.metadata.Folder;
047import org.ametys.plugins.repository.metadata.RichText;
048
049/**
050 * Reader for binary or file metadata.
051 */
052public class ContentFileReader extends AbstractReader implements CacheableProcessingComponent
053{
054    private Content _content;
055    private File _file;
056    
057    private String _path;
058    
059    private int _width;
060    private int _height;
061    private int _maxWidth;
062    private int _maxHeight;
063    
064    private boolean _readForDownload;
065    private Collection<String> _allowedFormats = Arrays.asList(new String[]{"png", "gif", "jpg", "jpeg"});
066    
067    @Override
068    public void setup(SourceResolver res, Map objModel, String src, Parameters par) throws ProcessingException, SAXException, IOException
069    {
070        super.setup(res, objModel, src, par);
071        
072        Request request = ObjectModelHelper.getRequest(objectModel);
073        _content = (Content) request.getAttribute(Content.class.getName());
074        
075        String metadataName = parameters.getParameter("metadata", null);
076        RichText richText = _getMeta(_content.getMetadataHolder(), metadataName);
077        
078        Folder folder = richText.getAdditionalDataFolder();
079        
080        try
081        {
082            _path = parameters.getParameter("path");
083        }
084        catch (ParameterException e)
085        {
086            throw new ProcessingException("The path parameter is mandatory for reading binary metadata.", e);
087        }
088        
089        List<String> pathElements = Arrays.asList(_path.split("/"));
090        
091        Iterator<String> it = pathElements.iterator();
092        
093        while (it.hasNext())
094        {
095            String pathElement = it.next();
096            pathElement = URLDecoder.decode(pathElement, "utf-8");
097            
098            if (it.hasNext())
099            {
100                // not the last segment : it is a composite
101                folder = folder.getFolder(pathElement);
102            }
103            else
104            {
105                File file = folder.getFile(pathElement);
106                _file = file;
107            }
108        }
109        
110        _readForDownload = par.getParameterAsBoolean("download", false);
111        Response response = ObjectModelHelper.getResponse(objectModel);
112
113        if (_readForDownload)
114        {
115            response.setHeader("Content-Disposition", "attachment; filename=\"" + _file.getName() + "\"");
116        }
117
118        // parameters for image resizing
119        _width = par.getParameterAsInteger("width", 0);
120        _height = par.getParameterAsInteger("height", 0);
121        _maxWidth = par.getParameterAsInteger("maxWidth", 0);
122        _maxHeight = par.getParameterAsInteger("maxHeight", 0);
123    }
124    
125    @Override
126    public Serializable getKey()
127    {
128        return _path + "#" + _height + "#" + _width + "#" + _maxHeight + "#" + _maxWidth;
129    }
130
131    @Override
132    public SourceValidity getValidity()
133    {
134        return new TimeStampValidity(getLastModified());
135    }
136    
137    @Override
138    public long getLastModified()
139    {
140        if (_content != null)
141        {
142            return _content.getLastModified().getTime();
143        }
144        
145        return super.getLastModified();
146    }
147    
148    @Override
149    public String getMimeType()
150    {
151        if (_file != null)
152        {
153            return _file.getResource().getMimeType();
154        }
155
156        return super.getMimeType();
157    }
158
159    @Override
160    public void generate() throws IOException, SAXException, ProcessingException
161    {
162        try (InputStream is = _file.getResource().getInputStream())
163        {
164            if (_isImage())
165            {
166                // it's an image (which must be resized or not)
167                int i = _file.getName().lastIndexOf('.');
168                String format = i != -1 ? _file.getName().substring(i + 1) : "png";
169                format = _allowedFormats.contains(format) ? format : "png";
170                ImageHelper.generateThumbnail(is, out, format, _height, _width, _maxHeight, _maxWidth);
171            }
172            else
173            {
174                Response response = ObjectModelHelper.getResponse(objectModel);
175                response.setHeader("Content-Length", Long.toString(_file.getResource().getLength()));
176                
177                // Copy data in response
178                IOUtils.copy(is, out);
179            }
180        }
181        finally
182        {
183            IOUtils.closeQuietly(out);
184        }
185    }
186    
187    @Override
188    public void recycle()
189    {
190        super.recycle();
191        
192        _file = null;
193        _content = null;
194    }
195    
196    /**
197     * Determines if the file is an image
198     * @return <code>true</code> if file is a image
199     */
200    protected boolean _isImage()
201    {
202        if (_width > 0 || _height > 0 || _maxHeight > 0 || _maxWidth > 0)
203        {
204            // resize is required, assume this is a image
205            return true;
206        }
207        else
208        {
209            return getMimeType() != null && getMimeType().startsWith("image/");
210        }
211    }
212    
213    /** 
214     * Get the rich text meta
215     * @param meta The composite meta
216     * @param metadataName The metadata name (with /)
217     * @return The rich text meta
218     */
219    protected RichText _getMeta(CompositeMetadata meta, String metadataName)
220    {
221        int pos = metadataName.indexOf("/");
222        if (pos == -1)
223        {
224            return meta.getRichText(metadataName);
225        }
226        return _getMeta(meta.getCompositeMetadata(metadataName.substring(0, pos)), metadataName.substring(pos + 1));
227    }
228
229}