/******************************************************************************* * Copyright (c) 2002-2006 Innoopract Informationssysteme GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Innoopract Informationssysteme GmbH - initial API and implementation ******************************************************************************/ package org.eclipse.rwt.widgets.upload.servlet; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.*; import org.apache.commons.fileupload.*; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.eclipse.rwt.RWT; import org.eclipse.rwt.internal.util.URLHelper; import org.eclipse.rwt.service.IServiceHandler; import org.eclipse.rwt.widgets.IUploadConfiguration; import org.eclipse.rwt.widgets.internal.UploadConfiguration; /** * Handles file uploads and upload progress updates. * <p> * Implementation note: uploaded files are currently stored in the * java.io.tmpdir. See * {@link #handleFileUpload(HttpServletRequest, FileUploadStorageItem)} on * how to change this. * * @author stefan.roeck */ public class FileUploadServiceHandler implements IServiceHandler { private static final String REQUEST_WIDGET_ID = "widgetId"; private static final String REQUEST_PROCESS_ID = "processId"; private static final String XML_HEAD = "<?xml version=\"1.0\" encoding=\"utf-8\"?><response>"; /** * Holds configuration data for the upload widget. */ private static final IUploadConfiguration uploadConfiguration = new UploadConfiguration(); /** * Requests to this service handler without a valid session id are ignored for * security reasons. The same applies to request with widgetIds which haven't been * registered at the session singleton {@link FileUploadStorage}. */ public void service() throws IOException, ServletException { final HttpServletRequest request = RWT.getRequest(); final String widgetId = request.getParameter( REQUEST_WIDGET_ID ); final String uploadProcessId = request.getParameter( REQUEST_PROCESS_ID ); final HttpSession session = request.getSession( false ); if( session != null && widgetId != null && !"".equals( widgetId ) && uploadProcessId != null && !"".equals( uploadProcessId ) ) { final FileUploadStorage fileUploadStorage = FileUploadStorage.getInstance(); final FileUploadStorageItem fileUploadStorageItem = fileUploadStorage.getUploadStorageItem( widgetId ); // fileUploadStorageItem can be null, if Upload widget is dispsed! if (ServletFileUpload.isMultipartContent(request)) { // Handle post-request which contains the file to upload handleFileUpload( request, fileUploadStorageItem, uploadProcessId ); } else { // This is probably a request for updating the progress bar handleUpdateProgress( fileUploadStorageItem, uploadProcessId ); } } } /** * Treats the request as a post request which contains the file to be * uploaded. Uses the apache commons fileupload library to * extract the file from the request, attaches a {@link FileUploadListener} to * get notified about the progress and writes the file content * to the given {@link FileUploadStorageItem} * @param request Request object, must not be null * @param fileUploadStorageitem Object where the file content is set to. * If null, nothing happens. * @param uploadProcessId Each upload action has a unique process identifier to * match subsequent polling calls to get the progress correctly to the uploaded file. * */ private void handleFileUpload( final HttpServletRequest request, final FileUploadStorageItem fileUploadStorageitem, final String uploadProcessId ) { // Ignore upload requests which have no valid widgetId if (fileUploadStorageitem != null && uploadProcessId != null && !"".equals( uploadProcessId )) { // Reset storage item to clear values from last upload process fileUploadStorageitem.reset(); // Create file upload factory and upload servlet // You could use new DiskFileItemFactory(threshold, location) // to configure a custom in-memory threshold and storage location. // By default the upload files are stored in the java.io.tmpdir final FileItemFactory factory = new DiskFileItemFactory(); final ServletFileUpload upload = new ServletFileUpload( factory ); // apply configuration params applyConfiguration(upload); // Create a file upload progress listener final ProgressListener listener = new ProgressListener() { public void update( final long aBytesRead, final long aContentLength, final int anItem ) { fileUploadStorageitem.updateProgress( aBytesRead, aContentLength ); } }; // Upload servlet allows to set upload listener upload.setProgressListener( listener ); fileUploadStorageitem.setUploadProcessId( uploadProcessId ); FileItem fileItem = null; try { final List uploadedItems = upload.parseRequest( request ); // Only one file upload at once is supported. If there are multiple files, take // the first one and ignore other if ( uploadedItems.size() > 0 ) { fileItem = ( FileItem )uploadedItems.get( 0 ); // Don't check for file size 0 because this prevents uploading new empty office xp documents // which have a file size of 0. if( !fileItem.isFormField() ) { fileUploadStorageitem.setFileInputStream( fileItem.getInputStream() ); fileUploadStorageitem.setContentType(fileItem.getContentType()); } } } catch( final FileUploadException e ) { fileUploadStorageitem.setException(e); } catch( final Exception e ) { fileUploadStorageitem.setException(e); } } } /** * Applies custom configuration parameters specified by the * user. * @param upload The upload handler to which the config is applied. */ private void applyConfiguration( final ServletFileUpload upload ) { upload.setFileSizeMax( getConfiguration().getFileSizeMax() ); upload.setSizeMax( getConfiguration().getSizeMax() ); } /** * Treats the request as a get request which is triggered by the * browser to retrieve the progress state. Gets the registered * {@link FileUploadListener} from the given {@link FileUploadStorageItem} * to check the current progress. The result is written to the response * in an XML format. * <br> * <b>Note:</b> It is important that a valid response is written in any case * to let the Browser know, when polling can be stopped. * @param uploadProcessId */ private void handleUpdateProgress( final FileUploadStorageItem fileUploadStorageitem, final String uploadProcessId ) throws IOException { final HttpServletResponse response = RWT.getResponse(); final PrintWriter out = response.getWriter(); final StringBuffer buffy = new StringBuffer( XML_HEAD ); long bytesRead = 0; long contentLength = 0; // Check to see if we've created the listener object yet response.setContentType( "text/xml" ); response.setHeader( "Cache-Control", "no-cache" ); if( fileUploadStorageitem != null ) { if ( uploadProcessId != null && uploadProcessId.equals( fileUploadStorageitem.getUploadProcessId() )) { // Get the meta information bytesRead = fileUploadStorageitem.getBytesRead(); contentLength = fileUploadStorageitem.getContentLength(); /* * XML Response Code */ buffy.append( "<bytes_read>" ); buffy.append( bytesRead ); buffy.append( "</bytes_read><content_length>" ); buffy.append( contentLength ); buffy.append( "</content_length>" ); // Check to see if we're done // Even files with a size of 0 have a content length > 0 if( contentLength != 0 ) { if( bytesRead == contentLength ) { buffy.append( "<finished />" ); } else { // Calculate the percent complete buffy.append( "<percent_complete>" ); buffy.append( ( 100 * bytesRead / contentLength ) ); buffy.append( "</percent_complete>" ); } } else { // Contentlength should not be 0, however, send finished to make sure // the Browser side polling stops. buffy.append( "<finished />" ); } } else { //System.out.println("No match: " + uploadProcessId + " " + fileUploadStorageitem.getUploadProcessId()); // if the processId doesn't match, return nothing // which causes the client script to send another // request after waiting. This could happen, // if the first GET-request was send, before the // Upload-POST request arrived. } } else { // if fileUploadStorageitem is null, the upload widget is disposed // return "finished" to stop monitoring buffy.append( "<finished />" ); } buffy.append( "</response>" ); out.println( buffy.toString() ); out.flush(); out.close(); } /** * Registers this service handler. This method should be called only once. */ public static void register() { final FileUploadServiceHandler instance = new FileUploadServiceHandler(); final String serviceHandlerId = getServiceHandlerId(); RWT.getServiceManager().registerServiceHandler(serviceHandlerId, instance); } /** * Returns a unique id for this service handler class. */ private static String getServiceHandlerId() { final String serviceHandlerId = FileUploadServiceHandler.class.getName(); return serviceHandlerId; } /** * Builds a url which points to the service handler and encodes the given parameters * as url parameters. */ public static String getUrl(final String widgetId) { final StringBuffer url = new StringBuffer(); url.append(URLHelper.getURLString(false)); URLHelper.appendFirstParam(url, REQUEST_PARAM, getServiceHandlerId()); URLHelper.appendParam(url, REQUEST_WIDGET_ID, widgetId); // convert to relative URL final int firstSlash = url.indexOf( "/" , url.indexOf( "//" ) + 2 ); // first slash after double slash of "http://" url.delete( 0, firstSlash ); // Result is sth like "/rap?custom_service_handler..." return RWT.getResponse().encodeURL(url.toString()); } /** * Returns a configuration facade. */ public static IUploadConfiguration getConfiguration() { return uploadConfiguration; } }