diff options
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()); + } + } +} |