diff options
author | Matti Tahvonen <matti.tahvonen@itmill.com> | 2010-10-08 11:21:50 +0000 |
---|---|---|
committer | Matti Tahvonen <matti.tahvonen@itmill.com> | 2010-10-08 11:21:50 +0000 |
commit | 011608a0a39784c3834aa1a4c42f6b7280f3240a (patch) | |
tree | ec592e82dfac39f98c67a28211cfb9adf3d0cbed /src/com/vaadin/terminal | |
parent | 4f1b424e2edd2f0c0c2b0f0aeb27d36e0c6155f3 (diff) | |
download | vaadin-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
Diffstat (limited to 'src/com/vaadin/terminal')
20 files changed, 1025 insertions, 325 deletions
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); + } +} |