]> source.dussan.org Git - vaadin-framework.git/commitdiff
refactored handling of Receiver's lots of other upload related improvements. fixes...
authorMatti Tahvonen <matti.tahvonen@itmill.com>
Fri, 8 Oct 2010 11:21:50 +0000 (11:21 +0000)
committerMatti Tahvonen <matti.tahvonen@itmill.com>
Fri, 8 Oct 2010 11:21:50 +0000 (11:21 +0000)
svn changeset:15461/svn branch:6.5

27 files changed:
build/build.xml
src/com/vaadin/terminal/PaintTarget.java
src/com/vaadin/terminal/Receiver.java [new file with mode: 0644]
src/com/vaadin/terminal/ReceiverOwner.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java
src/com/vaadin/terminal/gwt/client/ui/VUpload.java
src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
src/com/vaadin/terminal/gwt/server/AbstractReceivingEvent.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/CommunicationManager.java
src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java
src/com/vaadin/terminal/gwt/server/NoInputStreamException.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/NoOutputStreamException.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java
src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java
src/com/vaadin/terminal/gwt/server/ReceivingEndedEventImpl.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/ReceivingFailedEventImpl.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/ReceivingProgressedEventImpl.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/ReceivingStartedEventImpl.java [new file with mode: 0644]
src/com/vaadin/terminal/gwt/server/UploadException.java [new file with mode: 0644]
src/com/vaadin/ui/DragAndDropWrapper.java
src/com/vaadin/ui/Html5File.java [new file with mode: 0644]
src/com/vaadin/ui/Upload.java
tests/src/com/vaadin/tests/dd/DDTest6.java
tests/src/com/vaadin/tests/dd/DragAndDropFiles.java [new file with mode: 0644]
tests/src/com/vaadin/tests/dd/DragDropPane.java

index ff988138573422c53596b4ed23763dcb83da7521..e7cad185aaf4125052584708ce7f8f2c1ddcab76 100644 (file)
         <!-- Construct classpath used by java and javadoc compilation -->
         <path id="compile.classpath">
             <pathelement path="build/lib/servlet.jar" />
-            <pathelement path="build/external/fileupload/classes" />
                <fileset dir="lib/core">
                    <include name="**/*.jar"/>
                        
         </copy>
     </target>
 
-    <target name="compile-fileupload">
-        <echo>Compiling custom fileupload classes.</echo>
-        <ant dir="build/external/fileupload" antfile="build.xml" target="compile" />
-    </target>
-
-    <target name="compile-java" depends="init, check-servlet-version, compile-fileupload, webcontent">
+    <target name="compile-java" depends="init, check-servlet-version, webcontent">
         <echo>Compiling src (server-side)</echo>
        
         <!-- Compile all sources at the same time as they depend on each other -->
                     <exclude name="${toolkit-package}/launcher/**" />
                 </patternset>
             </fileset>
-            <!-- fileupload, see build/external/fileupload/build.xml -->
-            <fileset dir="build/external/fileupload/classes">
-                <include name="**/*" />
-            </fileset>
             <!-- add sources -->
             <fileset dir="${result-path}/src/core">
                 <patternset>
