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