]> source.dussan.org Git - vaadin-framework.git/commitdiff
Add Upload to compatibility packages
authorPekka Hyvönen <pekka@vaadin.com>
Thu, 6 Oct 2016 13:19:53 +0000 (16:19 +0300)
committerVaadin Code Review <review@vaadin.com>
Fri, 7 Oct 2016 11:53:56 +0000 (11:53 +0000)
Step 1 / 2 of adding a new Upload Component.

Change-Id: I137d7952a540db18a616da4a944a2a44f27435da

compatibility-client/src/main/java/com/vaadin/v7/client/ui/AbstractLegacyComponentConnector.java [new file with mode: 0644]
compatibility-client/src/main/java/com/vaadin/v7/client/ui/VUpload.java [new file with mode: 0644]
compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadConnector.java [new file with mode: 0644]
compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategy.java [new file with mode: 0644]
compatibility-client/src/main/java/com/vaadin/v7/client/ui/upload/UploadIFrameOnloadStrategyIE.java [new file with mode: 0644]
compatibility-server/src/main/java/com/vaadin/v7/ui/Upload.java [new file with mode: 0644]
compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadClientRpc.java [new file with mode: 0644]
compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadServerRpc.java [new file with mode: 0644]
compatibility-shared/src/main/java/com/vaadin/v7/shared/ui/upload/UploadState.java [new file with mode: 0644]
uitest/src/main/java/com/vaadin/v7/tests/components/upload/TestFileUpload.java [new file with mode: 0644]
uitest/src/test/java/com/vaadin/v7/tests/components/upload/TestFileUploadTest.java [new file with mode: 0644]

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 (file)
index 0000000..a9fe473
--- /dev/null
@@ -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 (file)
index 0000000..1610879
--- /dev/null
@@ -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 (file)
index 0000000..eefbe60
--- /dev/null
@@ -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 (file)
index 0000000..c01fce1
--- /dev/null
@@ -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 (file)
index 0000000..a40abfa
--- /dev/null
@@ -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 (file)
index 0000000..d3bc4b1
--- /dev/null
@@ -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 (file)
index 0000000..0dbdcdf
--- /dev/null
@@ -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 (file)
index 0000000..a50f53b
--- /dev/null
@@ -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 (file)
index 0000000..2a6cb0e
--- /dev/null
@@ -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 (file)
index 0000000..fc430b4
--- /dev/null
@@ -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 (file)
index 0000000..d0bd71c
--- /dev/null
@@ -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());
+        }
+    }
+}