aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--compatibility-client/src/main/java/com/vaadin/v7/client/ui/AbstractLegacyComponentConnector.java39
-rw-r--r--compatibility-client/src/main/java/com/vaadin/v7/client/ui/VUpload.java394
-rw-r--r--compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadConnector.java112
-rw-r--r--compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategy.java39
-rw-r--r--compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategyIE.java42
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/ui/Upload.java1173
-rw-r--r--compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadClientRpc.java26
-rw-r--r--compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadServerRpc.java38
-rw-r--r--compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadState.java30
-rw-r--r--uitest/src/main/java/com/vaadin/v7/tests/components/upload/TestFileUpload.java77
-rw-r--r--uitest/src/test/java/com/vaadin/v7/tests/components/upload/TestFileUploadTest.java126
11 files changed, 2096 insertions, 0 deletions
diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/AbstractLegacyComponentConnector.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/AbstractLegacyComponentConnector.java
new file mode 100644
index 0000000000..a9fe473ad4
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/AbstractLegacyComponentConnector.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.v7.client.ui;
+
+import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.AbstractConnector;
+import com.vaadin.shared.communication.ServerRpc;
+
+/**
+ * Legacy connector for Vaadin 7 compatibility connectors. Needed because
+ * <code>immediate</code> has been removed from {@link AbstractConnector} in
+ * Vaadin 8.
+ *
+ * @author Vaadin Ltd
+ * @since 8.0
+ */
+public class AbstractLegacyComponentConnector
+ extends AbstractComponentConnector {
+
+ // overridden to be visible to VUpload in the same package. Without making
+ // it public in VUploadConnector
+ @Override
+ protected <T extends ServerRpc> T getRpcProxy(Class<T> rpcInterface) {
+ return super.getRpcProxy(rpcInterface);
+ }
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VUpload.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VUpload.java
new file mode 100644
index 0000000000..16108792b4
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/VUpload.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.v7.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.FormElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.FileUpload;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.Hidden;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.StyleConstants;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.VButton;
+import com.vaadin.v7.client.ui.upload.UploadConnector;
+import com.vaadin.v7.client.ui.upload.UploadIFrameOnloadStrategy;
+import com.vaadin.v7.shared.ui.upload.UploadServerRpc;
+
+/**
+ *
+ * Note, we are not using GWT FormPanel as we want to listen submitcomplete
+ * events even though the upload component is already detached.
+ *
+ */
+public class VUpload extends SimplePanel {
+
+ private final class MyFileUpload extends FileUpload {
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ if (event.getTypeInt() == Event.ONCHANGE) {
+ if (immediate && fu.getFilename() != null
+ && !"".equals(fu.getFilename())) {
+ submit();
+ }
+ } else if (BrowserInfo.get().isIE()
+ && event.getTypeInt() == Event.ONFOCUS) {
+ // IE and user has clicked on hidden textarea part of upload
+ // field. Manually open file selector, other browsers do it by
+ // default.
+ fireNativeClick(fu.getElement());
+ // also remove focus to enable hack if user presses cancel
+ // button
+ fireNativeBlur(fu.getElement());
+ }
+ }
+ }
+
+ public static final String CLASSNAME = "v-upload";
+
+ /**
+ * FileUpload component that opens native OS dialog to select file.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public FileUpload fu = new MyFileUpload();
+
+ Panel panel = new FlowPanel();
+
+ UploadIFrameOnloadStrategy onloadstrategy = GWT
+ .create(UploadIFrameOnloadStrategy.class);
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public ApplicationConnection client;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public String paintableId;
+
+ /**
+ * Button that initiates uploading.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public final VButton submitButton;
+
+ /**
+ * When expecting big files, programmer may initiate some UI changes when
+ * uploading the file starts. Bit after submitting file we'll visit the
+ * server to check possible changes.
+ * <p>
+ * For internal use only. May be removed or replaced in the future.
+ */
+ public Timer t;
+
+ /**
+ * some browsers tries to send form twice if submit is called in button
+ * click handler, some don't submit at all without it, so we need to track
+ * if form is already being submitted
+ */
+ private boolean submitted = false;
+
+ private boolean enabled = true;
+
+ private boolean immediate;
+
+ private Hidden maxfilesize = new Hidden();
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public FormElement element;
+
+ private com.google.gwt.dom.client.Element synthesizedFrame;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public int nextUploadId;
+
+ public VUpload() {
+ super(com.google.gwt.dom.client.Document.get().createFormElement());
+
+ element = getElement().cast();
+ setEncoding(getElement(), FormPanel.ENCODING_MULTIPART);
+ element.setMethod(FormPanel.METHOD_POST);
+
+ setWidget(panel);
+ panel.add(maxfilesize);
+ panel.add(fu);
+ submitButton = new VButton();
+ submitButton.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ if (immediate) {
+ // fire click on upload (eg. focused button and hit space)
+ fireNativeClick(fu.getElement());
+ } else {
+ submit();
+ }
+ }
+ });
+ panel.add(submitButton);
+
+ setStyleName(CLASSNAME);
+ }
+
+ private static native void setEncoding(Element form, String encoding)
+ /*-{
+ form.enctype = encoding;
+ }-*/;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void setImmediate(boolean booleanAttribute) {
+ if (immediate != booleanAttribute) {
+ immediate = booleanAttribute;
+ if (immediate) {
+ fu.sinkEvents(Event.ONCHANGE);
+ fu.sinkEvents(Event.ONFOCUS);
+ }
+ }
+ setStyleName(getElement(), CLASSNAME + "-immediate", immediate);
+ }
+
+ private static native void fireNativeClick(Element element)
+ /*-{
+ element.click();
+ }-*/;
+
+ private static native void fireNativeBlur(Element element)
+ /*-{
+ element.blur();
+ }-*/;
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void disableUpload() {
+ setEnabledForSubmitButton(false);
+ if (!submitted) {
+ // Cannot disable the fileupload while submitting or the file won't
+ // be submitted at all
+ fu.getElement().setPropertyBoolean("disabled", true);
+ }
+ enabled = false;
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void enableUpload() {
+ setEnabledForSubmitButton(true);
+ fu.getElement().setPropertyBoolean("disabled", false);
+ enabled = true;
+ if (submitted) {
+ /*
+ * An old request is still in progress (most likely cancelled),
+ * ditching that target frame to make it possible to send a new
+ * file. A new target frame is created later."
+ */
+ cleanTargetFrame();
+ submitted = false;
+ }
+ }
+
+ private void setEnabledForSubmitButton(boolean enabled) {
+ submitButton.setEnabled(enabled);
+ submitButton.setStyleName(StyleConstants.DISABLED, !enabled);
+ }
+
+ /**
+ * Re-creates file input field and populates panel. This is needed as we
+ * want to clear existing values from our current file input field.
+ */
+ private void rebuildPanel() {
+ panel.remove(submitButton);
+ panel.remove(fu);
+ fu = new MyFileUpload();
+ fu.setName(paintableId + "_file");
+ fu.getElement().setPropertyBoolean("disabled", !enabled);
+ panel.add(fu);
+ panel.add(submitButton);
+ if (immediate) {
+ fu.sinkEvents(Event.ONCHANGE);
+ }
+ }
+
+ /**
+ * Called by JSNI (hooked via {@link #onloadstrategy})
+ */
+ private void onSubmitComplete() {
+ /* Needs to be run dereferred to avoid various browser issues. */
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ if (submitted) {
+ if (client != null) {
+ if (t != null) {
+ t.cancel();
+ }
+ VConsole.log("VUpload:Submit complete");
+ ((UploadConnector) ConnectorMap.get(client)
+ .getConnector(VUpload.this))
+ .getRpcProxy(UploadServerRpc.class)
+ .poll();
+ }
+
+ rebuildPanel();
+
+ submitted = false;
+ enableUpload();
+ if (!isAttached()) {
+ /*
+ * Upload is complete when upload is already abandoned.
+ */
+ cleanTargetFrame();
+ }
+ }
+ }
+ });
+ }
+
+ ScheduledCommand startUploadCmd = new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ element.submit();
+ submitted = true;
+
+ disableUpload();
+
+ /*
+ * Visit server a moment after upload has started to see possible
+ * changes from UploadStarted event. Will be cleared on complete.
+ *
+ * Must get the id here as the upload can finish before the timer
+ * expires and in that case nextUploadId has been updated and is
+ * wrong.
+ */
+ final int thisUploadId = nextUploadId;
+ t = new Timer() {
+ @Override
+ public void run() {
+ // Only visit the server if the upload has not already
+ // finished
+ if (thisUploadId == nextUploadId) {
+ VConsole.log(
+ "Visiting server to see if upload started event changed UI.");
+ client.updateVariable(paintableId, "pollForStart",
+ thisUploadId, true);
+ }
+ }
+ };
+ t.schedule(800);
+ }
+
+ };
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void submit() {
+ if (submitted || !enabled) {
+ VConsole.log("Submit cancelled (disabled or already submitted)");
+ return;
+ }
+ if (fu.getFilename().length() == 0) {
+ VConsole.log("Submitting empty selection (no file)");
+ }
+ // flush possibly pending variable changes, so they will be handled
+ // before upload
+ client.sendPendingVariableChanges();
+
+ // This is done as deferred because sendPendingVariableChanges is also
+ // deferred and we want to start the upload only after the changes have
+ // been sent to the server
+ Scheduler.get().scheduleDeferred(startUploadCmd);
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void disableTitle(boolean disable) {
+ if (disable) {
+ // Disable title attribute for upload element.
+ if (BrowserInfo.get().isChrome()) {
+ // In Chrome title has to be set to " " to make it invisible
+ fu.setTitle(" ");
+ } else if (BrowserInfo.get().isFirefox()) {
+ // In FF title has to be set to empty string to make it
+ // invisible
+ // Method setTitle removes title attribute when it's an empty
+ // string, so setAttribute() should be used here
+ fu.getElement().setAttribute("title", "");
+ }
+ // For other browsers absent title doesn't show default tooltip for
+ // input element
+ } else {
+ fu.setTitle(null);
+ }
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ if (client != null) {
+ ensureTargetFrame();
+ }
+ }
+
+ /** For internal use only. May be removed or replaced in the future. */
+ public void ensureTargetFrame() {
+ if (synthesizedFrame == null) {
+ // Attach a hidden IFrame to the form. This is the target iframe to
+ // which the form will be submitted. We have to create the iframe
+ // using innerHTML, because setting an iframe's 'name' property
+ // dynamically doesn't work on most browsers.
+ DivElement dummy = Document.get().createDivElement();
+ dummy.setInnerHTML("<iframe src=\"javascript:''\" name='"
+ + getFrameName()
+ + "' style='position:absolute;width:0;height:0;border:0'>");
+ synthesizedFrame = dummy.getFirstChildElement();
+ Document.get().getBody().appendChild(synthesizedFrame);
+ element.setTarget(getFrameName());
+ onloadstrategy.hookEvents(synthesizedFrame, this);
+ }
+ }
+
+ private String getFrameName() {
+ return paintableId + "_TGT_FRAME";
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ if (!submitted) {
+ cleanTargetFrame();
+ }
+ }
+
+ private void cleanTargetFrame() {
+ if (synthesizedFrame != null) {
+ Document.get().getBody().removeChild(synthesizedFrame);
+ onloadstrategy.unHookEvents(synthesizedFrame);
+ synthesizedFrame = null;
+ }
+ }
+
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadConnector.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadConnector.java
new file mode 100644
index 0000000000..eefbe60a1a
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadConnector.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.v7.client.ui.upload;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.Paintable;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.v7.client.ui.AbstractLegacyComponentConnector;
+import com.vaadin.v7.client.ui.VUpload;
+import com.vaadin.v7.shared.ui.upload.UploadClientRpc;
+import com.vaadin.v7.shared.ui.upload.UploadServerRpc;
+import com.vaadin.v7.shared.ui.upload.UploadState;
+
+@Connect(com.vaadin.v7.ui.Upload.class)
+public class UploadConnector extends AbstractLegacyComponentConnector
+ implements Paintable {
+
+ public UploadConnector() {
+ registerRpc(UploadClientRpc.class, new UploadClientRpc() {
+ @Override
+ public void submitUpload() {
+ getWidget().submit();
+ }
+ });
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+
+ getWidget().fu.addChangeHandler(new ChangeHandler() {
+ @Override
+ public void onChange(ChangeEvent event) {
+ if (hasEventListener(EventId.CHANGE)) {
+ getRpcProxy(UploadServerRpc.class)
+ .change(getWidget().fu.getFilename());
+ }
+ }
+ });
+ }
+
+ @Override
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ if (!isRealUpdate(uidl)) {
+ return;
+ }
+ if (uidl.hasAttribute("notStarted")) {
+ getWidget().t.schedule(400);
+ return;
+ }
+ getWidget().setImmediate(getState().immediate);
+ getWidget().client = client;
+ getWidget().paintableId = uidl.getId();
+ getWidget().nextUploadId = uidl.getIntAttribute("nextid");
+ final String action = client
+ .translateVaadinUri(uidl.getStringVariable("action"));
+ getWidget().element.setAction(action);
+ if (uidl.hasAttribute("buttoncaption")) {
+ getWidget().submitButton
+ .setText(uidl.getStringAttribute("buttoncaption"));
+ getWidget().submitButton.setVisible(true);
+ } else {
+ getWidget().submitButton.setVisible(false);
+ }
+ getWidget().fu.setName(getWidget().paintableId + "_file");
+
+ if (!isEnabled() || isReadOnly()) {
+ getWidget().disableUpload();
+ } else if (!uidl.getBooleanAttribute("state")) {
+ // Enable the button only if an upload is not in progress
+ getWidget().enableUpload();
+ getWidget().ensureTargetFrame();
+ }
+ }
+
+ @Override
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+ super.onStateChanged(stateChangeEvent);
+
+ getWidget().disableTitle(hasTooltip());
+ }
+
+ @Override
+ public VUpload getWidget() {
+ return (VUpload) super.getWidget();
+ }
+
+ @Override
+ public UploadState getState() {
+ return (UploadState) super.getState();
+ }
+
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategy.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategy.java
new file mode 100644
index 0000000000..c01fce1b50
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategy.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.v7.client.ui.upload;
+
+import com.vaadin.v7.client.ui.VUpload;
+
+public class UploadIFrameOnloadStrategy {
+
+ public native void hookEvents(com.google.gwt.dom.client.Element iframe,
+ VUpload upload)
+ /*-{
+ iframe.onload = $entry(function() {
+ upload.@com.vaadin.client.ui.VUpload::onSubmitComplete()();
+ });
+ }-*/;
+
+ /**
+ * @param iframe
+ * the iframe whose onLoad event is to be cleaned
+ */
+ public native void unHookEvents(com.google.gwt.dom.client.Element iframe)
+ /*-{
+ iframe.onload = null;
+ }-*/;
+
+}
diff --git a/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategyIE.java b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategyIE.java
new file mode 100644
index 0000000000..a40abfafdf
--- /dev/null
+++ b/compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategyIE.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.v7.client.ui.upload;
+
+import com.google.gwt.dom.client.Element;
+import com.vaadin.v7.client.ui.VUpload;
+
+/**
+ * IE does not have onload, detect onload via readystatechange
+ *
+ */
+public class UploadIFrameOnloadStrategyIE extends UploadIFrameOnloadStrategy {
+ @Override
+ public native void hookEvents(Element iframe, VUpload upload)
+ /*-{
+ iframe.onreadystatechange = $entry(function() {
+ if (iframe.readyState == 'complete') {
+ upload.@com.vaadin.client.ui.VUpload::onSubmitComplete()();
+ }
+ });
+ }-*/;
+
+ @Override
+ public native void unHookEvents(Element iframe)
+ /*-{
+ iframe.onreadystatechange = null;
+ }-*/;
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/ui/Upload.java b/compatibility-server/src/main/java/com/vaadin/v7/ui/Upload.java
new file mode 100644
index 0000000000..d3bc4b1473
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/ui/Upload.java
@@ -0,0 +1,1173 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.v7.ui;
+
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Objects;
+
+import com.vaadin.server.NoInputStreamException;
+import com.vaadin.server.NoOutputStreamException;
+import com.vaadin.server.PaintException;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.server.StreamVariable.StreamingProgressEvent;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.Registration;
+import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.LegacyComponent;
+import com.vaadin.util.ReflectTools;
+import com.vaadin.v7.shared.ui.upload.UploadClientRpc;
+import com.vaadin.v7.shared.ui.upload.UploadServerRpc;
+import com.vaadin.v7.shared.ui.upload.UploadState;
+
+/**
+ * Component for uploading files from client to server.
+ *
+ * <p>
+ * The visible component consists of a file name input box and a browse button
+ * and an upload submit button to start uploading.
+ *
+ * <p>
+ * The Upload component needs a java.io.OutputStream to write the uploaded data.
+ * You need to implement the Upload.Receiver interface and return the output
+ * stream in the receiveUpload() method.
+ *
+ * <p>
+ * You can get an event regarding starting (StartedEvent), progress
+ * (ProgressEvent), and finishing (FinishedEvent) of upload by implementing
+ * StartedListener, ProgressListener, and FinishedListener, respectively. The
+ * FinishedListener is called for both failed and succeeded uploads. If you wish
+ * to separate between these two cases, you can use SucceededListener
+ * (SucceededEvenet) and FailedListener (FailedEvent).
+ *
+ * <p>
+ * The upload component does not itself show upload progress, but you can use
+ * the ProgressIndicator for providing progress feedback by implementing
+ * ProgressListener and updating the indicator in updateProgress().
+ *
+ * <p>
+ * Setting upload component immediate initiates the upload as soon as a file is
+ * selected, instead of the common pattern of file selection field and upload
+ * button.
+ *
+ * <p>
+ * Note! Because of browser dependent implementations of <input type="file">
+ * element, setting size for Upload component is not supported. For some
+ * browsers setting size may work to some extend.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+@Deprecated
+public class Upload extends AbstractComponent
+ implements Component.Focusable, LegacyComponent {
+
+ /**
+ * Should the field be focused on next repaint?
+ */
+ private final boolean focus = false;
+
+ /**
+ * The tab order number of this field.
+ */
+ private int tabIndex = 0;
+
+ /**
+ * The output of the upload is redirected to this receiver.
+ */
+ private Receiver receiver;
+
+ private boolean isUploading;
+
+ private long contentLength = -1;
+
+ private int totalBytes;
+
+ private String buttonCaption = "Upload";
+
+ /**
+ * ProgressListeners to which information about progress is sent during
+ * upload
+ */
+ private LinkedHashSet<ProgressListener> progressListeners;
+
+ private boolean interrupted = false;
+
+ private boolean notStarted;
+
+ private int nextid;
+
+ /**
+ * Creates a new instance of Upload.
+ *
+ * The receiver must be set before performing an upload.
+ */
+ public Upload() {
+ registerRpc(new UploadServerRpc() {
+ @Override
+ public void change(String filename) {
+ fireEvent(new ChangeEvent(Upload.this, filename));
+ }
+
+ @Override
+ public void poll() {
+ // Nothing to do, called only to visit the server
+ }
+ });
+ }
+
+ public Upload(String caption, Receiver uploadReceiver) {
+ this();
+ setCaption(caption);
+ receiver = uploadReceiver;
+ }
+
+ /**
+ * Invoked when the value of a variable has changed.
+ *
+ * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ if (variables.containsKey("pollForStart")) {
+ int id = (Integer) variables.get("pollForStart");
+ if (!isUploading && id == nextid) {
+ notStarted = true;
+ markAsDirty();
+ } else {
+ }
+ }
+ }
+
+ /**
+ * Paints the content of this component.
+ *
+ * @param target
+ * Target to paint the content on.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (notStarted) {
+ target.addAttribute("notStarted", true);
+ notStarted = false;
+ return;
+ }
+ // The field should be focused
+ if (focus) {
+ target.addAttribute("focus", true);
+ }
+
+ // The tab ordering number
+ if (tabIndex >= 0) {
+ target.addAttribute("tabindex", tabIndex);
+ }
+
+ target.addAttribute("state", isUploading);
+
+ if (buttonCaption != null) {
+ target.addAttribute("buttoncaption", buttonCaption);
+ }
+
+ target.addAttribute("nextid", nextid);
+
+ // Post file to this strean variable
+ target.addVariable(this, "action", getStreamVariable());
+
+ }
+
+ /**
+ * Interface that must be implemented by the upload receivers to provide the
+ * Upload component an output stream to write the uploaded data.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ @Deprecated
+ 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);
+
+ }
+
+ /* Upload events */
+
+ private static final Method UPLOAD_FINISHED_METHOD;
+
+ private static final Method UPLOAD_FAILED_METHOD;
+
+ private static final Method UPLOAD_SUCCEEDED_METHOD;
+
+ private static final Method UPLOAD_STARTED_METHOD;
+
+ static {
+ try {
+ UPLOAD_FINISHED_METHOD = FinishedListener.class.getDeclaredMethod(
+ "uploadFinished", new Class[] { FinishedEvent.class });
+ UPLOAD_FAILED_METHOD = FailedListener.class.getDeclaredMethod(
+ "uploadFailed", new Class[] { FailedEvent.class });
+ UPLOAD_STARTED_METHOD = StartedListener.class.getDeclaredMethod(
+ "uploadStarted", new Class[] { StartedEvent.class });
+ UPLOAD_SUCCEEDED_METHOD = SucceededListener.class.getDeclaredMethod(
+ "uploadSucceeded", new Class[] { SucceededEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in Upload");
+ }
+ }
+
+ /**
+ * Upload.FinishedEvent is sent when the upload receives a file, regardless
+ * of whether the reception was successful or failed. If you wish to
+ * distinguish between the two cases, use either SucceededEvent or
+ * FailedEvent, which are both subclasses of the FinishedEvent.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ @Deprecated
+ public static class FinishedEvent extends Component.Event {
+
+ /**
+ * Length of the received file.
+ */
+ private final long length;
+
+ /**
+ * MIME type of the received file.
+ */
+ private final String type;
+
+ /**
+ * Received file name.
+ */
+ private final String filename;
+
+ /**
+ *
+ * @param source
+ * the source of the file.
+ * @param filename
+ * the received file name.
+ * @param MIMEType
+ * the MIME type of the received file.
+ * @param length
+ * the length of the received file.
+ */
+ public FinishedEvent(Upload source, String filename, String MIMEType,
+ long length) {
+ super(source);
+ type = MIMEType;
+ this.filename = filename;
+ this.length = length;
+ }
+
+ /**
+ * Uploads where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ public Upload getUpload() {
+ return (Upload) getSource();
+ }
+
+ /**
+ * Gets the file name.
+ *
+ * @return the filename.
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * Gets the MIME Type of the file.
+ *
+ * @return the MIME type.
+ */
+ public String getMIMEType() {
+ return type;
+ }
+
+ /**
+ * Gets the length of the file.
+ *
+ * @return the length.
+ */
+ public long getLength() {
+ return length;
+ }
+
+ }
+
+ /**
+ * Upload.FailedEvent event is sent when the upload is received, but the
+ * reception is interrupted for some reason.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ @Deprecated
+ public static class FailedEvent extends FinishedEvent {
+
+ private Exception reason = null;
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ * @param exception
+ */
+ public FailedEvent(Upload source, String filename, String MIMEType,
+ long length, Exception reason) {
+ this(source, filename, MIMEType, length);
+ this.reason = reason;
+ }
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ * @param exception
+ */
+ public FailedEvent(Upload source, String filename, String MIMEType,
+ long length) {
+ super(source, filename, MIMEType, length);
+ }
+
+ /**
+ * Gets the exception that caused the failure.
+ *
+ * @return the exception that caused the failure, null if n/a
+ */
+ public Exception getReason() {
+ return reason;
+ }
+
+ }
+
+ /**
+ * FailedEvent that indicates that an output stream could not be obtained.
+ */
+ @Deprecated
+ public static class NoOutputStreamEvent extends FailedEvent {
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ public NoOutputStreamEvent(Upload source, String filename,
+ String MIMEType, long length) {
+ super(source, filename, MIMEType, length);
+ }
+ }
+
+ /**
+ * FailedEvent that indicates that an input stream could not be obtained.
+ */
+ @Deprecated
+ public static class NoInputStreamEvent extends FailedEvent {
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ public NoInputStreamEvent(Upload source, String filename,
+ String MIMEType, long length) {
+ super(source, filename, MIMEType, length);
+ }
+
+ }
+
+ /**
+ * Upload.SucceededEvent event is sent when the upload is received
+ * successfully.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ @Deprecated
+ public static class SucceededEvent extends FinishedEvent {
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ public SucceededEvent(Upload source, String filename, String MIMEType,
+ long length) {
+ super(source, filename, MIMEType, length);
+ }
+
+ }
+
+ /**
+ * Upload.StartedEvent event is sent when the upload is started to received.
+ *
+ * @author Vaadin Ltd.
+ * @since 5.0
+ */
+ @Deprecated
+ public static class StartedEvent extends Component.Event {
+
+ private final String filename;
+ private final String type;
+ /**
+ * Length of the received file.
+ */
+ private final long length;
+
+ /**
+ *
+ * @param source
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ public StartedEvent(Upload source, String filename, String MIMEType,
+ long contentLength) {
+ super(source);
+ this.filename = filename;
+ type = MIMEType;
+ length = contentLength;
+ }
+
+ /**
+ * Uploads where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ public Upload getUpload() {
+ return (Upload) getSource();
+ }
+
+ /**
+ * Gets the file name.
+ *
+ * @return the filename.
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * Gets the MIME Type of the file.
+ *
+ * @return the MIME type.
+ */
+ public String getMIMEType() {
+ return type;
+ }
+
+ /**
+ * @return the length of the file that is being uploaded
+ */
+ public long getContentLength() {
+ return length;
+ }
+
+ }
+
+ /**
+ * Upload.ChangeEvent event is sent when the value (filename) of the upload
+ * changes.
+ *
+ * @since 7.2
+ */
+ @Deprecated
+ public static class ChangeEvent extends Component.Event {
+
+ private final String filename;
+
+ public ChangeEvent(Upload source, String filename) {
+ super(source);
+ this.filename = filename;
+ }
+
+ /**
+ * Uploads where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ @Override
+ public Upload getSource() {
+ return (Upload) super.getSource();
+ }
+
+ /**
+ * Gets the file name.
+ *
+ * @return the filename.
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ }
+
+ /**
+ * Receives the events when the upload starts.
+ *
+ * @author Vaadin Ltd.
+ * @since 5.0
+ */
+ @Deprecated
+ public interface StartedListener extends Serializable {
+
+ /**
+ * Upload has started.
+ *
+ * @param event
+ * the Upload started event.
+ */
+ public void uploadStarted(StartedEvent event);
+ }
+
+ /**
+ * Receives the events when the uploads are ready.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ @Deprecated
+ public interface FinishedListener extends Serializable {
+
+ /**
+ * Upload has finished.
+ *
+ * @param event
+ * the Upload finished event.
+ */
+ public void uploadFinished(FinishedEvent event);
+ }
+
+ /**
+ * Receives events when the uploads are finished, but unsuccessful.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ @Deprecated
+ public interface FailedListener extends Serializable {
+
+ /**
+ * Upload has finished unsuccessfully.
+ *
+ * @param event
+ * the Upload failed event.
+ */
+ public void uploadFailed(FailedEvent event);
+ }
+
+ /**
+ * Receives events when the uploads are successfully finished.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ @Deprecated
+ public interface SucceededListener extends Serializable {
+
+ /**
+ * Upload successfull..
+ *
+ * @param event
+ * the Upload successfull event.
+ */
+ public void uploadSucceeded(SucceededEvent event);
+ }
+
+ /**
+ * Listener for {@link ChangeEvent}
+ *
+ * @since 7.2
+ */
+ @Deprecated
+ public interface ChangeListener extends Serializable {
+
+ Method FILENAME_CHANGED = ReflectTools.findMethod(ChangeListener.class,
+ "filenameChanged", ChangeEvent.class);
+
+ /**
+ * A file has been selected but upload has not yet started.
+ *
+ * @param event
+ * the change event
+ */
+ public void filenameChanged(ChangeEvent event);
+ }
+
+ /**
+ * Adds the upload started event listener.
+ *
+ * @param listener
+ * the Listener to be added, not null
+ */
+ public Registration addStartedListener(StartedListener listener) {
+ addListener(StartedEvent.class, listener, UPLOAD_STARTED_METHOD);
+ return () -> removeListener(StartedEvent.class, listener,
+ UPLOAD_STARTED_METHOD);
+ }
+
+ /**
+ * Removes the upload started event listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ @Deprecated
+ public void removeStartedListener(StartedListener listener) {
+ removeListener(StartedEvent.class, listener, UPLOAD_STARTED_METHOD);
+ }
+
+ /**
+ * Adds the upload received event listener.
+ *
+ * @param listener
+ * the Listener to be added, not null
+ */
+ public Registration addFinishedListener(FinishedListener listener) {
+ addListener(FinishedEvent.class, listener, UPLOAD_FINISHED_METHOD);
+ return () -> removeListener(FinishedEvent.class, listener,
+ UPLOAD_FINISHED_METHOD);
+ }
+
+ /**
+ * Removes the upload received event listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ @Deprecated
+ public void removeFinishedListener(FinishedListener listener) {
+ removeListener(FinishedEvent.class, listener, UPLOAD_FINISHED_METHOD);
+ }
+
+ /**
+ * Adds the upload interrupted event listener.
+ *
+ * @param listener
+ * the Listener to be added, not null
+ */
+ public Registration addFailedListener(FailedListener listener) {
+ addListener(FailedEvent.class, listener, UPLOAD_FAILED_METHOD);
+ return () -> removeListener(FailedEvent.class, listener,
+ UPLOAD_FAILED_METHOD);
+ }
+
+ /**
+ * Removes the upload interrupted event listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ @Deprecated
+ public void removeFailedListener(FailedListener listener) {
+ removeListener(FailedEvent.class, listener, UPLOAD_FAILED_METHOD);
+ }
+
+ /**
+ * Adds the upload success event listener.
+ *
+ * @param listener
+ * the Listener to be added, not null
+ */
+ public Registration addSucceededListener(SucceededListener listener) {
+ addListener(SucceededEvent.class, listener, UPLOAD_SUCCEEDED_METHOD);
+ return () -> removeListener(SucceededEvent.class, listener,
+ UPLOAD_SUCCEEDED_METHOD);
+ }
+
+ /**
+ * Removes the upload success event listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ @Deprecated
+ public void removeSucceededListener(SucceededListener listener) {
+ removeListener(SucceededEvent.class, listener, UPLOAD_SUCCEEDED_METHOD);
+ }
+
+ /**
+ * Adds the upload progress event listener.
+ *
+ * @param listener
+ * the progress listener to be added
+ */
+ public Registration addProgressListener(ProgressListener listener) {
+ Objects.requireNonNull(listener, "Listener must not be null.");
+ if (progressListeners == null) {
+ progressListeners = new LinkedHashSet<>();
+ }
+ progressListeners.add(listener);
+ return () -> {
+ if (progressListeners != null) {
+ progressListeners.remove(listener);
+ }
+ };
+ }
+
+ /**
+ * Removes the upload progress event listener.
+ *
+ * @param listener
+ * the progress listener to be removed
+ */
+ @Deprecated
+ public void removeProgressListener(ProgressListener listener) {
+ if (progressListeners != null) {
+ progressListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Adds a filename change event listener
+ *
+ * @param listener
+ * the Listener to add, not null
+ */
+ public Registration addChangeListener(ChangeListener listener) {
+ super.addListener(EventId.CHANGE, ChangeEvent.class, listener,
+ ChangeListener.FILENAME_CHANGED);
+ return () -> super.removeListener(EventId.CHANGE, ChangeEvent.class,
+ listener);
+ }
+
+ /**
+ * Removes a filename change event listener
+ *
+ * @param listener
+ * the listener to be removed
+ */
+ @Deprecated
+ public void removeChangeListener(ChangeListener listener) {
+ super.removeListener(EventId.CHANGE, ChangeEvent.class, listener);
+ }
+
+ /**
+ * Emit upload received event.
+ *
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ protected void fireStarted(String filename, String MIMEType) {
+ fireEvent(new Upload.StartedEvent(this, filename, MIMEType,
+ contentLength));
+ }
+
+ /**
+ * Emits the upload failed event.
+ *
+ * @param filename
+ * @param MIMEType
+ * @param length
+ */
+ protected void fireUploadInterrupted(String filename, String MIMEType,
+ long length) {
+ fireEvent(new Upload.FailedEvent(this, filename, MIMEType, length));
+ }
+
+ protected void fireNoInputStream(String filename, String MIMEType,
+ long length) {
+ fireEvent(new Upload.NoInputStreamEvent(this, filename, MIMEType,
+ length));
+ }
+
+ protected void fireNoOutputStream(String filename, String MIMEType,
+ long length) {
+ fireEvent(new Upload.NoOutputStreamEvent(this, filename, MIMEType,
+ length));
+ }
+
+ protected void fireUploadInterrupted(String filename, String MIMEType,
+ long length, Exception e) {
+ fireEvent(new Upload.FailedEvent(this, filename, MIMEType, length, e));
+ }
+
+ /**
+ * Emits the upload success event.
+ *
+ * @param filename
+ * @param MIMEType
+ * @param length
+ *
+ */
+ protected void fireUploadSuccess(String filename, String MIMEType,
+ long length) {
+ fireEvent(new Upload.SucceededEvent(this, filename, MIMEType, length));
+ }
+
+ /**
+ * Emits the progress event.
+ *
+ * @param totalBytes
+ * bytes received so far
+ * @param contentLength
+ * actual size of the file being uploaded, if known
+ *
+ */
+ protected void fireUpdateProgress(long totalBytes, long contentLength) {
+ // this is implemented differently than other listeners to maintain
+ // backwards compatibility
+ if (progressListeners != null) {
+ for (Iterator<ProgressListener> it = progressListeners
+ .iterator(); it.hasNext();) {
+ ProgressListener l = it.next();
+ l.updateProgress(totalBytes, contentLength);
+ }
+ }
+ }
+
+ /**
+ * Returns the current receiver.
+ *
+ * @return the StreamVariable.
+ */
+ public Receiver getReceiver() {
+ return receiver;
+ }
+
+ /**
+ * Sets the receiver.
+ *
+ * @param receiver
+ * the receiver to set.
+ */
+ public void setReceiver(Receiver receiver) {
+ this.receiver = receiver;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void focus() {
+ super.focus();
+ }
+
+ /**
+ * Gets the Tabulator index of this Focusable component.
+ *
+ * @see com.vaadin.ui.Component.Focusable#getTabIndex()
+ */
+ @Override
+ public int getTabIndex() {
+ return tabIndex;
+ }
+
+ /**
+ * Sets the Tabulator index of this Focusable component.
+ *
+ * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
+ */
+ @Override
+ public void setTabIndex(int tabIndex) {
+ this.tabIndex = tabIndex;
+ }
+
+ /**
+ * Go into upload state. This is to prevent double uploading on same
+ * component.
+ *
+ * Warning: this is an internal method used by the framework and should not
+ * be used by user of the Upload component. Using it results in the Upload
+ * component going in wrong state and not working. It is currently public
+ * because it is used by another class.
+ */
+ public void startUpload() {
+ if (isUploading) {
+ throw new IllegalStateException("uploading already started");
+ }
+ isUploading = true;
+ nextid++;
+ }
+
+ /**
+ * Interrupts the upload currently being received. The interruption will be
+ * done by the receiving thread so this method will return immediately and
+ * the actual interrupt will happen a bit later.
+ */
+ public void interruptUpload() {
+ if (isUploading) {
+ interrupted = true;
+ }
+ }
+
+ /**
+ * Go into state where new uploading can begin.
+ *
+ * Warning: this is an internal method used by the framework and should not
+ * be used by user of the Upload component.
+ */
+ private void endUpload() {
+ isUploading = false;
+ contentLength = -1;
+ interrupted = false;
+ markAsDirty();
+ }
+
+ public boolean isUploading() {
+ return isUploading;
+ }
+
+ /**
+ * Gets read bytes of the file currently being uploaded.
+ *
+ * @return bytes
+ */
+ public long getBytesRead() {
+ return totalBytes;
+ }
+
+ /**
+ * Returns size of file currently being uploaded. Value sane only during
+ * upload.
+ *
+ * @return size in bytes
+ */
+ public long getUploadSize() {
+ return contentLength;
+ }
+
+ /**
+ * ProgressListener receives events to track progress of upload.
+ */
+ @Deprecated
+ public interface ProgressListener extends Serializable {
+ /**
+ * Updates progress to listener
+ *
+ * @param readBytes
+ * bytes transferred
+ * @param contentLength
+ * total size of file currently being uploaded, -1 if unknown
+ */
+ public void updateProgress(long readBytes, long contentLength);
+ }
+
+ /**
+ * @return String to be rendered into button that fires uploading
+ */
+ public String getButtonCaption() {
+ return buttonCaption;
+ }
+
+ /**
+ * In addition to the actual file chooser, upload components have button
+ * that starts actual upload progress. This method is used to set text in
+ * that button.
+ * <p>
+ * In case the button text is set to null, the button is hidden. In this
+ * case developer must explicitly initiate the upload process with
+ * {@link #submitUpload()}.
+ * <p>
+ * In case the Upload is used in immediate mode using
+ * {@link #setImmediate(boolean)}, the file choose (html input with type
+ * "file") is hidden and only the button with this text is shown.
+ * <p>
+ *
+ * <p>
+ * <strong>Note</strong> the string given is set as is to the button. HTML
+ * formatting is not stripped. Be sure to properly validate your value
+ * according to your needs.
+ *
+ * @param buttonCaption
+ * text for upload components button.
+ */
+ public void setButtonCaption(String buttonCaption) {
+ this.buttonCaption = buttonCaption;
+ markAsDirty();
+ }
+
+ /**
+ * Forces the upload the send selected file to the server.
+ * <p>
+ * In case developer wants to use this feature, he/she will most probably
+ * want to hide the uploads internal submit button by setting its caption to
+ * null with {@link #setButtonCaption(String)} method.
+ * <p>
+ * Note, that the upload runs asynchronous. Developer should use normal
+ * upload listeners to trac the process of upload. If the field is empty
+ * uploaded the file name will be empty string and file length 0 in the
+ * upload finished event.
+ * <p>
+ * Also note, that the developer should not remove or modify the upload in
+ * the same user transaction where the upload submit is requested. The
+ * upload may safely be hidden or removed once the upload started event is
+ * fired.
+ */
+ public void submitUpload() {
+ markAsDirty();
+ getRpcProxy(UploadClientRpc.class).submitUpload();
+ }
+
+ @Override
+ public void markAsDirty() {
+ super.markAsDirty();
+ }
+
+ /*
+ * Handle to terminal via Upload monitors and controls the upload during it
+ * is being streamed.
+ */
+ private com.vaadin.server.StreamVariable streamVariable;
+
+ protected com.vaadin.server.StreamVariable getStreamVariable() {
+ if (streamVariable == null) {
+ streamVariable = new com.vaadin.server.StreamVariable() {
+ private StreamingStartEvent lastStartedEvent;
+
+ @Override
+ public boolean listenProgress() {
+ return progressListeners != null
+ && !progressListeners.isEmpty();
+ }
+
+ @Override
+ public void onProgress(StreamingProgressEvent event) {
+ fireUpdateProgress(event.getBytesReceived(),
+ event.getContentLength());
+ }
+
+ @Override
+ public boolean isInterrupted() {
+ return interrupted;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ if (getReceiver() == null) {
+ throw new IllegalStateException(
+ "Upload cannot be performed without a receiver set");
+ }
+ OutputStream receiveUpload = getReceiver().receiveUpload(
+ lastStartedEvent.getFileName(),
+ lastStartedEvent.getMimeType());
+ lastStartedEvent = null;
+ return receiveUpload;
+ }
+
+ @Override
+ public void streamingStarted(StreamingStartEvent event) {
+ startUpload();
+ contentLength = event.getContentLength();
+ fireStarted(event.getFileName(), event.getMimeType());
+ lastStartedEvent = event;
+ }
+
+ @Override
+ public void streamingFinished(StreamingEndEvent event) {
+ fireUploadSuccess(event.getFileName(), event.getMimeType(),
+ event.getContentLength());
+ endUpload();
+ }
+
+ @Override
+ public void streamingFailed(StreamingErrorEvent event) {
+ try {
+ 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);
+ }
+ } finally {
+ endUpload();
+ }
+ }
+ };
+ }
+ return streamVariable;
+ }
+
+ @Override
+ public java.util.Collection<?> getListeners(java.lang.Class<?> eventType) {
+ if (StreamingProgressEvent.class.isAssignableFrom(eventType)) {
+ if (progressListeners == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections.unmodifiableCollection(progressListeners);
+ }
+
+ }
+ return super.getListeners(eventType);
+ }
+
+ /**
+ * Returns the immediate mode of the component.
+ * <p>
+ * An immediate mode Upload component displays the browser file choosing
+ * button immediately, whereas a non-immediate upload only shows a Vaadin
+ * button.
+ * <p>
+ * The default mode of an Upload component is non-immediate.
+ *
+ * @return true if the component is in immediate mode, false if the
+ * component if not in immediate mode
+ */
+ @Override
+ public boolean isImmediate() {
+ if (getExplicitImmediateValue() != null) {
+ return getExplicitImmediateValue();
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected UploadState getState() {
+ return (UploadState) super.getState();
+ }
+}
diff --git a/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadClientRpc.java b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadClientRpc.java
new file mode 100644
index 0000000000..0dbdcdfd99
--- /dev/null
+++ b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadClientRpc.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.v7.shared.ui.upload;
+
+import com.vaadin.shared.communication.ClientRpc;
+
+public interface UploadClientRpc extends ClientRpc {
+
+ /**
+ * Forces the upload the send selected file to the server.
+ */
+ void submitUpload();
+}
diff --git a/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadServerRpc.java b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadServerRpc.java
new file mode 100644
index 0000000000..a50f53b0a7
--- /dev/null
+++ b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadServerRpc.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.v7.shared.ui.upload;
+
+import com.vaadin.shared.communication.ServerRpc;
+
+public interface UploadServerRpc extends ServerRpc {
+
+ /**
+ * Event sent when the file name of the upload component is changed.
+ *
+ * @param filename
+ * The filename
+ */
+ void change(String filename);
+
+ /**
+ * Called to poll the server to see if any changes have been made e.g. when
+ * starting upload
+ *
+ * @since 7.6
+ */
+ void poll();
+
+}
diff --git a/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadState.java b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadState.java
new file mode 100644
index 0000000000..2a6cb0ea66
--- /dev/null
+++ b/compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadState.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.v7.shared.ui.upload;
+
+import com.vaadin.shared.AbstractComponentState;
+
+/**
+ * Shared state for the Upload component.
+ *
+ * @since 7.6
+ */
+public class UploadState extends AbstractComponentState {
+
+ {
+ primaryStyleName = "v-upload";
+ }
+}
diff --git a/uitest/src/main/java/com/vaadin/v7/tests/components/upload/TestFileUpload.java b/uitest/src/main/java/com/vaadin/v7/tests/components/upload/TestFileUpload.java
new file mode 100644
index 0000000000..fc430b4838
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/v7/tests/components/upload/TestFileUpload.java
@@ -0,0 +1,77 @@
+package com.vaadin.v7.tests.components.upload;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.codec.digest.DigestUtils;
+
+import com.vaadin.tests.components.TestBase;
+import com.vaadin.tests.util.Log;
+import com.vaadin.v7.ui.Upload;
+import com.vaadin.v7.ui.Upload.FailedEvent;
+import com.vaadin.v7.ui.Upload.FailedListener;
+import com.vaadin.v7.ui.Upload.Receiver;
+import com.vaadin.v7.ui.Upload.SucceededEvent;
+import com.vaadin.v7.ui.Upload.SucceededListener;
+
+public class TestFileUpload extends TestBase implements Receiver {
+
+ private Log log = new Log(5);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ @Override
+ protected void setup() {
+ Upload u = new Upload("Upload", new Upload.Receiver() {
+
+ @Override
+ public OutputStream receiveUpload(String filename,
+ String mimeType) {
+ return baos;
+ }
+ });
+ u.setId("UPL");
+ u.addFailedListener(new FailedListener() {
+
+ @Override
+ public void uploadFailed(FailedEvent event) {
+ String hash = DigestUtils.md5Hex(baos.toByteArray());
+
+ log.log("<span style=\"color: red;\">Upload failed. Name: "
+ + event.getFilename() + ", Size: " + baos.size()
+ + ", md5: " + hash + "</span>");
+ baos.reset();
+ }
+ });
+ u.addSucceededListener(new SucceededListener() {
+
+ @Override
+ public void uploadSucceeded(SucceededEvent event) {
+ String hash = DigestUtils.md5Hex(baos.toByteArray());
+ log.log("Upload finished. Name: " + event.getFilename()
+ + ", Size: " + baos.size() + ", md5: " + hash);
+ baos.reset();
+ }
+ });
+
+ addComponent(log);
+ addComponent(u);
+ }
+
+ @Override
+ public OutputStream receiveUpload(String filename, String MIMEType) {
+ getMainWindow().showNotification("Receiving upload");
+ return new ByteArrayOutputStream();
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 6465;
+ }
+
+ @Override
+ protected String getDescription() {
+ return "Creates and prints an MD5 hash of any uploaded file.";
+ }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/v7/tests/components/upload/TestFileUploadTest.java b/uitest/src/test/java/com/vaadin/v7/tests/components/upload/TestFileUploadTest.java
new file mode 100644
index 0000000000..d0bd71c590
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/v7/tests/components/upload/TestFileUploadTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.v7.tests.components.upload;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.internal.WrapsElement;
+import org.openqa.selenium.remote.DesiredCapabilities;
+import org.openqa.selenium.remote.LocalFileDetector;
+import org.openqa.selenium.remote.RemoteWebElement;
+
+import com.vaadin.testbench.elements.UploadElement;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class TestFileUploadTest extends MultiBrowserTest {
+
+ @Override
+ public List<DesiredCapabilities> getBrowsersToTest() {
+ // PhantomJS fails to upload files for unknown reasons
+ return getBrowsersExcludingPhantomJS();
+ }
+
+ @Test
+ public void testUploadAnyFile() throws Exception {
+ openTestURL();
+
+ File tempFile = createTempFile();
+ fillPathToUploadInput(tempFile.getPath());
+
+ getSubmitButton().click();
+
+ String expected = String.format(
+ "1. Upload finished. Name: %s, Size: %s, md5: %s",
+ tempFile.getName(), getTempFileContents().length(),
+ md5(getTempFileContents()));
+
+ String actual = getLogRow(0);
+ Assert.assertEquals("Upload log row does not match expected", expected,
+ actual);
+ }
+
+ private String md5(String string) throws NoSuchAlgorithmException {
+ byte[] digest = MessageDigest.getInstance("MD5")
+ .digest(string.getBytes());
+ BigInteger bigInt = new BigInteger(1, digest);
+ String hashtext = bigInt.toString(16);
+ return hashtext;
+ }
+
+ /**
+ * @return The generated temp file handle
+ * @throws IOException
+ */
+ private File createTempFile() throws IOException {
+ File tempFile = File.createTempFile("TestFileUpload", ".txt");
+ BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile));
+ writer.write(getTempFileContents());
+ writer.close();
+ tempFile.deleteOnExit();
+ return tempFile;
+ }
+
+ private String getTempFileContents() {
+ return "This is a test file!\nRow 2\nRow3";
+ }
+
+ private void fillPathToUploadInput(String tempFileName) throws Exception {
+ // create a valid path in upload input element. Instead of selecting a
+ // file by some file browsing dialog, we use the local path directly.
+ WebElement input = getInput();
+ setLocalFileDetector(input);
+ input.sendKeys(tempFileName);
+ }
+
+ private WebElement getSubmitButton() {
+ UploadElement upload = $(UploadElement.class).first();
+ WebElement submitButton = upload.findElement(By.className("v-button"));
+ return submitButton;
+ }
+
+ private WebElement getInput() {
+ return getDriver().findElement(By.className("gwt-FileUpload"));
+ }
+
+ private void setLocalFileDetector(WebElement element) throws Exception {
+ if (getRunLocallyBrowser() != null) {
+ return;
+ }
+
+ if (element instanceof WrapsElement) {
+ element = ((WrapsElement) element).getWrappedElement();
+ }
+ if (element instanceof RemoteWebElement) {
+ ((RemoteWebElement) element)
+ .setFileDetector(new LocalFileDetector());
+ } else {
+ throw new IllegalArgumentException(
+ "Expected argument of type RemoteWebElement, received "
+ + element.getClass().getName());
+ }
+ }
+}