Java tutorial
/*----------------------------------------------------------------------------- * Copyright (c) 2008 Civic Computing Ltd. * All rights reserved. * * This file is part of Content Control. * * Content Control is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Content Control is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Content Control. If not, see http://www.gnu.org/licenses/. * * Revision $Rev$ * Modified by $Author$ * Modified on $Date$ * * Changes: see subversion log. *----------------------------------------------------------------------------- */ package ccc.plugins.multipart.apache; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUpload; import org.apache.commons.fileupload.FileUploadBase; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException; import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import ccc.api.exceptions.InvalidException; import ccc.api.types.DBC; import ccc.commons.Resources; import ccc.plugins.multipart.MultipartFormData; /** * A multi-part form implemented via Apache commons file-upload. * * @author Civic Computing Ltd. */ public class MultipartForm implements MultipartFormData { private static final Properties PROPS = Resources.readIntoProps("uploadConfig.properties"); private final Map<String, FileItem> _formItems = new HashMap<String, FileItem>(); private final Map<String, FileItem> _files = new HashMap<String, FileItem>(); private final String _charEncoding; /** * Constructor. * * @param items The list of items on this form. * @param charEncoding The character encoding of the input stream. */ public MultipartForm(final List<FileItem> items, final String charEncoding) { _charEncoding = selectEncoding(charEncoding); DBC.require().notNull(items); for (final FileItem item : items) { final String key = item.getFieldName(); if (item.isFormField()) { addItem(_formItems, key, item); } else { addItem(_files, key, item); } } /* * #430: IE 6 and IE 7 send two fields with same name. One is with * content type 'file' and the other one is the path of the original * file location. */ } /** * Constructor. * * @param charEncoding The character encoding of the input stream. * @param contentLength The number of bytes on the input stream. * @param contentType The input stream's media type. * @param inputStream The stream to parse as multipart. */ public MultipartForm(final String charEncoding, final int contentLength, final String contentType, final InputStream inputStream) { this(parseFileItems( new JaxrsRequestContext(selectEncoding(charEncoding), contentLength, contentType, inputStream)), charEncoding); } /** * Retrieve form item keys. * * @return List of names. */ public List<String> getFormItemNames() { final List<String> keyList = new ArrayList<String>(); for (final String key : _formItems.keySet()) { keyList.add(key); } return keyList; } /** {@inheritDoc} */ @Override public InputStream getInputStream(final String string) throws IOException { final FileItem item = getFormItem(string); return (null == item) ? null : item.getInputStream(); } /** {@inheritDoc} */ @Override public String getContentType(final String string) { final FileItem item = getFormItem(string); return (null == item) ? null : item.getContentType(); } /** {@inheritDoc} */ @Override public long getSize(final String string) { final FileItem item = getFormItem(string); return (null == item) ? -1 : item.getSize(); } /** {@inheritDoc} */ @Override public String getString(final String string) { try { final FileItem item = getFormItem(string); return (null == item) ? null : item.getString(_charEncoding); } catch (final UnsupportedEncodingException e) { throw new RuntimeException("JVM doesn't support encoding: " + _charEncoding, e); } } /** {@inheritDoc} */ @Override public String getFilename(final String key) { final FileItem item = getFormItem(key); return (null == item) ? null : Resources.lastPart(item.getName()); } private void addItem(final Map<String, FileItem> items, final String key, final FileItem item) { if (items.containsKey(key)) { throw new RuntimeException("Duplicate field on form: " + key); } items.put(key, item); } /** * Parse an HTTP request, extracting the file items. * * @param context The JAXRS context to parse. * * @return A list of file items. */ @SuppressWarnings("unchecked") private static List<FileItem> parseFileItems(final JaxrsRequestContext context) { DBC.require().notNull(context); // Check that we have a file upload request final boolean isMultipart = FileUploadBase.isMultipartContent(context); if (!isMultipart) { throw new RuntimeException("Not a multipart."); } // Create a factory for disk-based file items final DiskFileItemFactory factory = new DiskFileItemFactory(); // Set factory constraints factory.setSizeThreshold(maxInMemorySize()); // Create a new file upload handler final FileUpload upload = new FileUpload(factory); // Set overall request size constraint upload.setFileSizeMax(maxFileSize()); try { return upload.parseRequest(context); } catch (final FileSizeLimitExceededException e) { throw handleTooBig(e, e.getPermittedSize()); } catch (final SizeLimitExceededException e) { throw handleTooBig(e, e.getPermittedSize()); } catch (final FileUploadException e) { throw new RuntimeException("Failed to parse multipart request.", e); } } private static InvalidException handleTooBig(final Throwable t, final long permittedSize) { final String message = "The file exceeds the maximum permitted size of " + permittedSize + " bytes."; final InvalidException ie = new InvalidException(message, t); ie.setResolution("Upload a smaller file."); return ie; } /** * Get the file item for a key. * <p>If both a file & simple value exists the file will be returned. * * @param key The key to the file item. * * @return The corresponding file item or NULL if it doesn't exist. */ private FileItem getFormItem(final String key) { final FileItem item = _files.get(key); if (null != item) { return item; } return _formItems.get(key); } /** * Determine the max size of a file stored in memory. * * @return The max number of bytes, as an int. */ static int maxInMemorySize() { final int defaultValue = 500 * 1024; // 500kb final String propValue = PROPS.getProperty("max-in-memory"); try { return DBC.ensure().greaterThan(-1, Integer.valueOf(propValue).intValue()); } catch (final RuntimeException e) { return defaultValue; } } /** * Determine the max size of a file that can be handled. * * @return The max number of bytes, as an int. */ static int maxFileSize() { final int defaultValue = 32 * 1024 * 1024; // 32mb final String propValue = PROPS.getProperty("maxFileSize"); try { return DBC.ensure().greaterThan(0, Integer.valueOf(propValue).intValue()); } catch (final RuntimeException e) { return defaultValue; } } private static String selectEncoding(final String charEncoding) { return (null == charEncoding) ? "UTF-8" : charEncoding; } }