Browse Source

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
tags/6.7.0.beta1
Matti Tahvonen 13 years ago
parent
commit
011608a0a3
27 changed files with 1502 additions and 589 deletions
  1. 1
    11
      build/build.xml
  2. 16
    0
      src/com/vaadin/terminal/PaintTarget.java
  3. 29
    0
      src/com/vaadin/terminal/Receiver.java
  4. 161
    0
      src/com/vaadin/terminal/ReceiverOwner.java
  5. 162
    96
      src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java
  6. 1
    1
      src/com/vaadin/terminal/gwt/client/ui/VUpload.java
  7. 9
    24
      src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
  8. 13
    2
      src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
  9. 317
    144
      src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
  10. 49
    0
      src/com/vaadin/terminal/gwt/server/AbstractReceivingEvent.java
  11. 108
    22
      src/com/vaadin/terminal/gwt/server/CommunicationManager.java
  12. 9
    0
      src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java
  13. 6
    0
      src/com/vaadin/terminal/gwt/server/NoInputStreamException.java
  14. 6
    0
      src/com/vaadin/terminal/gwt/server/NoOutputStreamException.java
  15. 1
    0
      src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java
  16. 59
    36
      src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java
  17. 15
    0
      src/com/vaadin/terminal/gwt/server/ReceivingEndedEventImpl.java
  18. 22
    0
      src/com/vaadin/terminal/gwt/server/ReceivingFailedEventImpl.java
  19. 15
    0
      src/com/vaadin/terminal/gwt/server/ReceivingProgressedEventImpl.java
  20. 15
    0
      src/com/vaadin/terminal/gwt/server/ReceivingStartedEventImpl.java
  21. 12
    0
      src/com/vaadin/terminal/gwt/server/UploadException.java
  22. 122
    86
      src/com/vaadin/ui/DragAndDropWrapper.java
  23. 137
    0
      src/com/vaadin/ui/Html5File.java
  24. 86
    165
      src/com/vaadin/ui/Upload.java
  25. 1
    1
      tests/src/com/vaadin/tests/dd/DDTest6.java
  26. 129
    0
      tests/src/com/vaadin/tests/dd/DragAndDropFiles.java
  27. 1
    1
      tests/src/com/vaadin/tests/dd/DragDropPane.java

+ 1
- 11
build/build.xml View File

@@ -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>

+ 16
- 0
src/com/vaadin/terminal/PaintTarget.java View 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.

+ 29
- 0
src/com/vaadin/terminal/Receiver.java View File

@@ -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);
}

+ 161
- 0
src/com/vaadin/terminal/ReceiverOwner.java View File

@@ -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();
}

}

+ 162
- 96
src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java View 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() {

+ 1
- 1
src/com/vaadin/terminal/gwt/client/ui/VUpload.java View 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");


+ 9
- 24
src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java View 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() + "'");

+ 13
- 2
src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java View 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) {

+ 317
- 144
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java View 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);
}

+ 49
- 0
src/com/vaadin/terminal/gwt/server/AbstractReceivingEvent.java View File

@@ -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;
}

}

+ 108
- 22
src/com/vaadin/terminal/gwt/server/CommunicationManager.java View 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;

}

}

+ 9
- 0
src/com/vaadin/terminal/gwt/server/JsonPaintTarget.java View 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);
}

}

+ 6
- 0
src/com/vaadin/terminal/gwt/server/NoInputStreamException.java View File

@@ -0,0 +1,6 @@
package com.vaadin.terminal.gwt.server;

@SuppressWarnings("serial")
public class NoInputStreamException extends Exception {

}

+ 6
- 0
src/com/vaadin/terminal/gwt/server/NoOutputStreamException.java View File

@@ -0,0 +1,6 @@
package com.vaadin.terminal.gwt.server;

@SuppressWarnings("serial")
public class NoOutputStreamException extends Exception {

}

+ 1
- 0
src/com/vaadin/terminal/gwt/server/PortletApplicationContext2.java View 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;
}
}

+ 59
- 36
src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java View 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();
}

}

+ 15
- 0
src/com/vaadin/terminal/gwt/server/ReceivingEndedEventImpl.java View File

@@ -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);
}

}

+ 22
- 0
src/com/vaadin/terminal/gwt/server/ReceivingFailedEventImpl.java View File

@@ -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;
}

}

+ 15
- 0
src/com/vaadin/terminal/gwt/server/ReceivingProgressedEventImpl.java View File

@@ -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);
}

}

+ 15
- 0
src/com/vaadin/terminal/gwt/server/ReceivingStartedEventImpl.java View File

@@ -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);
}

}

+ 12
- 0
src/com/vaadin/terminal/gwt/server/UploadException.java View File

@@ -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);
}
}

+ 122
- 86
src/com/vaadin/ui/DragAndDropWrapper.java View 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;
}

}

+ 137
- 0
src/com/vaadin/ui/Html5File.java View File

@@ -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);
}

}

+ 86
- 165
src/com/vaadin/ui/Upload.java View 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;
}

}

+ 1
- 1
tests/src/com/vaadin/tests/dd/DDTest6.java View 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;

+ 129
- 0
tests/src/com/vaadin/tests/dd/DragAndDropFiles.java View File

@@ -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;
}

}

+ 1
- 1
tests/src/com/vaadin/tests/dd/DragDropPane.java View 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;


Loading…
Cancel
Save