001/*
002 *  Copyright 2016 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.core.resources;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.io.OutputStream;
021import java.io.Serializable;
022import java.util.Collection;
023import java.util.Map;
024import java.util.Set;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028import org.apache.avalon.framework.parameters.Parameters;
029import org.apache.cocoon.ProcessingException;
030import org.apache.cocoon.ResourceNotFoundException;
031import org.apache.commons.io.IOUtils;
032import org.apache.commons.lang.StringUtils;
033import org.apache.excalibur.source.Source;
034import org.apache.excalibur.source.SourceException;
035
036import org.ametys.core.util.ImageHelper;
037
038/**
039 * Resource handler for images
040 */
041public class ImageResourceHandler extends DefaultResourceHandler
042{
043    private static final Pattern _SIZE_PATTERN = Pattern.compile("^(.+)_(max|crop|)(\\d+)x(\\d+)(\\.[^./]+)?$");
044
045    private static final Collection<String> __ALLOWED_OUTPUT_FORMATS = Set.of("png", "gif", "jpg", "jpeg");
046    private static final Collection<String> __UNRESIZABLE_FORMATS = Set.of("svg");
047    
048    private int _height;
049    private int _width;
050    private int _maxHeight;
051    private int _maxWidth;
052    private int _cropHeight;
053    private int _cropWidth;
054    
055    private boolean _download;
056
057    @Override
058    public Source setup(String location, Map objectModel, Parameters par, boolean readForDownload) throws IOException, ProcessingException
059    {
060        Source source = null;
061        try 
062        {
063            source = _resolver.resolveURI(location);
064        } 
065        catch (SourceException e) 
066        {
067            // Nothing
068        }
069        
070        // Compute the locale
071        if (source == null || !source.exists())
072        {
073            _resolver.release(source);
074            
075            Matcher sizeMatcher = _SIZE_PATTERN.matcher(location);
076            if (sizeMatcher.matches())
077            {
078                String computedLocation = sizeMatcher.group(1) + sizeMatcher.group(5);
079                
080                source = _resolver.resolveURI(computedLocation);
081                if (!source.exists())
082                {
083                    _resolver.release(source);
084                    throw new ResourceNotFoundException("Resource not found for URI : " + source.getURI());
085                }
086                
087                // type is either empty (resize), max or crop.
088                String type = sizeMatcher.group(2);
089                
090                int height = Integer.parseInt(sizeMatcher.group(3));
091                int width = Integer.parseInt(sizeMatcher.group(4));
092
093                _height = "".equals(type) ? height : 0;
094                _width = "".equals(type) ? width : 0;
095                _maxHeight = "max".equals(type) ? height : 0;
096                _maxWidth = "max".equals(type) ? width : 0;
097                _cropHeight = "crop".equals(type) ? height : 0;
098                _cropWidth = "crop".equals(type) ? width : 0;
099            }
100            else
101            {
102                throw new ResourceNotFoundException("Resource not found for URI : " + location);
103            }
104        }
105        
106        _download = readForDownload;
107        
108        _source = source;
109        return _source;
110    }
111
112    @Override
113    public void generate(OutputStream out) throws IOException, ProcessingException
114    {
115        String fileExtension = StringUtils.substringAfterLast(_source.getURI(), ".").toLowerCase();
116        
117        try (InputStream is = _source.getInputStream())
118        {
119            if (_processImage(fileExtension))
120            {
121                String outputFormat = __ALLOWED_OUTPUT_FORMATS.contains(fileExtension) ? fileExtension : "png";
122                ImageHelper.generateThumbnail(is, out, outputFormat, _height, _width, _maxHeight, _maxWidth, _cropHeight, _cropWidth);
123            }
124            else
125            {
126                // Copy data in response
127                IOUtils.copy(is, out);
128            }
129        }
130    }
131    
132    private boolean _processImage(String fileExtension)
133    {
134        if (__UNRESIZABLE_FORMATS.contains(fileExtension))
135        {
136            return false;
137        }
138        else if (_width > 0 || _height > 0 || _maxHeight > 0 || _maxWidth > 0 || _cropHeight > 0 || _cropWidth > 0)
139        {
140            // resize or crop is required, assume this is an image
141            return true;
142        }
143        else if (!_download)
144        {
145            String mimeType = _source.getMimeType();
146                    
147            // only process image if it is for rendering purposes
148            return mimeType != null && mimeType.startsWith("image/");
149        }
150        else
151        {
152            return false;
153        }
154    }
155
156    @Override
157    public Serializable getKey()
158    {
159        return _source.getURI() + "###" + _width + "x" + _height + "x" + _maxWidth + "x" + _maxHeight + "x" + _cropWidth + "x" + _cropHeight;
160    }
161}