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