index 7bfc3555fc5763e2e44e5589dd2f09c20e209f45..52ac4908635bedfa036abe236779a523167c0374 100644 (file)
@@ -151,6 +151,22 @@ public interface PaintTarget extends Serializable {
      */
     public void addAttribute(String name, Resource value) throws PaintException;
 
+    /**
+     * Adds a Receiver attribute to component. Eg. in web terminals Receivers
+     * are typically URIs, where the client side can do an http post (multipart
+     * request).
+     * 
+     * @param name
+     *            the Attribute name
+     * @param value
+     *            the Attribute value
+     * 
+     * @throws PaintException
+     *             if the paint operation failed.
+     */
+    public void addVariable(ReceiverOwner owner, String name, Receiver value)
+            throws PaintException;
+
     /**
      * Adds a long attribute to component. Atributes must be added before any
      * content is written.
diff --git a/src/com/vaadin/terminal/Receiver.java b/src/com/vaadin/terminal/Receiver.java
new file mode 100644 (file)
index 0000000..6958cfd
--- /dev/null
@@ -0,0 +1,29 @@
+package com.vaadin.terminal;
+
+import java.io.OutputStream;
+import java.io.Serializable;
+
+/**
+ * 
+ * Interface that must be implemented by the upload receivers to provide the
+ * Upload component an output stream to write the uploaded data.
+ * 
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 6.5
+ */
+public interface Receiver extends Serializable {
+
+    /**
+     * Invoked when a new upload arrives.
+     * 
+     * @param filename
+     *            the desired filename of the upload, usually as specified by
+     *            the client.
+     * @param MIMEType
+     *            the MIME type of the uploaded file.
+     * @return Stream to which the uploaded file should be written.
+     */
+    public OutputStream receiveUpload(String filename, String MIMEType);
+}
diff --git a/src/com/vaadin/terminal/ReceiverOwner.java b/src/com/vaadin/terminal/ReceiverOwner.java
new file mode 100644 (file)
index 0000000..28ec83b
--- /dev/null
@@ -0,0 +1,161 @@
+package com.vaadin.terminal;
+
+import java.io.Serializable;
+
+import com.vaadin.Application;
+import com.vaadin.terminal.ReceiverOwner.ReceivingController;
+
+/**
+ * Special kind of {@link VariableOwner} that can send and receive information
+ * with the terminal implementation about the progress of receiving data to its
+ * Receiver. The actual communication happens via {@link ReceivingController}
+ * which is fetched by the terminal when the Receiving is about to start.
+ */
+public interface ReceiverOwner extends VariableOwner {
+
+    /*
+     * The monitor/control is passed to separate ReceivingContorller because:
+     * 
+     * - possibly some component in the future may need support for streaming to
+     * multiple Receivers at the same time.
+     * 
+     * - we don't want to bloat implementing ReceiverOwner's API. Now only one
+     * method is published and they can decide what event/methods to publish as
+     * their public API.
+     */
+
+    /**
+     * Returns a handle for the terminal via the ReceiverOwner can monitor and
+     * control the steaming of data to {@link Receiver}.
+     * <p>
+     * Most commonly ReceiverOwner implementation wants to implement this method
+     * as final and reveal its own API for the end users.
+     * 
+     * @param receiver
+     *            the Receiver whose streaming is to be controlled
+     * @return a {@link ReceivingController} that will be used to control and
+     *         monitor the progress of streaming
+     */
+    ReceivingController getReceivingController(Receiver receiver);
+
+    interface ReceivingEvent extends Serializable {
+
+        /**
+         * @return the file name of the streamed file if known
+         */
+        String getFileName();
+
+        /**
+         * @return the mime type of the streamed file if known
+         */
+        String getMimeType();
+
+        /**
+         * @return the Receiver into which the content is being streamed
+         */
+        Receiver getReceiver();
+
+        /**
+         * @return the length of the stream (in bytes) if known, else -1
+         */
+        long getContentLength();
+
+        /**
+         * @return then number of bytes streamed to Receiver
+         */
+        long getBytesReceived();
+    }
+
+    /**
+     * Event passed to
+     * {@link ReceivingController#uploadStarted(ReceivingStartedEvent)} method
+     * before the streaming of the content to {@link Receiver} starts.
+     */
+    public interface ReceivingStartedEvent extends ReceivingEvent {
+    }
+
+    /**
+     * Event passed to
+     * {@link ReceivingController#onProgress(ReceivingProgressedEvent)} method
+     * during the streaming progresses.
+     */
+    public interface ReceivingProgressedEvent extends ReceivingEvent {
+    }
+
+    /**
+     * Event passed to
+     * {@link ReceivingController#uploadFinished(ReceivingEndedEvent)} method
+     * the contents have been streamed to Receiver successfully.
+     */
+    public interface ReceivingEndedEvent extends ReceivingEvent {
+    }
+
+    /**
+     * Event passed to
+     * {@link ReceivingController#uploadFailed(ReceivingFailedEvent)} method
+     * when the streaming ended before the end of the input. The streaming may
+     * fail due an interruption by {@link ReceivingController} or due an other
+     * unknown exception in communication. In the latter case the exception is
+     * also passed to
+     * {@link Application#terminalError(com.vaadin.terminal.Terminal.ErrorEvent)}
+     * .
+     */
+    public interface ReceivingFailedEvent extends ReceivingEvent {
+
+        /**
+         * @return the exception that caused the receiving not to finish cleanly
+         */
+        Exception getException();
+
+    }
+
+    public interface ReceivingController {
+        /**
+         * Whether the {@link #onProgress(long, long)} method should be called
+         * during the upload.
+         * <p>
+         * {@link #onProgress(long, long)} is called in a synchronized block
+         * during the content is being received. This is potentially bit slow,
+         * so we are calling this method only if requested. The value is
+         * requested after the {@link #uploadStarted(ReceivingStartedEvent)}
+         * event, but not after each buffer reading.
+         * 
+         * @return true if this ReceiverOwner wants to by notified during the
+         *         upload of the progress of streaming.
+         * @see ReceiverOwner#onProgress(int, int)
+         */
+        boolean listenProgress();
+
+        /**
+         * This method is called by the terminal if {@link #listenProgress()}
+         * returns true when the streaming starts.
+         */
+        void onProgress(ReceivingProgressedEvent event);
+
+        void uploadStarted(ReceivingStartedEvent event);
+
+        void uploadFinished(ReceivingEndedEvent event);
+
+        void uploadFailed(ReceivingFailedEvent event);
+
+        /*
+         * Not synchronized to avoid stalls (caused by UIDL requests) while
+         * streaming the content. Implementations also most commonly atomic even
+         * without the restriction.
+         */
+        /**
+         * ReceiverOwner can set this flag to true if it wants the Terminal to
+         * stop receiving current upload.
+         * <p>
+         * Note, the usage of this method is not synchronized over the
+         * Application instance by the terminal like other methods. The
+         * implementation should only return a boolean field and especially not
+         * to modify UI or implement a synchronization by itself.
+         * 
+         * @return true if the streaming should be interrupted as soon as
+         *         possible.
+         */
+        boolean isInterrupted();
+    }
+
+}
index ffbe25e9a78757ed7c447a5b284e83f08066ffdd..702b3241294525c263f9e37dfe357340107cdc61 100644 (file)
@@ -3,8 +3,13 @@
  */
 package com.vaadin.terminal.gwt.client.ui;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArrayString;
 import com.google.gwt.event.dom.client.MouseDownEvent;
@@ -33,7 +38,6 @@ import com.vaadin.terminal.gwt.client.ui.dd.VDropHandler;
 import com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler;
 import com.vaadin.terminal.gwt.client.ui.dd.VHtml5DragEvent;
 import com.vaadin.terminal.gwt.client.ui.dd.VHtml5File;
-import com.vaadin.terminal.gwt.client.ui.dd.VHtml5File.Callback;
 import com.vaadin.terminal.gwt.client.ui.dd.VTransferable;
 import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation;
 
@@ -97,45 +101,115 @@ public class VDragAndDropWrapper extends VCustomComponent implements
     private int dragStarMode;
     private int filecounter = 0;
     private boolean dragLeavPostponed;
+    private Map<String, String> fileIdToReveiver;
 
     @Override
     public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
         this.client = client;
         super.updateFromUIDL(uidl, client);
         if (!uidl.hasAttribute("cached") && !uidl.hasAttribute("hidden")) {
-            int childCount = uidl.getChildCount();
-            if (childCount > 1) {
-                UIDL childUIDL = uidl.getChildUIDL(1);
+            UIDL acceptCrit = uidl.getChildByTagName("-ac");
+            if (acceptCrit == null) {
+                dropHandler = null;
+            } else {
                 if (dropHandler == null) {
                     dropHandler = new CustomDropHandler();
                 }
-                dropHandler.updateAcceptRules(childUIDL);
-            } else {
-                dropHandler = null;
+                dropHandler.updateAcceptRules(acceptCrit);
+            }
+
+            Set<String> variableNames = uidl.getVariableNames();
+            for (String fileId : variableNames) {
+                if (fileId.startsWith("rec-")) {
+                    String receiverUrl = uidl.getStringVariable(fileId);
+                    fileId = fileId.substring(4);
+                    if (fileIdToReveiver == null) {
+                        fileIdToReveiver = new HashMap<String, String>();
+                    }
+                    if ("".equals(receiverUrl)) {
+                        Integer id = Integer.parseInt(fileId);
+                        int indexOf = fileIds.indexOf(id);
+                        if (indexOf != -1) {
+                            files.remove(indexOf);
+                            fileIds.remove(indexOf);
+                        }
+                    } else {
+                        fileIdToReveiver.put(fileId, receiverUrl);
+                    }
+                }
             }
+            startNextUpload();
 
             dragStarMode = uidl.getIntAttribute("dragStartMode");
         }
     }
 
+    private boolean uploading;
+
+    private ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() {
+        public void onReadyStateChange(XMLHttpRequest xhr) {
+            if (xhr.getReadyState() == XMLHttpRequest.DONE) {
+                // visit server for possible
+                // variable changes
+                client.sendPendingVariableChanges();
+                uploading = false;
+                startNextUpload();
+                xhr.clearOnReadyStateChange();
+            }
+        }
+    };
+
+    private void startNextUpload() {
+        DeferredCommand.addCommand(new Command() {
+
+            public void execute() {
+                if (!uploading) {
+                    if (fileIds.size() > 0) {
+
+                        uploading = true;
+                        final Integer fileId = fileIds.remove(0);
+                        VHtml5File file = files.remove(0);
+                        final String receiverUrl = fileIdToReveiver
+                                .remove(fileId.toString());
+                        ExtendedXHR extendedXHR = (ExtendedXHR) ExtendedXHR
+                                .create();
+                        extendedXHR
+                                .setOnReadyStateChange(readyStateChangeHandler);
+                        extendedXHR.open("POST", receiverUrl);
+                        extendedXHR.postFile(file);
+
+                    }
+                }
+
+            }
+        });
+
+    }
+
     public boolean html5DragEnter(VHtml5DragEvent event) {
         if (dropHandler == null) {
             return true;
         }
-        if (dragLeavPostponed) {
-            // returned quickly back to wrapper
-            dragLeavPostponed = false;
+        try {
+
+            if (dragLeavPostponed) {
+                // returned quickly back to wrapper
+                dragLeavPostponed = false;
+                return false;
+            }
+            VTransferable transferable = new VTransferable();
+            transferable.setDragSource(this);
+
+            vaadinDragEvent = VDragAndDropManager.get().startDrag(transferable,
+                    event, false);
+            VDragAndDropManager.get().setCurrentDropHandler(getDropHandler());
+            event.preventDefault();
+            event.stopPropagation();
             return false;
+        } catch (Exception e) {
+            GWT.getUncaughtExceptionHandler().onUncaughtException(e);
+            return true;
         }
-        VTransferable transferable = new VTransferable();
-        transferable.setDragSource(this);
-
-        vaadinDragEvent = VDragAndDropManager.get().startDrag(transferable,
-                event, false);
-        VDragAndDropManager.get().setCurrentDropHandler(getDropHandler());
-        event.preventDefault();
-        event.stopPropagation();
-        return false;
     }
 
     public boolean html5DragLeave(VHtml5DragEvent event) {
@@ -143,24 +217,32 @@ public class VDragAndDropWrapper extends VCustomComponent implements
             return true;
         }
 
-        dragLeavPostponed = true;
-        DeferredCommand.addCommand(new Command() {
-            public void execute() {
-                // Yes, dragleave happens before drop. Makes no sense to me.
-                // IMO shouldn't fire leave at all if drop happens (I guess this
-                // is what IE does).
-                // In Vaadin we fire it only if drop did not happen.
-                if (dragLeavPostponed
-                        && vaadinDragEvent != null
-                        && VDragAndDropManager.get().getCurrentDropHandler() == getDropHandler()) {
-                    VDragAndDropManager.get().interruptDrag();
+        try {
+
+            dragLeavPostponed = true;
+            DeferredCommand.addCommand(new Command() {
+                public void execute() {
+                    // Yes, dragleave happens before drop. Makes no sense to me.
+                    // IMO shouldn't fire leave at all if drop happens (I guess
+                    // this
+                    // is what IE does).
+                    // In Vaadin we fire it only if drop did not happen.
+                    if (dragLeavPostponed
+                            && vaadinDragEvent != null
+                            && VDragAndDropManager.get()
+                                    .getCurrentDropHandler() == getDropHandler()) {
+                        VDragAndDropManager.get().interruptDrag();
+                    }
+                    dragLeavPostponed = false;
                 }
-                dragLeavPostponed = false;
-            }
-        });
-        event.preventDefault();
-        event.stopPropagation();
-        return false;
+            });
+            event.preventDefault();
+            event.stopPropagation();
+            return false;
+        } catch (Exception e) {
+            GWT.getUncaughtExceptionHandler().onUncaughtException(e);
+            return true;
+        }
     }
 
     public boolean html5DragOver(VHtml5DragEvent event) {
@@ -188,41 +270,47 @@ public class VDragAndDropWrapper extends VCustomComponent implements
         if (dropHandler == null || !currentlyValid) {
             return true;
         }
+        try {
 
-        VTransferable transferable = vaadinDragEvent.getTransferable();
+            VTransferable transferable = vaadinDragEvent.getTransferable();
 
-        JsArrayString types = event.getTypes();
-        for (int i = 0; i < types.length(); i++) {
-            String type = types.get(i);
-            if (isAcceptedType(type)) {
-                String data = event.getDataAsText(type);
-                if (data != null) {
-                    transferable.setData(type, data);
+            JsArrayString types = event.getTypes();
+            for (int i = 0; i < types.length(); i++) {
+                String type = types.get(i);
+                if (isAcceptedType(type)) {
+                    String data = event.getDataAsText(type);
+                    if (data != null) {
+                        transferable.setData(type, data);
+                    }
                 }
             }
-        }
 
-        int fileCount = event.getFileCount();
-        if (fileCount > 0) {
-            transferable.setData("filecount", fileCount);
-            for (int i = 0; i < fileCount; i++) {
-                final int fileId = filecounter++;
-                final VHtml5File file = event.getFile(i);
-                transferable.setData("fi" + i, "" + fileId);
-                transferable.setData("fn" + i, file.getName());
-                transferable.setData("ft" + i, file.getType());
-                transferable.setData("fs" + i, file.getSize());
-                postFile(fileId, file);
+            int fileCount = event.getFileCount();
+            if (fileCount > 0) {
+                transferable.setData("filecount", fileCount);
+                for (int i = 0; i < fileCount; i++) {
+                    final int fileId = filecounter++;
+                    final VHtml5File file = event.getFile(i);
+                    transferable.setData("fi" + i, "" + fileId);
+                    transferable.setData("fn" + i, file.getName());
+                    transferable.setData("ft" + i, file.getType());
+                    transferable.setData("fs" + i, file.getSize());
+                    queueFilePost(fileId, file);
+                }
+
             }
 
-        }
+            VDragAndDropManager.get().endDrag();
+            vaadinDragEvent = null;
+            event.preventDefault();
+            event.stopPropagation();
 
-        VDragAndDropManager.get().endDrag();
-        vaadinDragEvent = null;
-        event.preventDefault();
-        event.stopPropagation();
+            return false;
+        } catch (Exception e) {
+            GWT.getUncaughtExceptionHandler().onUncaughtException(e);
+            return true;
+        }
 
-        return false;
     }
 
     protected String[] acceptedTypes = new String[] { "Text", "Url",
@@ -242,10 +330,14 @@ public class VDragAndDropWrapper extends VCustomComponent implements
         protected ExtendedXHR() {
         }
 
-        public final native void sendBinary(JavaScriptObject data)
+        public final native void postFile(VHtml5File file)
         /*-{
-            //this.overrideMimeType('text/plain; charset=x-user-defined-binary');
-            this.sendAsBinary(data);
+
+            // Accept header is readable in portlets resourceRequest
+            // TODO add filename and mime type too??
+            this.setRequestHeader('Accept', 'text/html,vaadin/filexhr');
+            this.setRequestHeader('Content-Type', 'multipart/form-data');
+            this.send(file);
         }-*/;
 
     }
@@ -257,38 +349,12 @@ public class VDragAndDropWrapper extends VCustomComponent implements
      * @param fileId
      * @param data
      */
-    private void postFile(final int fileId, final VHtml5File file) {
-        DeferredCommand.addCommand(new Command() {
-            public void execute() {
-                /*
-                 * File contents is sent deferred to allow quick reaction on GUI
-                 * although file upload may last long.
-                 */
-                file.readAsBinary(new Callback() {
-                    public void handleFile(final JavaScriptObject object) {
-
-                        ExtendedXHR extendedXHR = (ExtendedXHR) ExtendedXHR
-                                .create();
-                        String name = "XHRFILE" + getPid() + "." + fileId;
-                        extendedXHR
-                                .setOnReadyStateChange(new ReadyStateChangeHandler() {
-                                    public void onReadyStateChange(
-                                            XMLHttpRequest xhr) {
-                                        if (xhr.getReadyState() == XMLHttpRequest.DONE) {
-                                            client.sendPendingVariableChanges();
-                                            xhr.clearOnReadyStateChange();
-                                        }
-                                    }
-                                });
-                        extendedXHR.open("POST", client.getAppUri());
-                        multipartSend(extendedXHR, object, name);
-
-                    }
-                });
-
-            }
-        });
+    private List<Integer> fileIds = new ArrayList<Integer>();
+    private List<VHtml5File> files = new ArrayList<VHtml5File>();
 
+    private void queueFilePost(final int fileId, final VHtml5File file) {
+        fileIds.add(fileId);
+        files.add(file);
     }
 
     private String getPid() {
index 4cb7464a80c8068eff934b66af7ad1d6dba55652..8b5243429537775761132c36a47d08f3d63828aa 100644 (file)
@@ -147,7 +147,7 @@ public class VUpload extends SimplePanel implements Paintable {
         this.client = client;
         paintableId = uidl.getId();
         nextUploadId = uidl.getIntAttribute("nextid");
-        element.setAction(client.getAppUri());
+        element.setAction(uidl.getStringVariable("action"));
         submitButton.setText(uidl.getStringAttribute("buttoncaption"));
         fu.setName(paintableId + "_file");
 
index 8d5d32840257241a1b5d9fa942ad74b1d146e93d..f6a88d91006916117b455e99c150c7cc22c00e8b 100644 (file)
@@ -49,7 +49,6 @@ import com.liferay.portal.kernel.util.PortalClassInvoker;
 import com.liferay.portal.kernel.util.PropsUtil;
 import com.vaadin.Application;
 import com.vaadin.Application.SystemMessages;
-import com.vaadin.external.org.apache.commons.fileupload.portlet.PortletFileUpload;
 import com.vaadin.terminal.DownloadStream;
 import com.vaadin.terminal.Terminal;
 import com.vaadin.terminal.gwt.client.ApplicationConfiguration;
@@ -259,6 +258,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
         } else if (request instanceof ResourceRequest) {
             if (isUIDLRequest((ResourceRequest) request)) {
                 return RequestType.UIDL;
+            } else if (isFileUploadRequest((ResourceRequest) request)) {
+                return RequestType.FILE_UPLOAD;
             } else if (isApplicationResourceRequest((ResourceRequest) request)) {
                 return RequestType.APPLICATION_RESOURCE;
             } else if (isDummyRequest((ResourceRequest) request)) {
@@ -267,12 +268,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
                 return RequestType.STATIC_FILE;
             }
         } else if (request instanceof ActionRequest) {
-            if (isFileUploadRequest((ActionRequest) request)) {
-                return RequestType.FILE_UPLOAD;
-            } else {
-                // action other than upload
-                return RequestType.ACTION;
-            }
+            return RequestType.ACTION;
         } else if (request instanceof EventRequest) {
             return RequestType.EVENT;
         }
@@ -294,8 +290,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
                 && request.getResourceID().equals("DUMMY");
     }
 
-    private boolean isFileUploadRequest(ActionRequest request) {
-        return PortletFileUpload.isMultipartContent(request);
+    private boolean isFileUploadRequest(ResourceRequest request) {
+        return "UPLOAD".equals(request.getResourceID());
     }
 
     /**
@@ -356,19 +352,6 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
                 PortletCommunicationManager applicationManager = applicationContext
                         .getApplicationManager(application);
 
-                if (response instanceof RenderResponse
-                        && applicationManager.dummyURL == null) {
-                    /*
-                     * The application manager needs an URL to the dummy page.
-                     * See the PortletCommunicationManager.sendUploadResponse
-                     * method for more information.
-                     */
-                    ResourceURL dummyURL = ((RenderResponse) response)
-                            .createResourceURL();
-                    dummyURL.setResourceID("DUMMY");
-                    applicationManager.dummyURL = dummyURL.toString();
-                }
-
                 /* Update browser information from request */
                 updateBrowserProperties(applicationContext.getBrowser(),
                         request);
@@ -438,7 +421,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
                 /* Handle the request */
                 if (requestType == RequestType.FILE_UPLOAD) {
                     applicationManager.handleFileUpload(
-                            (ActionRequest) request, (ActionResponse) response);
+                            (ResourceRequest) request,
+                            (ResourceResponse) response);
                     return;
                 } else if (requestType == RequestType.UIDL) {
                     // Handles AJAX UIDL requests
@@ -1054,7 +1038,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
         Map<String, String> config = new LinkedHashMap<String, String>();
 
         /*
-         * We need this in order to get uploads to work.
+         * We need this in order to get uploads to work. TODO this is not needed
+         * for uploads anymore, check if this is needed for some other things
          */
         PortletURL appUri = response.createActionURL();
         config.put("appUri", "'" + appUri.toString() + "'");
index f461fce4042b194668736b263ba76b6b1f556eb8..aedfb2a204ba6c9756607cb9dc739734be478569 100644 (file)
@@ -34,7 +34,6 @@ import javax.servlet.http.HttpSession;
 
 import com.vaadin.Application;
 import com.vaadin.Application.SystemMessages;
-import com.vaadin.external.org.apache.commons.fileupload.servlet.ServletFileUpload;
 import com.vaadin.terminal.DownloadStream;
 import com.vaadin.terminal.ParameterHandler;
 import com.vaadin.terminal.Terminal;
@@ -177,6 +176,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
     private final String resourcePath = null;
 
     private int resourceCacheTime = 3600;
+    static final String UPLOAD_URL_PREFIX = "APP/UPLOAD/";
 
     /**
      * Called by the servlet container to indicate to a servlet that the servlet
@@ -1362,7 +1362,18 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
     }
 
     private boolean isFileUploadRequest(HttpServletRequest request) {
-        return ServletFileUpload.isMultipartContent(request);
+        String pathInfo = getRequestPathInfo(request);
+
+        if (pathInfo == null) {
+            return false;
+        }
+
+        if (pathInfo.startsWith("/" + UPLOAD_URL_PREFIX)) {
+            return true;
+        }
+
+        return false;
+
     }
 
     private boolean isOnUnloadRequest(HttpServletRequest request) {
index 0773dca4ec652683ca376c4a892001358e8dc6e0..b9d6bf99fabd77ca15e90af50f0c326bbd00d889 100644 (file)
@@ -43,29 +43,26 @@ import javax.servlet.ServletResponse;
 
 import com.vaadin.Application;
 import com.vaadin.Application.SystemMessages;
-import com.vaadin.external.org.apache.commons.fileupload.FileItemIterator;
-import com.vaadin.external.org.apache.commons.fileupload.FileItemStream;
-import com.vaadin.external.org.apache.commons.fileupload.FileUpload;
-import com.vaadin.external.org.apache.commons.fileupload.FileUploadException;
-import com.vaadin.external.org.apache.commons.fileupload.ProgressListener;
 import com.vaadin.terminal.ApplicationResource;
 import com.vaadin.terminal.DownloadStream;
 import com.vaadin.terminal.PaintException;
 import com.vaadin.terminal.PaintTarget;
 import com.vaadin.terminal.Paintable;
 import com.vaadin.terminal.Paintable.RepaintRequestEvent;
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner;
+import com.vaadin.terminal.ReceiverOwner.ReceivingController;
+import com.vaadin.terminal.ReceiverOwner.ReceivingEndedEvent;
+import com.vaadin.terminal.ReceiverOwner.ReceivingFailedEvent;
+import com.vaadin.terminal.ReceiverOwner.ReceivingStartedEvent;
 import com.vaadin.terminal.Terminal.ErrorEvent;
 import com.vaadin.terminal.Terminal.ErrorListener;
 import com.vaadin.terminal.URIHandler;
-import com.vaadin.terminal.UploadStream;
 import com.vaadin.terminal.VariableOwner;
 import com.vaadin.terminal.gwt.client.ApplicationConnection;
 import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout;
 import com.vaadin.ui.AbstractField;
 import com.vaadin.ui.Component;
-import com.vaadin.ui.DragAndDropWrapper;
-import com.vaadin.ui.Upload;
-import com.vaadin.ui.Upload.UploadException;
 import com.vaadin.ui.Window;
 
 /**
@@ -261,6 +258,12 @@ public abstract class AbstractCommunicationManager implements
 
     }
 
+    static class UploadInterruptedException extends Exception {
+        public UploadInterruptedException() {
+            super("Upload interrupted by other thread");
+        }
+    }
+
     private static String GET_PARAM_REPAINT_ALL = "repaintAll";
 
     // flag used in the request to indicate that the security token should be
@@ -296,6 +299,9 @@ public abstract class AbstractCommunicationManager implements
 
     private static final int MAX_BUFFER_SIZE = 64 * 1024;
 
+    /* Same as in apache commons file upload library that was previously used. */
+    private static final int MAX_UPLOAD_BUFFER_SIZE = 4 * 1024;
+
     private static final String GET_PARAM_ANALYZE_LAYOUTS = "analyzeLayouts";
 
     private final ArrayList<Paintable> dirtyPaintables = new ArrayList<Paintable>();
@@ -332,146 +338,319 @@ public abstract class AbstractCommunicationManager implements
         requireLocale(application.getLocale().toString());
     }
 
-    /**
-     * Create an upload handler that is appropriate to the context in which the
-     * application is being run (servlet or portlet).
-     * 
-     * @return new {@link FileUpload} instance
-     */
-    protected abstract FileUpload createFileUpload();
+    protected Application getApplication() {
+        return application;
+    }
 
-    /**
-     * TODO New method - document me!
-     * 
-     * @param upload
-     * @param request
-     * @return
-     * @throws IOException
-     * @throws FileUploadException
-     */
-    protected abstract FileItemIterator getUploadItemIterator(
-            FileUpload upload, Request request) throws IOException,
-            FileUploadException;
+    private static final int LF = "\n".getBytes()[0];
+
+    private static final String CRLF = "\r\n";
+
+    private static String readLine(InputStream stream) throws IOException {
+        StringBuilder sb = new StringBuilder();
+        int readByte = stream.read();
+        while (readByte != LF) {
+            char c = (char) readByte;
+            sb.append(c);
+            readByte = stream.read();
+        }
+
+        return sb.substring(0, sb.length() - 1);
+    }
 
     /**
-     * TODO New method - document me!
+     * Method used to stream content from a multipart request (either from
+     * servlet or portlet request) to given Receiver
+     * 
      * 
      * @param request
      * @param response
+     * @param receiver
+     * @param owner
+     * @param boundary
      * @throws IOException
-     * @throws FileUploadException
      */
-    protected void doHandleFileUpload(Request request, Response response)
-            throws IOException, FileUploadException {
+    protected void doHandleSimpleMultipartFileUpload(Request request,
+            Response response, Receiver receiver, ReceiverOwner owner,
+            String boundary) throws IOException {
+        boundary = CRLF + "--" + boundary + "--";
+
+        // multipart parsing, supports only one file for request, but that is
+        // fine for our current terminal
 
-        // Create a new file upload handler
-        final FileUpload upload = createFileUpload();
+        final InputStream inputStream = request.getInputStream();
 
-        final UploadProgressListener pl = new UploadProgressListener();
+        int contentLength = request.getContentLength();
 
-        upload.setProgressListener(pl);
+        boolean atStart = false;
 
-        // Parse the request
-        FileItemIterator iter;
+        String rawfilename = "unknown";
+        String rawMimeType = "application/octet-stream";
 
-        try {
-            iter = getUploadItemIterator(upload, request);
-            /*
-             * ATM this loop is run only once as we are uploading one file per
-             * request.
+        /*
+         * Read the stream until the actual file starts (empty line). Read
+         * filename and content type from multipart headers.
+         */
+        while (!atStart) {
+            String readLine = readLine(inputStream);
+            contentLength -= (readLine.length() + 2);
+            if (readLine.startsWith("Content-Disposition:")
+                    && readLine.indexOf("filename=") > 0) {
+                rawfilename = readLine.replaceAll(".*filename=", "");
+                String parenthesis = rawfilename.substring(0, 1);
+                rawfilename = rawfilename.substring(1);
+                rawfilename = rawfilename.substring(0,
+                        rawfilename.indexOf(parenthesis));
+            } else if (readLine.equals("")) {
+                atStart = true;
+            } else if (readLine.startsWith("Content-Type")) {
+                rawMimeType = readLine.split(": ")[1];
+            }
+        }
+
+        contentLength -= (boundary.length() + 2); // 2 == CRLF
+
+        final char[] charArray = boundary.toCharArray();
+
+        /*
+         * Reads bytes from the underlaying stream. Compares the read bytes to
+         * the boundary string and returns -1 if met.
+         * 
+         * The maching happens so that if the read byte equals to the first char
+         * of boundary string, the stream goes to "buffering mode". In buffering
+         * mode bytes are read until the character does not match the
+         * corresponding from boundary string or the full boundary string is
+         * found.
+         */
+        InputStream simpleMultiPartReader = new InputStream() {
+            int matchedCount = 0;
+            int curBoundaryIndex = 0;
+            /**
+             * The byte found after a "promising start for boundary"
              */
-            while (iter.hasNext()) {
-                final FileItemStream item = iter.next();
-                final String name = item.getFieldName();
-                // Should report only the filename even if the browser sends the
-                // path
-                final String filename = removePath(item.getName());
-                final String mimeType = item.getContentType();
-                final InputStream stream = item.openStream();
-                if (item.isFormField()) {
-                    // ignored, upload requests contains only files
+            private int bufferedByte = -1;
+            private boolean atTheEnd = false;
+
+            @Override
+            public int read() throws IOException {
+                if (atTheEnd) {
+                    return -1;
+                } else if (bufferedByte >= 0) {
+                    return getBuffered();
                 } else {
-                    final UploadStream upstream = new UploadStream() {
-
-                        public String getContentName() {
-                            return filename;
+                    int fromActualStream = inputStream.read();
+                    if (fromActualStream == -1) {
+                        // unexpected end of stream
+                        throw new IOException(
+                                "The multipart stream ended unexpectedly");
+                    }
+                    if (charArray[matchedCount] == fromActualStream) {
+                        while (true) {
+                            matchedCount++;
+                            if (matchedCount == charArray.length) {
+                                // reached the end of file
+                                atTheEnd = true;
+                                return -1;
+                            }
+                            fromActualStream = inputStream.read();
+                            if (fromActualStream != charArray[matchedCount]) {
+                                // Did not found full boundary, cache the last
+                                // byte
+                                bufferedByte = fromActualStream;
+                                return getBuffered();
+                            }
                         }
+                    }
+                    return fromActualStream;
+                }
+            }
 
-                        public String getContentType() {
-                            return mimeType;
-                        }
+            private int getBuffered() throws IOException {
+                int b;
+                if (matchedCount == 0) {
+                    b = bufferedByte;
+                    bufferedByte = -1;
+                } else {
+                    b = charArray[curBoundaryIndex++];
+                    if (curBoundaryIndex == matchedCount) {
+                        matchedCount = 0;
+                        curBoundaryIndex = 0;
+                        // next call for getBuffered will return the
+                        // bufferedByte, not from the char array.
+                    }
+                }
+                if (b == -1) {
+                    throw new IOException(
+                            "The multipart stream ended unexpectedly");
+                }
+                return b;
+            }
+        };
 
-                        public InputStream getStream() {
-                            return stream;
-                        }
+        /*
+         * Should report only the filename even if the browser sends the path
+         */
+        final String filename = removePath(rawfilename);
+        final String mimeType = rawMimeType;
 
-                        public String getStreamName() {
-                            return "stream";
-                        }
+        try {
+            /*
+             * safe cast as in GWT terminal all variable owners are expected to
+             * be components.
+             */
+            Component component = (Component) owner;
+            if (component.isReadOnly()) {
+                throw new UploadException(
+                        "Warning: file upload ignored because the componente was read-only");
+            }
+            streamToReceiver(simpleMultiPartReader, receiver, owner, filename,
+                    mimeType, contentLength);
+        } catch (Exception e) {
+            synchronized (application) {
+                handleChangeVariablesError(application, (Component) owner, e,
+                        new HashMap<String, Object>());
+            }
+        }
+        sendUploadResponse(request, response);
 
-                    };
+    }
 
-                    if (name.startsWith("XHRFILE")) {
-                        String[] split = item.getFieldName().substring(7)
-                                .split("\\.");
-                        DragAndDropWrapper ddw = (DragAndDropWrapper) idPaintableMap
-                                .get(split[0]);
+    /**
+     * Used to stream plain file post (aka XHR2.post(File))
+     * 
+     * @param request
+     * @param response
+     * @param receiver
+     * @param owner
+     * @param contentLength
+     * @throws IOException
+     */
+    protected void doHandleXhrFilePost(Request request, Response response,
+            Receiver receiver, ReceiverOwner owner, int contentLength)
+            throws IOException {
 
-                        try {
-                            ddw.receiveFile(upstream, split[1]);
-                        } catch (UploadException e) {
-                            synchronized (application) {
-                                handleChangeVariablesError(application, ddw, e,
-                                        new HashMap<String, Object>());
-                            }
-                        }
+        // These are unknown in filexhr ATM, maybe add to Accept header that
+        // is accessible in portlets
+        final String filename = "unknown";
+        final String mimeType = filename;
+        final InputStream stream = request.getInputStream();
+        try {
+            /*
+             * safe cast as in GWT terminal all variable owners are expected to
+             * be components.
+             */
+            Component component = (Component) owner;
+            if (component.isReadOnly()) {
+                throw new UploadException(
+                        "Warning: file upload ignored because the componente was read-only");
+            }
+            streamToReceiver(stream, receiver, owner, filename, mimeType,
+                    contentLength);
+        } catch (Exception e) {
+            synchronized (application) {
+                handleChangeVariablesError(application, (Component) owner, e,
+                        new HashMap<String, Object>());
+            }
+        }
+        sendUploadResponse(request, response);
+    }
 
-                    } else {
+    protected final void streamToReceiver(final InputStream in,
+            Receiver receiver, ReceiverOwner source, String filename,
+            String type, int contentLength) throws UploadException {
+        if (receiver == null) {
+            throw new IllegalStateException("Receiver for the post not found");
+        }
 
-                        int separatorPos = name.lastIndexOf("_");
-                        final String pid = name.substring(0, separatorPos);
-                        final Upload uploadComponent = (Upload) idPaintableMap
-                                .get(pid);
-                        if (uploadComponent == null) {
-                            throw new FileUploadException(
-                                    "Upload component not found");
-                        }
-                        if (uploadComponent.isReadOnly()) {
-                            throw new FileUploadException(
-                                    "Warning: ignored file upload because upload component is set as read-only");
-                        }
-                        synchronized (application) {
-                            // put upload component into receiving state
-                            uploadComponent.startUpload();
-                        }
+        ReceivingController controller = source
+                .getReceivingController(receiver);
 
-                        // tell UploadProgressListener which component is
-                        // receiving
-                        // file
-                        pl.setUpload(uploadComponent);
+        final Application application = getApplication();
 
-                        try {
-                            uploadComponent.receiveUpload(upstream);
-                        } catch (UploadException e) {
-                            // error happened while receiving file. Handle the
-                            // error in the same manner as it would have
-                            // happened in
-                            // variable change.
-                            synchronized (application) {
-                                handleChangeVariablesError(application,
-                                        uploadComponent, e,
-                                        new HashMap<String, Object>());
-                            }
-                        }
+        OutputStream out = null;
+        try {
+            boolean listenProgress;
+            synchronized (application) {
+                ReceivingStartedEvent startedEvent = new ReceivingStartedEventImpl(
+                        receiver, filename, type, contentLength);
+                controller.uploadStarted(startedEvent);
+                out = receiver.receiveUpload(filename, type);
+                listenProgress = controller.listenProgress();
+            }
+
+            // Gets the output target stream
+            if (out == null) {
+                throw new NoOutputStreamException();
+            }
+
+            if (null == in) {
+                // No file, for instance non-existent filename in html upload
+                throw new NoInputStreamException();
+            }
+
+            final byte buffer[] = new byte[MAX_UPLOAD_BUFFER_SIZE];
+            int bytesReadToBuffer = 0;
+            int totalBytes = 0;
+            ReceivingProgressedEventImpl progressEvent = new ReceivingProgressedEventImpl(
+                    receiver, filename, type, contentLength);
+
+            while ((bytesReadToBuffer = in.read(buffer)) > 0) {
+                out.write(buffer, 0, bytesReadToBuffer);
+                totalBytes += bytesReadToBuffer;
+                if (listenProgress) {
+                    // update progress if listener set and contentLength
+                    // received
+                    synchronized (application) {
+                        /*
+                         * Note, we are reusing the same progress event, not to
+                         * pollute VM with lots of objects. This might not help
+                         * though if the end user aggressively updates UI on
+                         * each onProgress.
+                         */
+                        progressEvent.setBytesReceived(totalBytes);
+                        controller.onProgress(progressEvent);
                     }
+                }
+                if (controller.isInterrupted()) {
+                    throw new UploadInterruptedException();
+                }
+            }
+
+            // upload successful
+            out.close();
+            ReceivingEndedEvent event = new ReceivingEndedEventImpl(receiver,
+                    filename, type, totalBytes);
+            synchronized (application) {
+                controller.uploadFinished(event);
+            }
 
+        } catch (UploadInterruptedException e) {
+            // Download interrupted by application code
+            try {
+                // still try to close output stream (e.g. file handle)
+                out.close();
+            } catch (IOException e1) {
+                // NOP
+            }
+            ReceivingFailedEvent event = new ReceivingFailedEventImpl(receiver,
+                    filename, type, contentLength, e);
+            synchronized (application) {
+                controller.uploadFailed(event);
+            }
+            // Note, we are not throwing interrupted exception forward as it is
+            // not a terminal level erro like all other exception.
+        } catch (final Exception e) {
+            synchronized (application) {
+                ReceivingFailedEvent event = new ReceivingFailedEventImpl(
+                        receiver, filename, type, contentLength, e);
+                synchronized (application) {
+                    controller.uploadFailed(event);
                 }
+                // throw exception for terminal to be handled (to be passed to
+                // terminalErrorHandler)
+                throw new UploadException(e);
             }
-        } catch (final FileUploadException e) {
-            throw e;
         }
-
-        sendUploadResponse(request, response);
     }
 
     /**
@@ -722,14 +901,19 @@ public abstract class AbstractCommunicationManager implements
             } else {
                 // remove detached components from paintableIdMap so they
                 // can be GC'ed
+                /*
+                 * TODO figure out if we could move this beyond the painting
+                 * phase, "respond as fast as possible, then do the cleanup".
+                 * Beware of painting the dirty detatched components.
+                 */
                 for (Iterator<Paintable> it = paintableIdMap.keySet()
                         .iterator(); it.hasNext();) {
                     Component p = (Component) it.next();
                     if (p.getApplication() == null) {
+                        unregisterPaintable(p);
                         idPaintableMap.remove(paintableIdMap.get(p));
                         it.remove();
                         dirtyPaintables.remove(p);
-                        p.removeListener(this);
                     }
                 }
                 paintables = getDirtyVisibleComponents(window);
@@ -977,6 +1161,16 @@ public abstract class AbstractCommunicationManager implements
 
     }
 
+    /**
+     * Called when communication manager stops listening for repaints for given
+     * component.
+     * 
+     * @param p
+     */
+    protected void unregisterPaintable(Component p) {
+        p.removeListener(this);
+    }
+
     /**
      * TODO document
      * 
@@ -1148,7 +1342,7 @@ public abstract class AbstractCommunicationManager implements
         return success;
     }
 
-    private VariableOwner getVariableOwner(String string) {
+    protected VariableOwner getVariableOwner(String string) {
         VariableOwner owner = (VariableOwner) idPaintableMap.get(string);
         if (owner == null && string.startsWith("DD")) {
             return getDragAndDropService();
@@ -1807,30 +2001,6 @@ public abstract class AbstractCommunicationManager implements
         }
     }
 
-    /*
-     * Upload progress listener notifies upload component once when Jakarta
-     * FileUpload can determine content length. Used to detect files total size,
-     * uploads progress can be tracked inside upload.
-     */
-    private class UploadProgressListener implements ProgressListener,
-            Serializable {
-
-        Upload uploadComponent;
-
-        boolean updated = false;
-
-        public void setUpload(Upload u) {
-            uploadComponent = u;
-        }
-
-        public void update(long bytesRead, long contentLength, int items) {
-            if (!updated && uploadComponent != null) {
-                uploadComponent.setUploadSize(contentLength);
-                updated = true;
-            }
-        }
-    }
-
     /**
      * Helper method to test if a component contains another
      * 
@@ -1964,4 +2134,7 @@ public abstract class AbstractCommunicationManager implements
         }
 
     }
+
+    abstract String createReceiverUrl(ReceiverOwner owner, String name,
+            Receiver value);
 }
diff --git a/src/com/vaadin/terminal/gwt/server/AbstractReceivingEvent.java b/src/com/vaadin/terminal/gwt/server/AbstractReceivingEvent.java
new file mode 100644 (file)
index 0000000..2e84538
--- /dev/null
@@ -0,0 +1,49 @@
+package com.vaadin.terminal.gwt.server;
+
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner.ReceivingEvent;
+
+/**
+ * Abstract base class for ReceivingEvent implementations.
+ */
+@SuppressWarnings("serial")
+abstract class AbstractReceivingEvent implements ReceivingEvent {
+    private final String type;
+    private final String filename;
+    private Receiver receiver;
+    private long contentLength;
+    private long bytesReceived;
+
+    public String getFileName() {
+        return filename;
+    }
+
+    public String getMimeType() {
+        return type;
+    }
+
+    public AbstractReceivingEvent(Receiver receiver, String filename,
+            String type, long length) {
+        this.receiver = receiver;
+        this.filename = filename;
+        this.type = type;
+        contentLength = length;
+    }
+
+    public Receiver getReceiver() {
+        return receiver;
+    }
+
+    public long getContentLength() {
+        return contentLength;
+    }
+
+    public long getBytesReceived() {
+        return bytesReceived;
+    }
+
+    void setBytesReceived(long bytesReceived) {
+        this.bytesReceived = bytesReceived;
+    }
+
+}
index c48af80305c4e5ea67bf1db4ef9375e5a854c060..b2575592319a6f431f33fbdbe6903082467a0dcd 100644 (file)
@@ -7,6 +7,9 @@ package com.vaadin.terminal.gwt.server;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
@@ -14,12 +17,12 @@ import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
 import com.vaadin.Application;
-import com.vaadin.external.org.apache.commons.fileupload.FileItemIterator;
-import com.vaadin.external.org.apache.commons.fileupload.FileUpload;
-import com.vaadin.external.org.apache.commons.fileupload.FileUploadException;
-import com.vaadin.external.org.apache.commons.fileupload.servlet.ServletFileUpload;
 import com.vaadin.terminal.ApplicationResource;
 import com.vaadin.terminal.DownloadStream;
+import com.vaadin.terminal.Paintable;
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner;
+import com.vaadin.ui.Component;
 import com.vaadin.ui.Window;
 
 /**
@@ -202,34 +205,56 @@ public class CommunicationManager extends AbstractCommunicationManager {
         super(application);
     }
 
-    @Override
-    protected FileUpload createFileUpload() {
-        return new ServletFileUpload();
-    }
-
-    @Override
-    protected FileItemIterator getUploadItemIterator(FileUpload upload,
-            Request request) throws IOException, FileUploadException {
-        return ((ServletFileUpload) upload)
-                .getItemIterator((HttpServletRequest) request
-                        .getWrappedRequest());
-    }
-
     /**
      * Handles file upload request submitted via Upload component.
      * 
-     * TODO document
+     * @see #createReceiverUrl(ReceiverOwner, String, Receiver)
      * 
      * @param request
      * @param response
      * @throws IOException
-     * @throws FileUploadException
+     * @throws InvalidUIDLSecurityKeyException
      */
     public void handleFileUpload(HttpServletRequest request,
             HttpServletResponse response) throws IOException,
-            FileUploadException {
-        doHandleFileUpload(new HttpServletRequestWrapper(request),
-                new HttpServletResponseWrapper(response));
+            InvalidUIDLSecurityKeyException {
+
+        /*
+         * URI pattern: APP/UPPLOAD/[PID]/[NAME]/[SECKEY] See #createReceiverUrl
+         */
+
+        String pathInfo = request.getPathInfo();
+        // strip away part until the data we are interested starts
+        int startOfData = pathInfo
+                .indexOf(AbstractApplicationServlet.UPLOAD_URL_PREFIX)
+                + AbstractApplicationServlet.UPLOAD_URL_PREFIX.length();
+        String uppUri = pathInfo.substring(startOfData);
+        String[] parts = uppUri.split("/", 3); // 0 = pid, 1= name, 2 = sec key
+
+        Receiver receiver = pidToNameToReceiver.get(parts[0]).remove(parts[1]);
+        String secKey = receiverToSeckey.remove(receiver);
+        if (secKey.equals(parts[2])) {
+
+            ReceiverOwner source = (ReceiverOwner) getVariableOwner(parts[0]);
+            String contentType = request.getContentType();
+            if (request.getContentType().contains("boundary")) {
+                // Multipart requests contain boundary string
+                doHandleSimpleMultipartFileUpload(
+                        new HttpServletRequestWrapper(request),
+                        new HttpServletResponseWrapper(response), receiver,
+                        source, contentType.split("boundary=")[1]);
+            } else {
+                // if boundary string does not exist, the posted file is from
+                // XHR2.post(File)
+                doHandleXhrFilePost(new HttpServletRequestWrapper(request),
+                        new HttpServletResponseWrapper(response), receiver,
+                        source, request.getContentLength());
+            }
+        } else {
+            throw new InvalidUIDLSecurityKeyException(
+                    "Security key in upload post did not match!");
+        }
+
     }
 
     /**
@@ -308,4 +333,65 @@ public class CommunicationManager extends AbstractCommunicationManager {
                 new AbstractApplicationServletWrapper(applicationServlet));
     }
 
+    @Override
+    protected void unregisterPaintable(Component p) {
+        /* Cleanup possible receivers */
+        if (pidToNameToReceiver != null && p instanceof ReceiverOwner) {
+            Map<String, Receiver> removed = pidToNameToReceiver
+                    .remove(getPaintableId(p));
+            if (removed != null) {
+                for (String key : removed.keySet()) {
+                    receiverToSeckey.remove(removed.get(key));
+                }
+            }
+        }
+        super.unregisterPaintable(p);
+
+    }
+
+    private Map<String, Map<String, Receiver>> pidToNameToReceiver;
+
+    private Map<Receiver, String> receiverToSeckey;
+
+    @Override
+    String createReceiverUrl(ReceiverOwner owner, String name, Receiver value) {
+
+        /*
+         * We will use the same APP/* URI space as ApplicationResources but
+         * prefix url with UPLOAD
+         * 
+         * eg. APP/UPPLOAD/[PID]/[NAME]/[SECKEY]
+         * 
+         * SECKEY is created on each paint to make URL's unpredictable (to
+         * prevent CSRF attacks).
+         * 
+         * NAME and PID from URI forms a key to fetch Receiver when handling
+         * post
+         */
+        String paintableId = getPaintableId((Paintable) owner);
+        String key = paintableId + "/" + name;
+
+        if (pidToNameToReceiver == null) {
+            pidToNameToReceiver = new HashMap<String, Map<String, Receiver>>();
+        }
+        Map<String, Receiver> nameToReceiver = pidToNameToReceiver
+                .get(paintableId);
+        if (nameToReceiver == null) {
+            nameToReceiver = new HashMap<String, Receiver>();
+            pidToNameToReceiver.put(paintableId, nameToReceiver);
+        }
+        nameToReceiver.put(name, value);
+
+        if (receiverToSeckey == null) {
+            receiverToSeckey = new HashMap<Receiver, String>();
+        }
+        String seckey = UUID.randomUUID().toString();
+        receiverToSeckey.put(value, seckey);
+
+        return getApplication().getURL()
+                + AbstractApplicationServlet.UPLOAD_URL_PREFIX + key + "/"
+                + seckey;
+
+    }
+
 }
index c94fd68473a122e1ad48e0f408c693936f9c5f53..b50fc0a8a186cb578824511b6109430becf7dfa8 100644 (file)
@@ -27,6 +27,8 @@ import com.vaadin.terminal.ExternalResource;
 import com.vaadin.terminal.PaintException;
 import com.vaadin.terminal.PaintTarget;
 import com.vaadin.terminal.Paintable;
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner;
 import com.vaadin.terminal.Resource;
 import com.vaadin.terminal.ThemeResource;
 import com.vaadin.terminal.VariableOwner;
@@ -1102,4 +1104,11 @@ public class JsonPaintTarget implements PaintTarget {
     Collection<Class<? extends Paintable>> getUsedPaintableTypes() {
         return usedPaintableTypes;
     }
+
+    public void addVariable(ReceiverOwner owner, String name, Receiver value)
+            throws PaintException {
+        String url = manager.createReceiverUrl(owner, name, value);
+        addVariable(owner, name, url);
+    }
+
 }
diff --git a/src/com/vaadin/terminal/gwt/server/NoInputStreamException.java b/src/com/vaadin/terminal/gwt/server/NoInputStreamException.java
new file mode 100644 (file)
index 0000000..72b9934
--- /dev/null
@@ -0,0 +1,6 @@
+package com.vaadin.terminal.gwt.server;
+
+@SuppressWarnings("serial")
+public class NoInputStreamException extends Exception {
+
+}
diff --git a/src/com/vaadin/terminal/gwt/server/NoOutputStreamException.java b/src/com/vaadin/terminal/gwt/server/NoOutputStreamException.java
new file mode 100644 (file)
index 0000000..6c33dab
--- /dev/null
@@ -0,0 +1,6 @@
+package com.vaadin.terminal.gwt.server;
+
+@SuppressWarnings("serial")
+public class NoOutputStreamException extends Exception {
+
+}
index ceb26d1fb13ae5daefaec5e2602ac082b4b7eeaa..1b3585cb80d75dbc94fd3c915dd2b0f2d59bcbdc 100644 (file)
@@ -250,6 +250,7 @@ public class PortletApplicationContext2 extends AbstractWebApplicationContext {
             return resourceURL.toString();
         } else {
             // in a background thread or otherwise outside a request
+            // TODO exception ??
             return null;
         }
     }
index 57226588f96f834108b17a12d65808499bfef94e..37beafa17228c017c3428468d32561672d06d4d1 100644 (file)
@@ -7,9 +7,9 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
 
-import javax.portlet.ActionRequest;
-import javax.portlet.ActionResponse;
 import javax.portlet.ClientDataRequest;
 import javax.portlet.MimeResponse;
 import javax.portlet.PortletRequest;
@@ -17,15 +17,16 @@ import javax.portlet.PortletResponse;
 import javax.portlet.PortletSession;
 import javax.portlet.ResourceRequest;
 import javax.portlet.ResourceResponse;
+import javax.portlet.ResourceURL;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequestWrapper;
 
 import com.vaadin.Application;
-import com.vaadin.external.org.apache.commons.fileupload.FileItemIterator;
-import com.vaadin.external.org.apache.commons.fileupload.FileUpload;
-import com.vaadin.external.org.apache.commons.fileupload.FileUploadException;
-import com.vaadin.external.org.apache.commons.fileupload.portlet.PortletFileUpload;
 import com.vaadin.terminal.DownloadStream;
+import com.vaadin.terminal.Paintable;
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner;
+import com.vaadin.ui.Component;
 import com.vaadin.ui.Window;
 
 /**
@@ -37,7 +38,7 @@ import com.vaadin.ui.Window;
 @SuppressWarnings("serial")
 public class PortletCommunicationManager extends AbstractCommunicationManager {
 
-    protected String dummyURL;
+    private ResourceResponse currentUidlResponse;
 
     private static class PortletRequestWrapper implements Request {
 
@@ -191,39 +192,36 @@ public class PortletCommunicationManager extends AbstractCommunicationManager {
         super(application);
     }
 
-    @Override
-    protected FileUpload createFileUpload() {
-        return new PortletFileUpload();
-    }
-
-    @Override
-    protected FileItemIterator getUploadItemIterator(FileUpload upload,
-            Request request) throws IOException, FileUploadException {
-        return ((PortletFileUpload) upload)
-                .getItemIterator((ActionRequest) request.getWrappedRequest());
-    }
+    public void handleFileUpload(ResourceRequest request,
+            ResourceResponse response) throws IOException {
+        String contentType = request.getContentType();
+        String name = request.getParameter("name");
+        String ownerId = request.getParameter("rec-owner");
+        ReceiverOwner variableOwner = (ReceiverOwner) getVariableOwner(ownerId);
+        Receiver receiver = ownerToNameToReceiver.get(variableOwner).remove(
+                name);
+
+        // clean up, may be re added on next paint
+        ownerToNameToReceiver.get(variableOwner).remove(name);
+
+        if (contentType.contains("boundary")) {
+            doHandleSimpleMultipartFileUpload(
+                    new PortletRequestWrapper(request),
+                    new PortletResponseWrapper(response), receiver,
+                    variableOwner, contentType.split("boundary=")[1]);
+        } else {
+            doHandleXhrFilePost(new PortletRequestWrapper(request),
+                    new PortletResponseWrapper(response), receiver,
+                    variableOwner, request.getContentLength());
+        }
 
-    public void handleFileUpload(ActionRequest request, ActionResponse response)
-            throws FileUploadException, IOException {
-        doHandleFileUpload(new PortletRequestWrapper(request),
-                new PortletResponseWrapper(response));
     }
 
     @Override
-    protected void sendUploadResponse(Request request, Response response)
-            throws IOException {
-        if (response.getWrappedResponse() instanceof ActionResponse) {
-            /*
-             * If we do not redirect to some other page, the entire portal page
-             * will be re-printed into the target of the upload request (an
-             * IFRAME), which in turn will cause very strange side effects.
-             */
-            System.out.println("Redirecting to dummyURL: " + dummyURL);
-            ((ActionResponse) response.getWrappedResponse())
-                    .sendRedirect(dummyURL == null ? "http://www.google.com"
-                            : dummyURL);
-        } else {
-            super.sendUploadResponse(request, response);
+    protected void unregisterPaintable(Component p) {
+        super.unregisterPaintable(p);
+        if (ownerToNameToReceiver != null) {
+            ownerToNameToReceiver.remove(p);
         }
     }
 
@@ -231,10 +229,12 @@ public class PortletCommunicationManager extends AbstractCommunicationManager {
             ResourceResponse response,
             AbstractApplicationPortlet applicationPortlet, Window window)
             throws InvalidUIDLSecurityKeyException, IOException {
+        currentUidlResponse = response;
         doHandleUidlRequest(new PortletRequestWrapper(request),
                 new PortletResponseWrapper(response),
                 new AbstractApplicationPortletWrapper(applicationPortlet),
                 window);
+        currentUidlResponse = null;
     }
 
     DownloadStream handleURI(Window window, ResourceRequest request,
@@ -265,9 +265,32 @@ public class PortletCommunicationManager extends AbstractCommunicationManager {
     Window getApplicationWindow(PortletRequest request,
             AbstractApplicationPortlet applicationPortlet,
             Application application, Window assumedWindow) {
+
         return doGetApplicationWindow(new PortletRequestWrapper(request),
                 new AbstractApplicationPortletWrapper(applicationPortlet),
                 application, assumedWindow);
     }
 
+    private Map<ReceiverOwner, Map<String, Receiver>> ownerToNameToReceiver;
+
+    @Override
+    String createReceiverUrl(ReceiverOwner owner, String name, Receiver value) {
+        if (ownerToNameToReceiver == null) {
+            ownerToNameToReceiver = new HashMap<ReceiverOwner, Map<String, Receiver>>();
+        }
+        Map<String, Receiver> nameToReceiver = ownerToNameToReceiver.get(owner);
+        if (nameToReceiver == null) {
+            nameToReceiver = new HashMap<String, Receiver>();
+            ownerToNameToReceiver.put(owner, nameToReceiver);
+        }
+        nameToReceiver.put(name, value);
+        ResourceURL resurl = currentUidlResponse.createResourceURL();
+        resurl.setResourceID("UPLOAD");
+        resurl.setParameter("name", name);
+        resurl.setParameter("rec-owner", getPaintableId((Paintable) owner));
+        resurl.setProperty("name", name);
+        resurl.setProperty("rec-owner", getPaintableId((Paintable) owner));
+        return resurl.toString();
+    }
+
 }
diff --git a/src/com/vaadin/terminal/gwt/server/ReceivingEndedEventImpl.java b/src/com/vaadin/terminal/gwt/server/ReceivingEndedEventImpl.java
new file mode 100644 (file)
index 0000000..e5519ab
--- /dev/null
@@ -0,0 +1,15 @@
+package com.vaadin.terminal.gwt.server;
+
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner.ReceivingEndedEvent;
+
+@SuppressWarnings("serial")
+class ReceivingEndedEventImpl extends AbstractReceivingEvent implements
+        ReceivingEndedEvent {
+
+    public ReceivingEndedEventImpl(Receiver receiver, String filename,
+            String type, long totalBytes) {
+        super(receiver, filename, type, totalBytes);
+    }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/server/ReceivingFailedEventImpl.java b/src/com/vaadin/terminal/gwt/server/ReceivingFailedEventImpl.java
new file mode 100644 (file)
index 0000000..f57675a
--- /dev/null
@@ -0,0 +1,22 @@
+package com.vaadin.terminal.gwt.server;
+
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner.ReceivingFailedEvent;
+
+@SuppressWarnings("serial")
+class ReceivingFailedEventImpl extends AbstractReceivingEvent implements
+        ReceivingFailedEvent {
+
+    private final Exception exception;
+
+    public ReceivingFailedEventImpl(Receiver receiver, final String filename,
+            final String type, long contentLength, final Exception exception) {
+        super(receiver, filename, type, contentLength);
+        this.exception = exception;
+    }
+
+    public Exception getException() {
+        return exception;
+    }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/server/ReceivingProgressedEventImpl.java b/src/com/vaadin/terminal/gwt/server/ReceivingProgressedEventImpl.java
new file mode 100644 (file)
index 0000000..a098cf0
--- /dev/null
@@ -0,0 +1,15 @@
+package com.vaadin.terminal.gwt.server;
+
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner.ReceivingProgressedEvent;
+
+@SuppressWarnings("serial")
+class ReceivingProgressedEventImpl extends AbstractReceivingEvent implements
+        ReceivingProgressedEvent {
+
+    public ReceivingProgressedEventImpl(Receiver receiver,
+            final String filename, final String type, long contentLength) {
+        super(receiver, filename, type, contentLength);
+    }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/server/ReceivingStartedEventImpl.java b/src/com/vaadin/terminal/gwt/server/ReceivingStartedEventImpl.java
new file mode 100644 (file)
index 0000000..119d169
--- /dev/null
@@ -0,0 +1,15 @@
+package com.vaadin.terminal.gwt.server;
+
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner.ReceivingStartedEvent;
+
+@SuppressWarnings("serial")
+class ReceivingStartedEventImpl extends AbstractReceivingEvent implements
+        ReceivingStartedEvent {
+
+    public ReceivingStartedEventImpl(Receiver receiver, final String filename,
+            final String type, long contentLength) {
+        super(receiver, filename, type, contentLength);
+    }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/server/UploadException.java b/src/com/vaadin/terminal/gwt/server/UploadException.java
new file mode 100644 (file)
index 0000000..ef3b255
--- /dev/null
@@ -0,0 +1,12 @@
+package com.vaadin.terminal.gwt.server;
+
+@SuppressWarnings("serial")
+public class UploadException extends Exception {
+    public UploadException(Exception e) {
+        super("Upload failed", e);
+    }
+
+    public UploadException(String msg) {
+        super(msg);
+    }
+}
index 87ff571f36e1c8a80f2e3390e131ae67785cd71c..afcb70506bc9cf255bf02e8e1bf0c4c705d16521 100644 (file)
@@ -3,10 +3,6 @@
  */
 package com.vaadin.ui;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -19,23 +15,34 @@ import com.vaadin.event.dd.TargetDetails;
 import com.vaadin.event.dd.TargetDetailsImpl;
 import com.vaadin.terminal.PaintException;
 import com.vaadin.terminal.PaintTarget;
-import com.vaadin.terminal.UploadStream;
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner;
 import com.vaadin.terminal.gwt.client.MouseEventDetails;
 import com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper;
 import com.vaadin.terminal.gwt.client.ui.dd.HorizontalDropLocation;
 import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation;
-import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
-import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable.Html5File;
-import com.vaadin.ui.Upload.Receiver;
-import com.vaadin.ui.Upload.UploadException;
+import com.vaadin.ui.Html5File.ProxyReceiver;
 
 @SuppressWarnings("serial")
 @ClientWidget(VDragAndDropWrapper.class)
 public class DragAndDropWrapper extends CustomComponent implements DropTarget,
-        DragSource {
+        DragSource, ReceiverOwner {
 
     public class WrapperTransferable extends TransferableImpl {
 
+        /**
+         * @deprecated this class is made top level in recent version. Use
+         *             com.vaadin.ui.Html5File instead
+         */
+        @Deprecated
+        private class Html5File extends com.vaadin.ui.Html5File {
+
+            Html5File(String name, long size, String mimeType) {
+                super(name, size, mimeType);
+            }
+
+        }
+
         private Html5File[] files;
 
         public WrapperTransferable(Component sourceComponent,
@@ -45,13 +52,14 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget,
             if (fc != null) {
                 files = new Html5File[fc];
                 for (int i = 0; i < fc; i++) {
-                    Html5File file = new Html5File();
+                    Html5File file = new Html5File(
+                            (String) rawVariables.get("fn" + i), // name
+                            (Integer) rawVariables.get("fs" + i), // size
+                            (String) rawVariables.get("ft" + i)); // mime
                     String id = (String) rawVariables.get("fi" + i);
-                    file.name = (String) rawVariables.get("fn" + i);
-                    file.size = (Integer) rawVariables.get("fs" + i);
-                    file.type = (String) rawVariables.get("ft" + i);
                     files[i] = file;
                     receivers.put(id, file);
+                    requestRepaint(); // paint Receivers
                 }
             }
         }
@@ -96,50 +104,6 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget,
             return data;
         }
 
-        /**
-         * {@link DragAndDropWrapper} can receive also files from client
-         * computer if appropriate HTML 5 features are supported on client side.
-         * This class wraps information about dragged file on server side.
-         */
-        public class Html5File implements Serializable {
-
-            public String name;
-            private int size;
-            private Receiver receiver;
-            private String type;
-
-            public String getFileName() {
-                return name;
-            }
-
-            public int getFileSize() {
-                return size;
-            }
-
-            public String getType() {
-                return type;
-            }
-
-            /**
-             * Sets the {@link Receiver} that into which the file contents will
-             * be written. Usage of Reveiver is similar to {@link Upload}
-             * component.
-             * 
-             * <p>
-             * <em>Note!</em> receiving file contents is experimental feature
-             * depending on HTML 5 API's. It is supported only by Firefox 3.6 at
-             * this time.
-             * 
-             * @param receiver
-             *            the callback that returns stream where the
-             *            implementation writes the file contents as it arrives.
-             */
-            public void setReceiver(Receiver receiver) {
-                this.receiver = receiver;
-            }
-
-        }
-
     }
 
     private Map<String, Html5File> receivers = new HashMap<String, Html5File>();
@@ -220,9 +184,23 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget,
         if (getDropHandler() != null) {
             getDropHandler().getAcceptCriterion().paint(target);
         }
+        if (receivers != null && receivers.size() > 0) {
+            for (String id : receivers.keySet()) {
+                Html5File html5File = receivers.get(id);
+                if (html5File.getReceiver() != null) {
+                    target.addVariable(this, "rec-" + id,
+                            html5File.getProxyReceiver());
+                } else {
+                    // instructs the client side not to send the file
+                    target.addVariable(this, "rec-" + id, (String) null);
+                }
+            }
+        }
     }
 
     private DropHandler dropHandler;
+    private Html5File currentlyUploadedFile;
+    private boolean listenProgressOfUploadedFile;
 
     public DropHandler getDropHandler() {
         return dropHandler;
@@ -251,38 +229,96 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget,
         return dragStartMode;
     }
 
-    /**
-     * This method should only be used by Vaadin terminal implementation. This
-     * is not end user api.
-     * 
-     * TODO should fire progress events + end/succes events like upload. Not
-     * critical until we have a wider browser support for HTML5 File API
-     * 
-     * @param upstream
-     * @param fileId
-     * @throws UploadException
+    /*
+     * Single controller is enough for atm as files are transferred in serial.
+     * If parallel transfer is needed, this logic needs to go to Html5File
      */
-    public void receiveFile(UploadStream upstream, String fileId)
-            throws UploadException {
-        Html5File file = receivers.get(fileId);
-        if (file != null && file.receiver != null) {
-            OutputStream receiveUpload = file.receiver.receiveUpload(
-                    file.getFileName(), "TODO");
-
-            InputStream stream = upstream.getStream();
-            byte[] buf = new byte[AbstractApplicationServlet.MAX_BUFFER_SIZE];
-            int bytesRead;
-            try {
-                while ((bytesRead = stream.read(buf)) != -1) {
-                    receiveUpload.write(buf, 0, bytesRead);
+    private ReceivingController controller = new ReceivingController() {
+        /*
+         * With XHR2 file posts we can't provide as much information from the
+         * terminal as with multipart request. This helper class wraps the
+         * terminal event and provides the lacking information from the
+         * Html5File.
+         */
+        class ReceivingEventWrapper implements ReceivingFailedEvent,
+                ReceivingEndedEvent, ReceivingStartedEvent,
+                ReceivingProgressedEvent {
+            private ReceivingEvent wrappedEvent;
+
+            ReceivingEventWrapper(ReceivingEvent e) {
+                wrappedEvent = e;
+            }
+
+            public String getMimeType() {
+                return currentlyUploadedFile.getType();
+            }
+
+            public String getFileName() {
+                return currentlyUploadedFile.getFileName();
+            }
+
+            public long getContentLength() {
+                return currentlyUploadedFile.getFileSize();
+            }
+
+            public Receiver getReceiver() {
+                return currentlyUploadedFile.getReceiver();
+            }
+
+            public Exception getException() {
+                if (wrappedEvent instanceof ReceivingFailedEvent) {
+                    return ((ReceivingFailedEvent) wrappedEvent).getException();
                 }
-                receiveUpload.close();
-            } catch (IOException e) {
-                throw new UploadException(e);
+                return null;
+            }
+
+            public long getBytesReceived() {
+                return wrappedEvent.getBytesReceived();
+            }
+        }
+
+        public boolean listenProgress() {
+            return listenProgressOfUploadedFile;
+        }
+
+        public void onProgress(ReceivingProgressedEvent event) {
+            currentlyUploadedFile.getUploadListener().onProgress(
+                    new ReceivingEventWrapper(event));
+        }
+
+        public void uploadStarted(ReceivingStartedEvent event) {
+            currentlyUploadedFile = ((ProxyReceiver) event.getReceiver())
+                    .getFile();
+            listenProgressOfUploadedFile = currentlyUploadedFile
+                    .getUploadListener() != null;
+            if (listenProgressOfUploadedFile) {
+                currentlyUploadedFile.getUploadListener().uploadStarted(
+                        new ReceivingEventWrapper(event));
+            }
+        }
+
+        public void uploadFinished(ReceivingEndedEvent event) {
+            if (listenProgressOfUploadedFile) {
+                currentlyUploadedFile.getUploadListener().uploadFinished(
+                        new ReceivingEventWrapper(event));
             }
-            // clean up the reference when file is downloaded
-            receivers.remove(fileId);
         }
 
+        public void uploadFailed(final ReceivingFailedEvent event) {
+            if (listenProgressOfUploadedFile) {
+                currentlyUploadedFile.getUploadListener().uploadFailed(
+                        new ReceivingEventWrapper(event));
+            }
+        }
+
+        public boolean isInterrupted() {
+            return currentlyUploadedFile.isInterrupted();
+        }
+
+    };
+
+    public ReceivingController getReceivingController(Receiver receiver) {
+        return controller;
     }
+
 }
diff --git a/src/com/vaadin/ui/Html5File.java b/src/com/vaadin/ui/Html5File.java
new file mode 100644 (file)
index 0000000..988bf53
--- /dev/null
@@ -0,0 +1,137 @@
+package com.vaadin.ui;
+
+import java.io.OutputStream;
+import java.io.Serializable;
+
+import com.vaadin.event.dd.DropHandler;
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner.ReceivingEndedEvent;
+import com.vaadin.terminal.ReceiverOwner.ReceivingFailedEvent;
+import com.vaadin.terminal.ReceiverOwner.ReceivingProgressedEvent;
+import com.vaadin.terminal.ReceiverOwner.ReceivingStartedEvent;
+
+/**
+ * {@link DragAndDropWrapper} can receive also files from client computer if
+ * appropriate HTML 5 features are supported on client side. This class wraps
+ * information about dragged file on server side.
+ */
+public class Html5File implements Serializable {
+
+    final class ProxyReceiver implements Receiver {
+        public OutputStream receiveUpload(String filename, String MIMEType) {
+            if (receiver == null) {
+                return null;
+            }
+            return receiver.receiveUpload(filename, MIMEType);
+        }
+
+        Html5File getFile() {
+            return Html5File.this;
+        }
+    }
+
+    private String name;
+    private long size;
+    private Receiver receiver;
+    private String type;
+
+    Html5File(String name, long size, String mimeType) {
+        this.name = name;
+        this.size = size;
+        type = mimeType;
+    }
+
+    /**
+     * The receiver that is registered to the terminal. Wraps the actual
+     * Receiver set later by Html5File user.
+     */
+    private ProxyReceiver proxyReceiver = new ProxyReceiver();
+    private boolean interrupted = false;
+    private Html5FileUploadListener listener;;
+
+    public String getFileName() {
+        return name;
+    }
+
+    public long getFileSize() {
+        return size;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * Sets the {@link Receiver} that into which the file contents will be
+     * written. Usage of Reveiver is similar to {@link Upload} component.
+     * <p>
+     * If the {@link Receiver} is not set in the {@link DropHandler} the file
+     * contents will not be sent to server.
+     * <p>
+     * <em>Note!</em> receiving file contents is experimental feature depending
+     * on HTML 5 API's. It is supported only by modern web brosers like Firefox
+     * 3.6 and above and recent webkit based browsers (Safari 5, Chrome 6) at
+     * this time.
+     * 
+     * @param receiver
+     *            the callback that returns stream where the implementation
+     *            writes the file contents as it arrives.
+     */
+    public void setReceiver(Receiver receiver) {
+        this.receiver = receiver;
+    }
+
+    public Receiver getReceiver() {
+        return receiver;
+    }
+
+    ProxyReceiver getProxyReceiver() {
+        return proxyReceiver;
+    }
+
+    /**
+     * Gets the {@link Html5FileUploadListener} that is used to track the progress of
+     * streaming the file contents to given {@link Receiver}.
+     * 
+     * @return
+     */
+    public Html5FileUploadListener getUploadListener() {
+        return listener;
+    }
+
+    /**
+     * Sets the {@link Html5FileUploadListener} that can be used to track the progress of
+     * streaming the file contents to given {@link Receiver}.
+     * 
+     * @param listener
+     * @see #setReceiver(Receiver)
+     */
+    public void setUploadListener(Html5FileUploadListener listener) {
+        this.listener = listener;
+    }
+
+    public boolean isInterrupted() {
+        return interrupted;
+    }
+
+    /**
+     * Interrupts uploading this file.
+     * 
+     * @param interrupted
+     */
+    public void setInterrupted(boolean interrupted) {
+        this.interrupted = interrupted;
+    }
+
+    public interface Html5FileUploadListener {
+
+        void onProgress(ReceivingProgressedEvent event);
+
+        void uploadStarted(ReceivingStartedEvent event);
+
+        void uploadFinished(ReceivingEndedEvent event);
+
+        void uploadFailed(ReceivingFailedEvent event);
+    }
+
+}
\ No newline at end of file
index b281d90b095901aa5b66eeed1b4da7c3494e0621..1acc91031e533dd50262a8e571023caad111ef58 100644 (file)
@@ -4,20 +4,18 @@
 
 package com.vaadin.ui;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.io.Serializable;
 import java.lang.reflect.Method;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.Map;
 
-import com.vaadin.Application;
 import com.vaadin.terminal.PaintException;
 import com.vaadin.terminal.PaintTarget;
-import com.vaadin.terminal.UploadStream;
+import com.vaadin.terminal.ReceiverOwner;
 import com.vaadin.terminal.gwt.client.ui.VUpload;
+import com.vaadin.terminal.gwt.server.NoInputStreamException;
+import com.vaadin.terminal.gwt.server.NoOutputStreamException;
 import com.vaadin.ui.ClientWidget.LoadStyle;
 
 /**
@@ -62,12 +60,8 @@ import com.vaadin.ui.ClientWidget.LoadStyle;
  */
 @SuppressWarnings("serial")
 @ClientWidget(value = VUpload.class, loadStyle = LoadStyle.LAZY)
-public class Upload extends AbstractComponent implements Component.Focusable {
-
-    /**
-     * Upload buffer size.
-     */
-    private static final int BUFFER_SIZE = 64 * 1024; // 64k
+public class Upload extends AbstractComponent implements Component.Focusable,
+        ReceiverOwner {
 
     /**
      * Should the field be focused on next repaint?
@@ -130,105 +124,6 @@ public class Upload extends AbstractComponent implements Component.Focusable {
         receiver = uploadReceiver;
     }
 
-    /**
-     * This method is called by terminal when upload is received.
-     * 
-     * Note, this method is called outside synchronized (Application) block, so
-     * overriding this may be dangerous.
-     * 
-     * @param upload
-     */
-    public void receiveUpload(UploadStream upload) throws UploadException {
-        if (receiver == null) {
-            throw new IllegalStateException(
-                    "Receiver not set for the Upload component");
-        }
-
-        if (!isUploading) {
-            throw new IllegalStateException("uploading not started");
-        }
-
-        // Gets file properties
-        final String filename = upload.getContentName();
-        final String type = upload.getContentType();
-
-        final Application application = getApplication();
-
-        synchronized (application) {
-            fireStarted(filename, type);
-        }
-
-        // Gets the output target stream
-        final OutputStream out = receiver.receiveUpload(filename, type);
-        if (out == null) {
-            synchronized (application) {
-                fireNoOutputStream(filename, type, 0);
-                endUpload();
-            }
-            return;
-        }
-
-        final InputStream in = upload.getStream();
-
-        if (null == in) {
-            // No file, for instance non-existent filename in html upload
-            synchronized (application) {
-                fireNoInputStream(filename, type, 0);
-                endUpload();
-            }
-            return;
-        }
-
-        final byte buffer[] = new byte[BUFFER_SIZE];
-        int bytesRead = 0;
-        totalBytes = 0;
-        try {
-            while ((bytesRead = in.read(buffer)) > 0) {
-                out.write(buffer, 0, bytesRead);
-                totalBytes += bytesRead;
-                if (contentLength > 0
-                        && (progressListeners != null || progressListener != null)) {
-                    // update progress if listener set and contentLength
-                    // received
-                    synchronized (application) {
-                        fireUpdateProgress(totalBytes, contentLength);
-                    }
-                }
-                if (interrupted) {
-                    throw new UploadInterruptedException();
-                }
-            }
-
-            // upload successful
-            out.close();
-            synchronized (application) {
-                fireUploadSuccess(filename, type, totalBytes);
-                endUpload();
-                requestRepaint();
-            }
-
-        } catch (final Exception e) {
-            synchronized (application) {
-                if (e instanceof UploadInterruptedException) {
-                    // Download interrupted
-                    try {
-                        // still try to close output stream
-                        out.close();
-                    } catch (IOException e1) {
-                        // NOP
-                    }
-                }
-                fireUploadInterrupted(filename, type, totalBytes, e);
-                endUpload();
-                interrupted = false;
-                if (!(e instanceof UploadInterruptedException)) {
-                    // throw exception for terminal to be handled
-                    throw new UploadException(e);
-                }
-            }
-        }
-    }
-
     /**
      * Invoked when the value of a variable has changed.
      * 
@@ -278,6 +173,9 @@ public class Upload extends AbstractComponent implements Component.Focusable {
 
         target.addAttribute("nextid", nextid);
 
+        // Post file to this receiver
+        target.addVariable(this, "action", receiver);
+
     }
 
     /**
@@ -288,20 +186,11 @@ public class Upload extends AbstractComponent implements Component.Focusable {
      * @version
      * @VERSION@
      * @since 3.0
+     * @deprecated use {@link com.vaadin.terminal.Receiver} instead. A "copy"
+     *             here is kept for backwards compatibility.
      */
-    public interface Receiver extends Serializable {
-
-        /**
-         * Invoked when a new upload arrives.
-         * 
-         * @param filename
-         *            the desired filename of the upload, usually as specified
-         *            by the client.
-         * @param MIMEType
-         *            the MIME type of the uploaded file.
-         * @return Stream to which the uploaded file should be written.
-         */
-        public OutputStream receiveUpload(String filename, String MIMEType);
+    @Deprecated
+    public interface Receiver extends com.vaadin.terminal.Receiver {
     }
 
     /* Upload events */
@@ -332,19 +221,6 @@ public class Upload extends AbstractComponent implements Component.Focusable {
         }
     }
 
-    private class UploadInterruptedException extends Exception {
-        public UploadInterruptedException() {
-            super("Upload interrupted by other thread");
-        }
-
-    }
-
-    public static class UploadException extends Exception {
-        public UploadException(Exception e) {
-            super("Upload failed", e);
-        }
-    }
-
     /**
      * Upload.Received event is sent when the upload receives a file, regardless
      * of whether the reception was successful or failed. If you wish to
@@ -356,7 +232,7 @@ public class Upload extends AbstractComponent implements Component.Focusable {
      * @VERSION@
      * @since 3.0
      */
-    public class FinishedEvent extends Component.Event {
+    public static class FinishedEvent extends Component.Event {
 
         /**
          * Length of the received file.
@@ -439,7 +315,7 @@ public class Upload extends AbstractComponent implements Component.Focusable {
      * @VERSION@
      * @since 3.0
      */
-    public class FailedEvent extends FinishedEvent {
+    public static class FailedEvent extends FinishedEvent {
 
         private Exception reason = null;
 
@@ -484,7 +360,7 @@ public class Upload extends AbstractComponent implements Component.Focusable {
     /**
      * FailedEvent that indicates that an output stream could not be obtained.
      */
-    public class NoOutputStreamEvent extends FailedEvent {
+    public static class NoOutputStreamEvent extends FailedEvent {
 
         /**
          * 
@@ -502,7 +378,7 @@ public class Upload extends AbstractComponent implements Component.Focusable {
     /**
      * FailedEvent that indicates that an input stream could not be obtained.
      */
-    public class NoInputStreamEvent extends FailedEvent {
+    public static class NoInputStreamEvent extends FailedEvent {
 
         /**
          * 
@@ -526,7 +402,7 @@ public class Upload extends AbstractComponent implements Component.Focusable {
      * @VERSION@
      * @since 3.0
      */
-    public class SucceededEvent extends FinishedEvent {
+    public static class SucceededEvent extends FinishedEvent {
 
         /**
          * 
@@ -550,10 +426,14 @@ public class Upload extends AbstractComponent implements Component.Focusable {
      * @VERSION@
      * @since 5.0
      */
-    public class StartedEvent extends Component.Event {
+    public static class StartedEvent extends Component.Event {
 
         private final String filename;
         private final String type;
+        /**
+         * Length of the received file.
+         */
+        private final long length;
 
         /**
          * 
@@ -562,10 +442,12 @@ public class Upload extends AbstractComponent implements Component.Focusable {
          * @param MIMEType
          * @param length
          */
-        public StartedEvent(Upload source, String filename, String MIMEType) {
+        public StartedEvent(Upload source, String filename, String MIMEType,
+                long contentLength) {
             super(source);
             this.filename = filename;
             type = MIMEType;
+            length = contentLength;
         }
 
         /**
@@ -595,6 +477,13 @@ public class Upload extends AbstractComponent implements Component.Focusable {
             return type;
         }
 
+        /**
+         * @return the length of the file that is being uploaded
+         */
+        public long getContentLength() {
+            return length;
+        }
+
     }
 
     /**
@@ -786,19 +675,8 @@ public class Upload extends AbstractComponent implements Component.Focusable {
      * @param length
      */
     protected void fireStarted(String filename, String MIMEType) {
-        fireEvent(new Upload.StartedEvent(this, filename, MIMEType));
-    }
-
-    /**
-     * Emit upload finished event.
-     * 
-     * @param filename
-     * @param MIMEType
-     * @param length
-     */
-    protected void fireUploadReceived(String filename, String MIMEType,
-            long length) {
-        fireEvent(new Upload.FinishedEvent(this, filename, MIMEType, length));
+        fireEvent(new Upload.StartedEvent(this, filename, MIMEType,
+                contentLength));
     }
 
     /**
@@ -913,15 +791,6 @@ public class Upload extends AbstractComponent implements Component.Focusable {
         this.tabIndex = tabIndex;
     }
 
-    /**
-     * Sets the size of the file currently being uploaded.
-     * 
-     * @param contentLength
-     */
-    public void setUploadSize(long contentLength) {
-        this.contentLength = contentLength;
-    }
-
     /**
      * Go into upload state. This is to prevent double uploading on same
      * component.
@@ -959,6 +828,8 @@ public class Upload extends AbstractComponent implements Component.Focusable {
     private void endUpload() {
         isUploading = false;
         contentLength = -1;
+        interrupted = false;
+        requestRepaint();
     }
 
     public boolean isUploading() {
@@ -1046,4 +917,54 @@ public class Upload extends AbstractComponent implements Component.Focusable {
         this.buttonCaption = buttonCaption;
     }
 
+    /*
+     * Handle to terminal via Upload monitors and controls the upload during it
+     * is being streamed.
+     */
+    private final ReceivingController controller = new ReceivingController() {
+        public boolean listenProgress() {
+            return (progressListeners != null || progressListener != null);
+        }
+
+        public void onProgress(ReceivingProgressedEvent event) {
+            fireUpdateProgress(event.getBytesReceived(),
+                    event.getContentLength());
+        }
+
+        public void uploadStarted(ReceivingStartedEvent event) {
+            startUpload();
+            contentLength = event.getContentLength();
+            fireStarted(event.getFileName(), event.getMimeType());
+        }
+
+        public void uploadFinished(ReceivingEndedEvent event) {
+            fireUploadSuccess(event.getFileName(), event.getMimeType(),
+                    event.getContentLength());
+            endUpload();
+            requestRepaint();
+        }
+
+        public void uploadFailed(ReceivingFailedEvent event) {
+            Exception exception = event.getException();
+            if (exception instanceof NoInputStreamException) {
+                fireNoInputStream(event.getFileName(), event.getMimeType(), 0);
+            } else if (exception instanceof NoOutputStreamException) {
+                fireNoOutputStream(event.getFileName(), event.getMimeType(), 0);
+            } else {
+                fireUploadInterrupted(event.getFileName(), event.getMimeType(),
+                        0, exception);
+            }
+            endUpload();
+        }
+
+        public boolean isInterrupted() {
+            return interrupted;
+        }
+    };
+
+    public final ReceivingController getReceivingController(
+            com.vaadin.terminal.Receiver receiver) {
+        return controller;
+    }
+
 }
index 8c86b935133a59f71fa37a2a4593fe1d2dd350d8..38c48ae44c6756470e3a833ba393d52afc8d99a5 100644 (file)
@@ -38,8 +38,8 @@ import com.vaadin.ui.AbsoluteLayout.ComponentPosition;
 import com.vaadin.ui.Component;
 import com.vaadin.ui.CssLayout;
 import com.vaadin.ui.DragAndDropWrapper;
-import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable.Html5File;
 import com.vaadin.ui.Embedded;
+import com.vaadin.ui.Html5File;
 import com.vaadin.ui.Label;
 import com.vaadin.ui.SplitPanel;
 import com.vaadin.ui.Table;
diff --git a/tests/src/com/vaadin/tests/dd/DragAndDropFiles.java b/tests/src/com/vaadin/tests/dd/DragAndDropFiles.java
new file mode 100644 (file)
index 0000000..a22be5c
--- /dev/null
@@ -0,0 +1,129 @@
+package com.vaadin.tests.dd;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.io.output.NullOutputStream;
+
+import com.vaadin.event.dd.DragAndDropEvent;
+import com.vaadin.event.dd.DropHandler;
+import com.vaadin.event.dd.acceptcriteria.AcceptAll;
+import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
+import com.vaadin.terminal.Receiver;
+import com.vaadin.terminal.ReceiverOwner.ReceivingEndedEvent;
+import com.vaadin.terminal.ReceiverOwner.ReceivingFailedEvent;
+import com.vaadin.terminal.ReceiverOwner.ReceivingProgressedEvent;
+import com.vaadin.terminal.ReceiverOwner.ReceivingStartedEvent;
+import com.vaadin.tests.components.TestBase;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.CssLayout;
+import com.vaadin.ui.DragAndDropWrapper;
+import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable;
+import com.vaadin.ui.Html5File;
+import com.vaadin.ui.Html5File.Html5FileUploadListener;
+import com.vaadin.ui.Label;
+
+public class DragAndDropFiles extends TestBase {
+
+    @Override
+    protected void setup() {
+        CssLayout cssLayout = new CssLayout() {
+            @Override
+            protected String getCss(Component c) {
+                return "display: block; padding:20px; border: 2px dotted black; background: #aaa;";
+            }
+        };
+        Component l = new Label("Drag file on me");
+        l.setSizeUndefined();
+        cssLayout.addComponent(l);
+        DragAndDropWrapper dragAndDropWrapper = new DragAndDropWrapper(
+                cssLayout);
+        dragAndDropWrapper.setSizeUndefined();
+        dragAndDropWrapper.setDropHandler(new DropHandler() {
+
+            public AcceptCriterion getAcceptCriterion() {
+                return AcceptAll.get();
+            }
+
+            public void drop(DragAndDropEvent event) {
+                WrapperTransferable transferable = (WrapperTransferable) event
+                        .getTransferable();
+                Html5File[] files = transferable.getFiles();
+                if (files != null) {
+
+                    for (int i = 0; i < files.length; i++) {
+                        Html5File file = files[i];
+                        // Max 1 MB files are uploaded
+                        if (file.getFileSize() > 1024 * 1024) {
+                            getMainWindow()
+                                    .showNotification(
+                                            "File "
+                                                    + file.getFileName()
+                                                    + " was too large, not transferred to the server side.");
+                            continue;
+                        }
+
+                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+
+                        Receiver receiver = new Receiver() {
+                            public OutputStream receiveUpload(String filename,
+                                    String MIMEType) {
+                                System.err.println("receiveUpload " + filename);
+
+                                return new NullOutputStream();
+                            }
+                        };
+                        file.setReceiver(receiver);
+                        Html5FileUploadListener listener = new Html5FileUploadListener() {
+
+                            public void uploadStarted(
+                                    ReceivingStartedEvent event) {
+                                getMainWindow().showNotification(
+                                        "Started uploading "
+                                                + event.getFileName());
+
+                            }
+
+                            public void uploadFinished(ReceivingEndedEvent event) {
+                                getMainWindow().showNotification(
+                                        "Finished uploading "
+                                                + event.getFileName());
+                            }
+
+                            public void uploadFailed(ReceivingFailedEvent event) {
+                                getMainWindow().showNotification(
+                                        "Failed uploading "
+                                                + event.getFileName());
+
+                            }
+
+                            public void onProgress(
+                                    ReceivingProgressedEvent event) {
+                                System.err.println("Progress"
+                                        + event.getBytesReceived());
+                            }
+                        };
+                        file.setUploadListener(listener);
+                    }
+                }
+
+            }
+        });
+
+        addComponent(dragAndDropWrapper);
+    }
+
+    /*
+     * TODO implement 'handbrake' for testing, progresss listener, interrupting.
+     */
+    @Override
+    protected String getDescription() {
+        return "Should work. Over 1 MB files will not be posted. TODO implement 'handbrake' for testing, progresss listener, interrupting.";
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return null;
+    }
+
+}
index a3552b05b581a75ad5cf1a4396340e6ab1522303..1bfc66aa7e36c3ab629ca0e89ad8e42e0da68d81 100644 (file)
@@ -15,7 +15,7 @@ import com.vaadin.ui.AbsoluteLayout;
 import com.vaadin.ui.AbsoluteLayout.ComponentPosition;
 import com.vaadin.ui.Component;
 import com.vaadin.ui.DragAndDropWrapper;
-import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable.Html5File;
+import com.vaadin.ui.Html5File;
 import com.vaadin.ui.Label;
 import com.vaadin.ui.Upload.Receiver;