View Javadoc
1   package org.apache.fulcrum.upload;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.List;
25  
26  import javax.portlet.ActionRequest;
27  import javax.servlet.http.HttpServletRequest;
28  
29  import org.apache.avalon.framework.activity.Initializable;
30  import org.apache.avalon.framework.configuration.Configurable;
31  import org.apache.avalon.framework.configuration.Configuration;
32  import org.apache.avalon.framework.context.Context;
33  import org.apache.avalon.framework.context.ContextException;
34  import org.apache.avalon.framework.context.Contextualizable;
35  import org.apache.avalon.framework.logger.AbstractLogEnabled;
36  import org.apache.avalon.framework.service.ServiceException;
37  import org.apache.commons.fileupload.FileItem;
38  import org.apache.commons.fileupload.FileItemIterator;
39  import org.apache.commons.fileupload.FileUploadException;
40  import org.apache.commons.fileupload.disk.DiskFileItemFactory;
41  import org.apache.commons.fileupload.portlet.PortletFileUpload;
42  import org.apache.commons.fileupload.servlet.ServletFileUpload;
43  
44  /**
45   * <p>
46   * This class is an implementation of {@link UploadService}.
47   *
48   * <p>
49   * Files will be stored in temporary disk storage on in memory, depending on
50   * request size, and will be available from the
51   * <code>org.apache.fulcrum.util.parser.ParameterParser</code> as
52   * <code>org.apache.commons.fileupload.FileItem</code> objects.
53   *
54   * <p>
55   * This implementation of {@link UploadService} handles multiple files per
56   * single html form, sent using multipart/form-data encoding type, as specified
57   * by RFC 1867. Use
58   * <code>org.apache.fulcrum.parser.ParameterParser#getFileItems(String)</code> to
59   * acquire an array of <code>org.apache.commons.fileupload.FileItem</code> objects
60   * associated with given html form.
61   *
62   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
63   * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
64   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
65   * @version $Id$
66   */
67  public class DefaultUploadService extends AbstractLogEnabled
68  		implements UploadService, Initializable, Configurable, Contextualizable {
69  	/** A File Item Factory object for the actual uploading */
70  	private DiskFileItemFactory itemFactory;
71  
72  	private int sizeThreshold;
73  	private int sizeMax;
74  
75  	private String repositoryPath;
76  	private String headerEncoding;
77  
78  	/**
79  	 * The application root
80  	 */
81  	private String applicationRoot;
82  
83  	/**
84  	 * The maximum allowed upload size
85  	 */
86  	@Override
87  	public long getSizeMax() {
88  		return sizeMax;
89  	}
90  
91  	/**
92  	 * The threshold beyond which files are written directly to disk.
93  	 */
94  	@Override
95  	public long getSizeThreshold() {
96  		return itemFactory.getSizeThreshold();
97  	}
98  
99  	/**
100 	 * The location used to temporarily store files that are larger than the size
101 	 * threshold.
102 	 */
103 	@Override
104 	public String getRepository() {
105 		return itemFactory.getRepository().getAbsolutePath();
106 	}
107 
108 	/**
109 	 * @return Returns the headerEncoding.
110 	 */
111 	@Override
112 	public String getHeaderEncoding() {
113 		return headerEncoding;
114 	}
115 
116 	/**
117 	 * <p>
118 	 * Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> compliant
119 	 * <code>multipart/form-data</code> stream.
120 	 * </p>
121 	 *
122 	 * @param req The servlet request to be parsed.
123 	 * @throws ServiceException Problems reading/parsing the request or storing the
124 	 *                          uploaded file(s).
125 	 */
126 	@Override
127 	public List<FileItem> parseRequest(HttpServletRequest req) throws ServiceException {
128 		return parseRequest(req, this.sizeMax, this.itemFactory);
129 	}
130 
131 	/**
132 	 * <p>
133 	 * Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> compliant
134 	 * <code>multipart/form-data</code> stream.
135 	 * </p>
136 	 *
137 	 * @param req  The servlet request to be parsed.
138 	 * @param path The location where the files should be stored.
139 	 * @throws ServiceException Problems reading/parsing the request or storing the
140 	 *                          uploaded file(s).
141 	 */
142 	@Override
143 	public List<FileItem> parseRequest(HttpServletRequest req, String path) throws ServiceException {
144 		return parseRequest(req, this.sizeThreshold, this.sizeMax, path);
145 	}
146 
147 	/**
148 	 * <p>
149 	 * Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> compliant
150 	 * <code>multipart/form-data</code> stream.
151 	 * </p>
152 	 *
153 	 * @param req           The servlet request to be parsed.
154 	 * @param sizeThreshold the max size in bytes to be stored in memory
155 	 * @param sizeMax       the maximum allowed upload size in bytes
156 	 * @param path          The location where the files should be stored.
157 	 * @return list of file items
158 	 * @throws ServiceException Problems reading/parsing the request or storing the
159 	 *                          uploaded file(s).
160 	 */
161 	@Override
162 	public List<FileItem> parseRequest(HttpServletRequest req, int sizeThreshold, int sizeMax, String path)
163 			throws ServiceException {
164 		return parseRequest(req, sizeMax, new DiskFileItemFactory(sizeThreshold, new File(path)));
165 	}
166 
167 	/**
168 	 * <p>
169 	 * Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> compliant
170 	 * <code>multipart/form-data</code> stream.
171 	 * </p>
172 	 *
173 	 * @param req     The servlet request to be parsed.
174 	 * @param sizeMax the maximum allowed upload size in bytes
175 	 * @param factory the file item factory to use
176 	 * @return list of file items
177 	 * @throws ServiceException Problems reading/parsing the request or storing the
178 	 *                          uploaded file(s).
179 	 */
180 	protected List<FileItem> parseRequest(HttpServletRequest req, int sizeMax, DiskFileItemFactory factory)
181 			throws ServiceException {
182 		try {
183 			ServletFileUpload fileUpload = new ServletFileUpload(factory);
184 			fileUpload.setSizeMax(sizeMax);
185 			fileUpload.setHeaderEncoding(headerEncoding);
186 			return fileUpload.parseRequest(req);
187 		} catch (FileUploadException e) {
188 			throw new ServiceException(UploadService.ROLE, e.getMessage(), e);
189 		}
190 	}
191 
192 	/**
193 	 * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
194 	 * compliant <code>multipart/form-data</code> stream.
195 	 *
196 	 * @param req The servlet request to be parsed.
197 	 *
198 	 * @return An iterator to instances of <code>FileItemStream</code> parsed from
199 	 *         the request, in the order that they were transmitted.
200 	 *
201 	 * @throws ServiceException if there are problems reading/parsing the request or
202 	 *                          storing files. This may also be a network error
203 	 *                          while communicating with the client or a problem
204 	 *                          while storing the uploaded content.
205 	 */
206 	@Override
207 	public FileItemIterator getItemIterator(HttpServletRequest req) throws ServiceException {
208 		ServletFileUpload upload = new ServletFileUpload();
209 		try {
210 			return upload.getItemIterator(req);
211 		} catch (FileUploadException e) {
212 			throw new ServiceException(UploadService.ROLE, e.getMessage(), e);
213 		} catch (IOException e) {
214 			throw new ServiceException(UploadService.ROLE, e.getMessage(), e);
215 		}
216 	}
217 
218 	/**
219 	 * <p>
220 	 * Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> compliant
221 	 * <code>multipart/form-data</code> stream.
222 	 * </p>
223 	 *
224 	 * @param req The portlet request to be parsed.
225 	 * @throws ServiceException Problems reading/parsing the request or storing the
226 	 *                          uploaded file(s).
227 	 */
228 	@Override
229 	public List<FileItem> parseRequest(ActionRequest req) throws ServiceException {
230 		return parseRequest(req, this.sizeMax, this.itemFactory);
231 	}
232 
233 	/**
234 	 * <p>
235 	 * Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> compliant
236 	 * <code>multipart/form-data</code> stream.
237 	 * </p>
238 	 *
239 	 * @param req  The portlet request to be parsed.
240 	 * @param path The location where the files should be stored.
241 	 * @throws ServiceException Problems reading/parsing the request or storing the
242 	 *                          uploaded file(s).
243 	 */
244 	@Override
245 	public List<FileItem> parseRequest(ActionRequest req, String path) throws ServiceException {
246 		return parseRequest(req, this.sizeThreshold, this.sizeMax, path);
247 	}
248 
249 	/**
250 	 * <p>
251 	 * Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> compliant
252 	 * <code>multipart/form-data</code> stream.
253 	 * </p>
254 	 *
255 	 * @param req           The portlet request to be parsed.
256 	 * @param sizeThreshold the max size in bytes to be stored in memory
257 	 * @param sizeMax       the maximum allowed upload size in bytes
258 	 * @param path          The location where the files should be stored.
259 	 * @throws ServiceException Problems reading/parsing the request or storing the
260 	 *                          uploaded file(s).
261 	 */
262 	@Override
263 	public List<FileItem> parseRequest(ActionRequest req, int sizeThreshold, int sizeMax, String path)
264 			throws ServiceException {
265 		return parseRequest(req, sizeMax, new DiskFileItemFactory(sizeThreshold, new File(path)));
266 	}
267 
268 	/**
269 	 * <p>
270 	 * Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> compliant
271 	 * <code>multipart/form-data</code> stream.
272 	 * </p>
273 	 *
274 	 * @param req     The portlet request to be parsed.
275 	 * @param sizeMax the maximum allowed upload size in bytes
276 	 * @param factory the file item factory to use
277 	 * @return The list of FileItem parts uploaded
278 	 * @throws ServiceException Problems reading/parsing the request or storing the
279 	 *                          uploaded file(s).
280 	 */
281 	protected List<FileItem> parseRequest(ActionRequest req, int sizeMax, DiskFileItemFactory factory)
282 			throws ServiceException {
283 		try {
284 			PortletFileUpload fileUpload = new PortletFileUpload(factory);
285 			fileUpload.setSizeMax(sizeMax);
286 			fileUpload.setHeaderEncoding(headerEncoding);
287 			return fileUpload.parseRequest(req);
288 		} catch (FileUploadException e) {
289 			throw new ServiceException(UploadService.ROLE, e.getMessage(), e);
290 		}
291 	}
292 
293 	/**
294 	 * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
295 	 * compliant <code>multipart/form-data</code> stream.
296 	 *
297 	 * @param req The portlet request to be parsed.
298 	 *
299 	 * @return An iterator to instances of <code>FileItemStream</code> parsed from
300 	 *         the request, in the order that they were transmitted.
301 	 *
302 	 * @throws ServiceException if there are problems reading/parsing the request or
303 	 *                          storing files. This may also be a network error
304 	 *                          while communicating with the client or a problem
305 	 *                          while storing the uploaded content.
306 	 */
307 	@Override
308 	public FileItemIterator getItemIterator(ActionRequest req) throws ServiceException {
309 		PortletFileUpload upload = new PortletFileUpload();
310 		try {
311 			return upload.getItemIterator(req);
312 		} catch (FileUploadException e) {
313 			throw new ServiceException(UploadService.ROLE, e.getMessage(), e);
314 		} catch (IOException e) {
315 			throw new ServiceException(UploadService.ROLE, e.getMessage(), e);
316 		}
317 	}
318 
319 	/**
320 	 * Utility method that determines whether the request contains multipart
321 	 * content.
322 	 *
323 	 * @param req The servlet request to be evaluated. Must be non-null.
324 	 *
325 	 * @return <code>true</code> if the request is multipart; <code>false</code>
326 	 *         otherwise.
327 	 */
328 	@Override
329 	public boolean isMultipart(HttpServletRequest req) {
330 		return ServletFileUpload.isMultipartContent(req);
331 	}
332 
333 	/**
334 	 * Utility method that determines whether the request contains multipart
335 	 * content.
336 	 *
337 	 * @param req The portlet request to be evaluated. Must be non-null.
338 	 *
339 	 * @return <code>true</code> if the request is multipart; <code>false</code>
340 	 *         otherwise.
341 	 */
342 	@Override
343 	public boolean isMultipart(ActionRequest req) {
344 		return PortletFileUpload.isMultipartContent(req);
345 	}
346 
347 	/**
348 	 * @see org.apache.fulcrum.ServiceBroker#getRealPath(String)
349 	 */
350 	private String getRealPath(String path) {
351 		String absolutePath = null;
352 		if (applicationRoot == null) {
353 			absolutePath = new File(path).getAbsolutePath();
354 		} else {
355 			absolutePath = new File(applicationRoot, path).getAbsolutePath();
356 		}
357 
358 		return absolutePath;
359 	}
360 
361 	// ---------------- Avalon Lifecycle Methods ---------------------
362 	/**
363 	 * Avalon component lifecycle method
364 	 */
365 	@Override
366 	public void configure(Configuration conf) {
367 		repositoryPath = conf.getAttribute(UploadService.REPOSITORY_KEY, UploadService.REPOSITORY_DEFAULT);
368 
369 		headerEncoding = conf.getAttribute(UploadService.HEADER_ENCODING_KEY, UploadService.HEADER_ENCODING_DEFAULT);
370 
371 		sizeMax = conf.getAttributeAsInteger(UploadService.SIZE_MAX_KEY, UploadService.SIZE_MAX_DEFAULT);
372 
373 		sizeThreshold = conf.getAttributeAsInteger(UploadService.SIZE_THRESHOLD_KEY,
374 				UploadService.SIZE_THRESHOLD_DEFAULT);
375 	}
376 
377 	/**
378 	 * Avalon component lifecycle method
379 	 *
380 	 * Initializes the service.
381 	 *
382 	 * This method processes the repository path, to make it relative to the web
383 	 * application root, if necessary
384 	 */
385 	@Override
386 	public void initialize() throws Exception {
387 		// test for the existence of the path within the webapp directory.
388 		// if it does not exist, assume the path was to be used as is.
389 		String testPath = getRealPath(repositoryPath);
390 		File testDir = new File(testPath);
391 		if (testDir.exists()) {
392 			repositoryPath = testPath;
393 		}
394 
395 		getLogger().debug("Upload Service: REPOSITORY_KEY => " + repositoryPath);
396 
397 		itemFactory = new DiskFileItemFactory(sizeThreshold, new File(repositoryPath));
398 	}
399 
400 	/**
401 	 * Avalon component lifecycle method
402 	 */
403 	@Override
404 	public void contextualize(Context context) throws ContextException {
405 		this.applicationRoot = context.get("urn:avalon:home").toString();
406 	}
407 }