summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatti Tahvonen <matti.tahvonen@itmill.com>2010-10-08 11:21:50 +0000
committerMatti Tahvonen <matti.tahvonen@itmill.com>2010-10-08 11:21:50 +0000
commit011608a0a39784c3834aa1a4c42f6b7280f3240a (patch)
treeec592e82dfac39f98c67a28211cfb9adf3d0cbed
parent4f1b424e2edd2f0c0c2b0f0aeb27d36e0c6155f3 (diff)
downloadvaadin-framework-011608a0a39784c3834aa1a4c42f6b7280f3240a.tar.gz
vaadin-framework-011608a0a39784c3834aa1a4c42f6b7280f3240a.zip
refactored handling of Receiver's lots of other upload related improvements. fixes #5743 (removed dependency to commons fileupload), #5742 (drag and drop file uploads in webkits), #5741 (Receiver is upload specific), #4271 (uploads in GateIn). Also improves API for receiving files with drag and drop (rejection, canceling, tracking of the actual streaming) and transfers files in serial in DDW.
svn changeset:15461/svn branch:6.5
-rw-r--r--build/build.xml12
-rw-r--r--src/com/vaadin/terminal/PaintTarget.java16
-rw-r--r--src/com/vaadin/terminal/Receiver.java29
-rw-r--r--src/com/vaadin/terminal/ReceiverOwner.java161
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java258
-rw-r--r--src/com/vaadin/terminal/gwt/client/ui/VUpload.java2
-rw-r--r--src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java33
-rw-r--r--src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java15
-rw-r--r--src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java461
-rw-r--r--src/com/vaadin/terminal/gwt/server/AbstractReceivingEvent.java49
-rw-r--r--src/com/vaadin/terminal/gwt/server/CommunicationManager.java130
-rw-r--r--src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java9
-rw-r--r--src/com/vaadin/terminal/gwt/server/NoInputStreamException.java6
-rw-r--r--src/com/vaadin/terminal/gwt/server/NoOutputStreamException.java6
-rw-r--r--src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java1
-rw-r--r--src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java95
-rw-r--r--src/com/vaadin/terminal/gwt/server/ReceivingEndedEventImpl.java15
-rw-r--r--src/com/vaadin/terminal/gwt/server/ReceivingFailedEventImpl.java22
-rw-r--r--src/com/vaadin/terminal/gwt/server/ReceivingProgressedEventImpl.java15
-rw-r--r--src/com/vaadin/terminal/gwt/server/ReceivingStartedEventImpl.java15
-rw-r--r--src/com/vaadin/terminal/gwt/server/UploadException.java12
-rw-r--r--src/com/vaadin/ui/DragAndDropWrapper.java208
-rw-r--r--src/com/vaadin/ui/Html5File.java137
-rw-r--r--src/com/vaadin/ui/Upload.java251
-rw-r--r--tests/src/com/vaadin/tests/dd/DDTest6.java2
-rw-r--r--tests/src/com/vaadin/tests/dd/DragAndDropFiles.java129
-rw-r--r--tests/src/com/vaadin/tests/dd/DragDropPane.java2
27 files changed, 1502 insertions, 589 deletions
diff --git a/build/build.xml b/build/build.xml
index ff98813857..e7cad185aa 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -180,7 +180,6 @@
<!-- 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"/>
@@ -574,12 +573,7 @@
</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 -->
@@ -798,10 +792,6 @@
<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>
diff --git a/src/com/vaadin/terminal/PaintTarget.java b/src/com/vaadin/terminal/PaintTarget.java
index 7bfc3555fc..52ac490863 100644
--- a/src/com/vaadin/terminal/PaintTarget.java
+++ b/src/com/vaadin/terminal/PaintTarget.java
@@ -152,6 +152,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
index 0000000000..6958cfdf0b
--- /dev/null
+++ b/src/com/vaadin/terminal/Receiver.java
@@ -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
index 0000000000..28ec83b067
--- /dev/null
+++ b/src/com/vaadin/terminal/ReceiverOwner.java
@@ -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();
+ }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java b/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java
index ffbe25e9a7..702b324129 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java
@@ -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() {
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VUpload.java b/src/com/vaadin/terminal/gwt/client/ui/VUpload.java
index 4cb7464a80..8b52434295 100644
--- a/src/com/vaadin/terminal/gwt/client/ui/VUpload.java
+++ b/src/com/vaadin/terminal/gwt/client/ui/VUpload.java
@@ -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");
diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
index 8d5d328402..f6a88d9100 100644
--- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
+++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
@@ -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() + "'");
diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
index f461fce404..aedfb2a204 100644
--- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
+++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
@@ -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) {
diff --git a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
index 0773dca4ec..b9d6bf99fa 100644
--- a/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
+++ b/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
@@ -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);
@@ -978,6 +1162,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
*
* If this method returns false, something was submitted that we did not
@@ -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
index 0000000000..2e84538e92
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/server/AbstractReceivingEvent.java
@@ -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;
+ }
+
+}
diff --git a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java
index c48af80305..b257559231 100644
--- a/src/com/vaadin/terminal/gwt/server/CommunicationManager.java
+++ b/src/com/vaadin/terminal/gwt/server/CommunicationManager.java
@@ -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;
+
+ }
+
}
diff --git a/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java
index c94fd68473..b50fc0a8a1 100644
--- a/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java
+++ b/src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java
@@ -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
index 0000000000..72b99342cf
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/server/NoInputStreamException.java
@@ -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
index 0000000000..6c33dab33e
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/server/NoOutputStreamException.java
@@ -0,0 +1,6 @@
+package com.vaadin.terminal.gwt.server;
+
+@SuppressWarnings("serial")
+public class NoOutputStreamException extends Exception {
+
+}
diff --git a/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java b/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java
index ceb26d1fb1..1b3585cb80 100644
--- a/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java
+++ b/src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java
@@ -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;
}
}
diff --git a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java
index 57226588f9..37beafa172 100644
--- a/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java
+++ b/src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java
@@ -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
index 0000000000..e5519abf56
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/server/ReceivingEndedEventImpl.java
@@ -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
index 0000000000..f57675af65
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/server/ReceivingFailedEventImpl.java
@@ -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
index 0000000000..a098cf0be9
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/server/ReceivingProgressedEventImpl.java
@@ -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
index 0000000000..119d169bec
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/server/ReceivingStartedEventImpl.java
@@ -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
index 0000000000..ef3b2554d2
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/server/UploadException.java
@@ -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);
+ }
+}
diff --git a/src/com/vaadin/ui/DragAndDropWrapper.java b/src/com/vaadin/ui/DragAndDropWrapper.java
index 87ff571f36..afcb70506b 100644
--- a/src/com/vaadin/ui/DragAndDropWrapper.java
+++ b/src/com/vaadin/ui/DragAndDropWrapper.java
@@ -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
index 0000000000..988bf534b9
--- /dev/null
+++ b/src/com/vaadin/ui/Html5File.java
@@ -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
diff --git a/src/com/vaadin/ui/Upload.java b/src/com/vaadin/ui/Upload.java
index b281d90b09..1acc91031e 100644
--- a/src/com/vaadin/ui/Upload.java
+++ b/src/com/vaadin/ui/Upload.java
@@ -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?
@@ -131,105 +125,6 @@ public class Upload extends AbstractComponent implements Component.Focusable {
}
/**
- * 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.
*
* @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
@@ -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));
}
/**
@@ -914,15 +792,6 @@ public class Upload extends AbstractComponent implements Component.Focusable {
}
/**
- * 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;
+ }
+
}
diff --git a/tests/src/com/vaadin/tests/dd/DDTest6.java b/tests/src/com/vaadin/tests/dd/DDTest6.java
index 8c86b93513..38c48ae44c 100644
--- a/tests/src/com/vaadin/tests/dd/DDTest6.java
+++ b/tests/src/com/vaadin/tests/dd/DDTest6.java
@@ -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
index 0000000000..a22be5c440
--- /dev/null
+++ b/tests/src/com/vaadin/tests/dd/DragAndDropFiles.java
@@ -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;
+ }
+
+}
diff --git a/tests/src/com/vaadin/tests/dd/DragDropPane.java b/tests/src/com/vaadin/tests/dd/DragDropPane.java
index a3552b05b5..1bfc66aa7e 100644
--- a/tests/src/com/vaadin/tests/dd/DragDropPane.java
+++ b/tests/src/com/vaadin/tests/dd/DragDropPane.java
@@ -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;