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 }