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.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.cocoon.ProcessingException; 033import org.apache.cocoon.caching.CacheableProcessingComponent; 034import org.apache.cocoon.environment.ObjectModelHelper; 035import org.apache.cocoon.environment.Request; 036import org.apache.cocoon.environment.Response; 037import org.apache.cocoon.environment.SourceResolver; 038import org.apache.cocoon.reading.ServiceableReader; 039import org.apache.commons.io.IOUtils; 040import org.apache.commons.lang.StringUtils; 041import org.apache.excalibur.source.SourceValidity; 042import org.xml.sax.SAXException; 043 044import org.ametys.cms.repository.Content; 045import org.ametys.core.util.ImageHelper; 046import org.ametys.core.util.cocoon.InvalidSourceValidity; 047import org.ametys.plugins.repository.AmetysObjectResolver; 048import org.ametys.plugins.repository.metadata.BinaryMetadata; 049import org.ametys.plugins.repository.metadata.CompositeMetadata; 050import org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType; 051import org.ametys.plugins.repository.metadata.MetadataAwareAmetysObject; 052import org.ametys.plugins.repository.version.VersionableAmetysObject; 053 054/** 055 * Reader for binary or file metadata. 056 */ 057public class FileReader extends ServiceableReader implements CacheableProcessingComponent 058{ 059 060 /** The ametys object resolver. */ 061 protected AmetysObjectResolver _resolver; 062 063 private int _width; 064 private int _height; 065 private int _maxWidth; 066 private int _maxHeight; 067 private int _cropWidth; 068 private int _cropHeight; 069 070 private boolean _readForDownload; 071 072 private MetadataAwareAmetysObject _ametysObject; 073 private BinaryMetadata _binary; 074 075 private String _path; 076 private Collection<String> _allowedFormats = Arrays.asList(new String[]{"png", "gif", "jpg", "jpeg"}); 077 078 @Override 079 public void service(ServiceManager serviceManager) throws ServiceException 080 { 081 super.service(serviceManager); 082 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 083 } 084 085 @Override 086 public void setup(SourceResolver res, Map objModel, String src, Parameters par) throws ProcessingException, SAXException, IOException 087 { 088 super.setup(res, objModel, src, par); 089 090 _ametysObject = getAmetysObject(); 091 092 CompositeMetadata metadata = _ametysObject.getMetadataHolder(); 093 094 try 095 { 096 _path = parameters.getParameter("path"); 097 } 098 catch (ParameterException e) 099 { 100 throw new ProcessingException("The path parameter is mandatory for reading binary metadata.", e); 101 } 102 103 List<String> pathElements = Arrays.asList(_path.split("/")); 104 105 Iterator<String> it = pathElements.iterator(); 106 107 while (it.hasNext()) 108 { 109 String pathElement = it.next(); 110 111 if (it.hasNext()) 112 { 113 // not the last segment : it is a composite 114 metadata = metadata.getCompositeMetadata(URLDecoder.decode(pathElement, "UTF-8")); 115 } 116 else 117 { 118 String metadataName = URLDecoder.decode(pathElement, "UTF-8"); 119 if (metadata.getType(metadataName) != MetadataType.BINARY) 120 { 121 throw new UnsupportedOperationException("Only binary metadata are allowed"); 122 } 123 124 _binary = metadata.getBinaryMetadata(metadataName); 125 } 126 } 127 128 _readForDownload = par.getParameterAsBoolean("download", false); 129 130 // parameters for image resizing 131 _width = par.getParameterAsInteger("width", 0); 132 _height = par.getParameterAsInteger("height", 0); 133 _maxWidth = par.getParameterAsInteger("maxWidth", 0); 134 _maxHeight = par.getParameterAsInteger("maxHeight", 0); 135 _cropWidth = par.getParameterAsInteger("cropWidth", 0); 136 _cropHeight = par.getParameterAsInteger("cropHeight", 0); 137 } 138 139 /** 140 * Get the ametys object. 141 * @return the ametys object. 142 * @throws ProcessingException if the information provided was insufficient to resolve an object. 143 */ 144 protected MetadataAwareAmetysObject getAmetysObject() throws ProcessingException 145 { 146 MetadataAwareAmetysObject object = null; 147 148 Request request = ObjectModelHelper.getRequest(objectModel); 149 String objectPath = parameters.getParameter("objectPath", request.getParameter("objectPath")); 150 String objectId = parameters.getParameter("objectId", request.getParameter("objectId")); 151 // Legacy parameter name. 152 if (StringUtils.isBlank(objectId)) 153 { 154 objectId = parameters.getParameter("contentId", request.getParameter("contentId")); 155 } 156 157 if (StringUtils.isNotBlank(objectId)) 158 { 159 object = _resolver.resolveById(objectId); 160 } 161 else if (StringUtils.isNotBlank(objectPath)) 162 { 163 objectPath = objectPath.replaceAll("%3A", ":"); 164 object = _resolver.resolveByPath(objectPath); 165 } 166 else 167 { 168 object = (Content) request.getAttribute(Content.class.getName()); 169 } 170 171 if (object == null) 172 { 173 throw new ProcessingException("The object ID parameter is mandatory for reading binary metadata."); 174 } 175 176 String revision = parameters.getParameter("contentVersion", null); 177 if (StringUtils.isNotEmpty(revision) && object instanceof VersionableAmetysObject) 178 { 179 ((VersionableAmetysObject) object).switchToRevision(revision); 180 } 181 182 return object; 183 } 184 185 @Override 186 public Serializable getKey() 187 { 188 return _path + "#" + _height + "#" + _width + "#" + _maxHeight + "#" + _maxWidth + "#" + _cropHeight + "#" + _cropWidth; 189 } 190 191 @Override 192 public SourceValidity getValidity() 193 { 194 return new InvalidSourceValidity(); 195 } 196 197 @Override 198 public long getLastModified() 199 { 200 if (_binary != null) 201 { 202 return _binary.getLastModified().getTime(); 203 } 204 205 return super.getLastModified(); 206 } 207 208 @Override 209 public String getMimeType() 210 { 211 if (_binary != null) 212 { 213 return _binary.getMimeType(); 214 } 215 216 return super.getMimeType(); 217 } 218 219 @Override 220 public void generate() throws IOException, SAXException, ProcessingException 221 { 222 Response response = ObjectModelHelper.getResponse(objectModel); 223 224 if (_readForDownload) 225 { 226 response.setHeader("Content-Disposition", "attachment; filename=\"" + _binary.getFilename() + "\""); 227 } 228 229 try (InputStream is = _binary.getInputStream()) 230 { 231 if (_isImage()) 232 { 233 // it's an image (which must be resized or not) 234 int i = _binary.getFilename().lastIndexOf('.'); 235 String format = i != -1 ? _binary.getFilename().substring(i + 1) : "png"; 236 format = _allowedFormats.contains(format) ? format : "png"; 237 238 ImageHelper.generateThumbnail(is, out, format, _height, _width, _maxHeight, _maxWidth, _cropHeight, _cropWidth); 239 } 240 else 241 { 242 response.setHeader("Content-Length", Long.toString(_binary.getLength())); 243 244 // Copy data in response 245 IOUtils.copy(is, out); 246 } 247 } 248 finally 249 { 250 IOUtils.closeQuietly(out); 251 } 252 } 253 254 @Override 255 public void recycle() 256 { 257 super.recycle(); 258 259 _binary = null; 260 _ametysObject = null; 261 } 262 263 /** 264 * Determines if the file is an image 265 * @return <code>true</code> if file is a image 266 */ 267 protected boolean _isImage() 268 { 269 if (_width > 0 || _height > 0 || _maxHeight > 0 || _maxWidth > 0 || _cropHeight > 0 || _cropWidth > 0) 270 { 271 // resize is required, assume this is a image 272 return true; 273 } 274 else 275 { 276 return getMimeType() != null && getMimeType().startsWith("image/"); 277 } 278 } 279}