--- /dev/null
+/*******************************************************************************
+ * 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;
+ }
+}