001/* 002 * Copyright 2019 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.util.cocoon; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Map; 023 024import org.apache.avalon.framework.parameters.Parameters; 025import org.apache.cocoon.ProcessingException; 026import org.apache.cocoon.environment.ObjectModelHelper; 027import org.apache.cocoon.environment.Response; 028import org.apache.cocoon.environment.SourceResolver; 029import org.apache.cocoon.reading.AbstractReader; 030import org.apache.commons.io.IOUtils; 031import org.xml.sax.SAXException; 032 033import org.ametys.core.util.ImageHelper; 034 035/** 036 * Abstract reader for Ametys resources. 037 * If the resource is an image, this reader handles resizing and cropping. 038 */ 039public abstract class AbstractResourceReader extends AbstractReader 040{ 041 private static final String __DEFAULT_FORMAT = "png"; 042 private static final Collection<String> __ALLOWED_FORMATS = Arrays.asList("png", "gif", "jpg", "jpeg"); 043 044 private int _width; 045 private int _height; 046 private int _maxWidth; 047 private int _maxHeight; 048 private int _cropWidth; 049 private int _cropHeight; 050 051 private boolean _readForDownload; 052 053 @Override 054 public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException 055 { 056 super.setup(resolver, objectModel, src, par); 057 058 doSetup(resolver, objectModel, src, par); 059 060 _readForDownload = par.getParameterAsBoolean("download", false); 061 Response response = ObjectModelHelper.getResponse(objectModel); 062 063 if (_readForDownload) 064 { 065 String name = getFilename(); 066 String encodedName = getEncodedFilename(); 067 068 response.setHeader("Content-Disposition", "attachment; filename=\"" + (encodedName != null ? encodedName : name) + "\"" + (encodedName != null ? ";filename*=UTF-8''" + encodedName : "")); 069 } 070 071 // parameters for image resizing 072 _width = par.getParameterAsInteger("width", 0); 073 _height = par.getParameterAsInteger("height", 0); 074 _maxWidth = par.getParameterAsInteger("maxWidth", 0); 075 _maxHeight = par.getParameterAsInteger("maxHeight", 0); 076 _cropWidth = par.getParameterAsInteger("cropWidth", 0); 077 _cropHeight = par.getParameterAsInteger("cropHeight", 0); 078 } 079 080 /** 081 * Called by {@link #setup(SourceResolver, Map, String, Parameters)}. This method should be implemented by subclasses to retrieve the actual resource. 082 * @param res the {@link SourceResolver}. 083 * @param objModel the Cocoon's object model. 084 * @param src the source, as given by the sitemap. 085 * @param par the parameters, as given by the sitemap. 086 * @throws ProcessingException if an error occurs while processing the resource. 087 * @throws IOException if an error occurs while accessing the resource. 088 */ 089 protected abstract void doSetup(SourceResolver res, Map objModel, String src, Parameters par) throws ProcessingException, IOException; 090 091 @SuppressWarnings("deprecation") 092 public void generate() throws IOException, SAXException, ProcessingException 093 { 094 Response response = ObjectModelHelper.getResponse(objectModel); 095 096 try (InputStream is = getInputStream()) 097 { 098 if (processImage()) 099 { 100 String fileName = getFilename(); 101 int i = fileName.lastIndexOf('.'); 102 String format = i != -1 ? fileName.substring(i + 1).toLowerCase() : __DEFAULT_FORMAT; 103 format = __ALLOWED_FORMATS.contains(format) ? format : __DEFAULT_FORMAT; 104 105 ImageHelper.generateThumbnail(is, out, format, _height, _width, _maxHeight, _maxWidth, _cropHeight, _cropWidth); 106 } 107 else 108 { 109 // Copy data in response 110 response.setHeader("Content-Length", Long.toString(getLength())); 111 IOUtils.copy(is, out); 112 } 113 } 114 finally 115 { 116 IOUtils.closeQuietly(out); 117 } 118 } 119 120 /** 121 * Returns the resource's {@link InputStream}. 122 * @return the resource's {@link InputStream}. 123 */ 124 protected abstract InputStream getInputStream(); 125 126 /** 127 * Returns the resource's name. 128 * @return the resource's name. 129 */ 130 protected abstract String getFilename(); 131 132 /** 133 * If needed, returns the resource's name, properly encoded for using in a "Content-Disposition" HTTP header.<br> 134 * May be null, in which case the result of {@link #getFilename()} is used instead. 135 * @return the encoded resource's name, if any. 136 */ 137 protected abstract String getEncodedFilename(); 138 139 /** 140 * Returns the resource's length. 141 * @return the resource's length. 142 */ 143 protected abstract long getLength(); 144 145 146 /** 147 * Determines if the file is an image and should be processed. 148 * @return <code>true</code> if file is a image 149 */ 150 protected boolean processImage() 151 { 152 if (_width > 0 || _height > 0 || _maxHeight > 0 || _maxWidth > 0 || _cropHeight > 0 || _cropWidth > 0) 153 { 154 // resize or crop is required, assume this is an image 155 return true; 156 } 157 else if (!_readForDownload) 158 { 159 // only process image if it is for rendering purposes 160 return getMimeType() != null && getMimeType().startsWith("image/"); 161 } 162 163 return false; 164 } 165 166 /** 167 * Helper method to compute a suffix based on resizing and cropping properties. 168 * @return a String to be used in cache keys. 169 */ 170 protected String getKeySuffix() 171 { 172 return "#" + _height + "#" + _width + "#" + _maxHeight + "#" + _maxWidth + "#" + _cropHeight + "#" + _cropWidth; 173 } 174}