001    package org.apache.fulcrum.upload;
002    
003    
004    /*
005     * Licensed to the Apache Software Foundation (ASF) under one
006     * or more contributor license agreements.  See the NOTICE file
007     * distributed with this work for additional information
008     * regarding copyright ownership.  The ASF licenses this file
009     * to you under the Apache License, Version 2.0 (the
010     * "License"); you may not use this file except in compliance
011     * with the License.  You may obtain a copy of the License at
012     *
013     *   http://www.apache.org/licenses/LICENSE-2.0
014     *
015     * Unless required by applicable law or agreed to in writing,
016     * software distributed under the License is distributed on an
017     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
018     * KIND, either express or implied.  See the License for the
019     * specific language governing permissions and limitations
020     * under the License.
021     */
022    
023    
024    import java.io.File;
025    import java.io.IOException;
026    import java.util.List;
027    
028    import javax.servlet.http.HttpServletRequest;
029    
030    import org.apache.avalon.framework.activity.Initializable;
031    import org.apache.avalon.framework.configuration.Configurable;
032    import org.apache.avalon.framework.configuration.Configuration;
033    import org.apache.avalon.framework.context.Context;
034    import org.apache.avalon.framework.context.ContextException;
035    import org.apache.avalon.framework.context.Contextualizable;
036    import org.apache.avalon.framework.logger.AbstractLogEnabled;
037    import org.apache.avalon.framework.service.ServiceException;
038    import org.apache.commons.fileupload.FileItem;
039    import org.apache.commons.fileupload.FileItemIterator;
040    import org.apache.commons.fileupload.FileUploadException;
041    import org.apache.commons.fileupload.disk.DiskFileItemFactory;
042    import org.apache.commons.fileupload.servlet.ServletFileUpload;
043    
044    /**
045     * <p> This class is an implementation of {@link UploadService}.
046     *
047     * <p> Files will be stored in temporary disk storage on in memory,
048     * depending on request size, and will be available from the {@link
049     * org.apache.fulcrum.util.parser.ParameterParser} as {@link
050     * org.apache.commons.fileupload.FileItem} objects.
051     *
052     * <p>This implementation of {@link UploadService} handles multiple
053     * files per single html form, sent using multipart/form-data encoding
054     * type, as specified by RFC 1867.  Use {@link
055     * org.apache.fulcrum.parser.ParameterParser#getFileItems(String)} to
056     * acquire an array of {@link
057     * org.apache.commons.fileupload.FileItem} objects associated with given
058     * html form.
059     *
060     * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
061     * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
062     * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
063     * @version $Id: DefaultUploadService.java 1351114 2012-06-17 15:59:20Z tv $
064     */
065    public class DefaultUploadService
066        extends AbstractLogEnabled
067        implements UploadService, Initializable, Configurable, Contextualizable
068    {
069        /** A File Item Factory object for the actual uploading */
070        private DiskFileItemFactory itemFactory;
071    
072        private int sizeThreshold;
073        private int sizeMax;
074    
075        private String repositoryPath;
076        private String headerEncoding;
077    
078        /**
079         * The application root
080         */
081        private String applicationRoot;
082    
083        /**
084         * The maximum allowed upload size
085         */
086        public long getSizeMax()
087        {
088            return sizeMax;
089        }
090    
091        /**
092         * The threshold beyond which files are written directly to disk.
093         */
094        public long getSizeThreshold()
095        {
096            return itemFactory.getSizeThreshold();
097        }
098    
099        /**
100         * The location used to temporarily store files that are larger
101         * than the size threshold.
102         */
103        public String getRepository()
104        {
105            return itemFactory.getRepository().getAbsolutePath();
106        }
107    
108        /**
109         * @return Returns the headerEncoding.
110         */
111        public String getHeaderEncoding()
112        {
113            return headerEncoding;
114        }
115    
116        /**
117         * <p>Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
118         * compliant <code>multipart/form-data</code> stream.</p>
119         *
120         * @param req The servlet request to be parsed.
121         * @exception ServiceException Problems reading/parsing the
122         * request or storing the uploaded file(s).
123         */
124        public List<FileItem> parseRequest(HttpServletRequest req)
125            throws ServiceException
126        {
127            return parseRequest(req, this.sizeMax, this.itemFactory);
128        }
129    
130        /**
131         * <p>Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
132         * compliant <code>multipart/form-data</code> stream.</p>
133         *
134         * @param req The servlet request to be parsed.
135         * @param path The location where the files should be stored.
136         * @exception ServiceException Problems reading/parsing the
137         * request or storing the uploaded file(s).
138         */
139        public List<FileItem> parseRequest(HttpServletRequest req, String path)
140            throws ServiceException
141        {
142            return parseRequest(req, this.sizeThreshold, this.sizeMax, path);
143        }
144    
145        /**
146         * <p>Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
147         * compliant <code>multipart/form-data</code> stream.</p>
148         *
149         * @param req The servlet request to be parsed.
150         * @param sizeThreshold the max size in bytes to be stored in memory
151         * @param sizeMax the maximum allowed upload size in bytes
152         * @param path The location where the files should be stored.
153         * @exception ServiceException Problems reading/parsing the
154         * request or storing the uploaded file(s).
155         */
156        public List<FileItem> parseRequest(HttpServletRequest req, int sizeThreshold,
157                                      int sizeMax, String path)
158                throws ServiceException
159        {
160            return parseRequest(req, sizeMax, new DiskFileItemFactory(sizeThreshold, new File(path)));
161        }
162    
163        /**
164         * <p>Parses a <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
165         * compliant <code>multipart/form-data</code> stream.</p>
166         *
167         * @param req The servlet request to be parsed.
168         * @param sizeMax the maximum allowed upload size in bytes
169         * @param factory the file item factory to use
170         *
171         * @exception ServiceException Problems reading/parsing the
172         * request or storing the uploaded file(s).
173         */
174        @SuppressWarnings("unchecked")
175        protected List<FileItem> parseRequest(HttpServletRequest req, int sizeMax, DiskFileItemFactory factory)
176                throws ServiceException
177        {
178            try
179            {
180                ServletFileUpload fileUpload = new ServletFileUpload(factory);
181                fileUpload.setSizeMax(sizeMax);
182                fileUpload.setHeaderEncoding(headerEncoding);
183                return fileUpload.parseRequest(req);
184            }
185            catch (FileUploadException e)
186            {
187                throw new ServiceException(UploadService.ROLE, e.getMessage(), e);
188            }
189        }
190    
191        /**
192         * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
193         * compliant <code>multipart/form-data</code> stream.
194         *
195         * @param req The servlet request to be parsed.
196         *
197         * @return An iterator to instances of <code>FileItemStream</code>
198         *         parsed from the request, in the order that they were
199         *         transmitted.
200         *
201         * @throws ServiceException if there are problems reading/parsing
202         *                             the request or storing files. This
203         *                             may also be a network error while
204         *                             communicating with the client or a
205         *                             problem while storing the uploaded
206         *                             content.
207         */
208        public FileItemIterator getItemIterator(HttpServletRequest req) throws ServiceException
209        {
210            ServletFileUpload upload = new ServletFileUpload();
211            try
212            {
213                return upload.getItemIterator(req);
214            }
215            catch (FileUploadException e)
216            {
217                throw new ServiceException(UploadService.ROLE, e.getMessage(), e);
218            }
219            catch (IOException e)
220            {
221                throw new ServiceException(UploadService.ROLE, e.getMessage(), e);
222            }
223        }
224    
225        /**
226         * Utility method that determines whether the request contains multipart
227         * content.
228         *
229         * @param req The servlet request to be evaluated. Must be non-null.
230         *
231         * @return <code>true</code> if the request is multipart;
232         *         <code>false</code> otherwise.
233         */
234        public boolean isMultipart(HttpServletRequest req)
235        {
236            return ServletFileUpload.isMultipartContent(req);
237        }
238    
239        /**
240         * @see org.apache.fulcrum.ServiceBroker#getRealPath(String)
241         */
242        private String getRealPath(String path)
243        {
244            String absolutePath = null;
245            if (applicationRoot == null)
246            {
247                absolutePath = new File(path).getAbsolutePath();
248            }
249            else
250            {
251                absolutePath = new File(applicationRoot, path).getAbsolutePath();
252            }
253    
254            return absolutePath;
255        }
256    
257        // ---------------- Avalon Lifecycle Methods ---------------------
258        /**
259         * Avalon component lifecycle method
260         */
261        public void configure(Configuration conf)
262        {
263            repositoryPath = conf.getAttribute(
264                    UploadService.REPOSITORY_KEY,
265                    UploadService.REPOSITORY_DEFAULT);
266    
267            headerEncoding = conf.getAttribute(
268                    UploadService.HEADER_ENCODING_KEY,
269                    UploadService.HEADER_ENCODING_DEFAULT);
270    
271            sizeMax = conf.getAttributeAsInteger(
272                    UploadService.SIZE_MAX_KEY,
273                    UploadService.SIZE_MAX_DEFAULT);
274    
275            sizeThreshold = conf.getAttributeAsInteger(
276                    UploadService.SIZE_THRESHOLD_KEY,
277                    UploadService.SIZE_THRESHOLD_DEFAULT);
278        }
279    
280        /**
281         * Avalon component lifecycle method
282         *
283         * Initializes the service.
284         *
285         * This method processes the repository path, to make it relative to the
286         * web application root, if necessary
287         */
288        public void initialize() throws Exception
289        {
290            // test for the existence of the path within the webapp directory.
291            // if it does not exist, assume the path was to be used as is.
292            String testPath = getRealPath(repositoryPath);
293            File testDir = new File(testPath);
294            if ( testDir.exists() )
295            {
296                repositoryPath = testPath;
297            }
298    
299            getLogger().debug(
300                    "Upload Service: REPOSITORY_KEY => " + repositoryPath);
301    
302            itemFactory = new DiskFileItemFactory(sizeThreshold, new File(repositoryPath));
303        }
304    
305        /**
306         * Avalon component lifecycle method
307         */
308        public void contextualize(Context context) throws ContextException
309        {
310            this.applicationRoot = context.get( "urn:avalon:home" ).toString();
311        }
312    }