Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * 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.haulmont.cuba.web.toolkit.ui; import com.haulmont.cuba.web.sys.WebJarResource; import com.haulmont.cuba.web.toolkit.ui.client.fileupload.CubaFileUploadClientRpc; import com.haulmont.cuba.web.toolkit.ui.client.fileupload.CubaFileUploadServerRpc; import com.haulmont.cuba.web.toolkit.ui.client.fileupload.CubaFileUploadState; import com.vaadin.server.*; import com.vaadin.server.communication.FileUploadHandler; import com.vaadin.ui.Component; import com.vaadin.ui.LegacyComponent; import com.vaadin.util.ReflectTools; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.OutputStream; import java.io.Serializable; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; @WebJarResource({ "jquery-ui:jquery-ui.min.js", "jquery-file-upload:jquery-fileupload.min.js", "jquery-file-upload:jquery-iframe-transport.min.js" }) public class CubaFileUpload extends CubaAbstractUploadComponent implements Component.Focusable, LegacyComponent { /** * The output of the upload is redirected to this receiver. */ protected Receiver receiver; protected boolean isUploading; protected long contentLength = -1; protected Map<String, String> mimeTypes = new HashMap<>(); protected boolean interrupted = false; /* * Handle to terminal via Upload monitors and controls the upload during it is being streamed. */ protected com.vaadin.server.StreamVariable streamVariable; public CubaFileUpload() { registerRpc(new CubaFileUploadServerRpc() { @Override public void fileUploaded(String fileName) { fireUploadSuccess(fileName, mimeTypes.get(fileName), contentLength); getRpcProxy(CubaFileUploadClientRpc.class).continueUploading(); } @Override public void fileSizeLimitExceeded(String fileName) { fireFileSizeLimitExceeded(fileName); } @Override public void fileExtensionNotAllowed(String fileName) { fireFileExtensionNotAllowed(fileName); } @Override public void queueUploadFinished() { // trigger UI update after uploading markAsDirty(); mimeTypes.clear(); fireQueueUploadFinished(); } }); } // set error handler for background upload thread protected void setUploadingErrorHandler() { setErrorHandler(event -> { //noinspection ThrowableResultOfMethodCallIgnored Throwable ex = event.getThrowable(); String rootCauseMessage = ExceptionUtils.getRootCauseMessage(ex); Logger log = LoggerFactory.getLogger(CubaFileUpload.class); if (StringUtils.contains(rootCauseMessage, "The multipart stream ended unexpectedly") || StringUtils.contains(rootCauseMessage, "Unexpected EOF read on the socket")) { log.warn("Unable to upload file, it seems upload canceled or network error occurred"); } else { log.error("Unexpected error in CubaFileUpload", ex); } if (isUploading) { endUpload(); } }); } protected void resetUploadingErrorHandler() { setErrorHandler(null); } @Override protected CubaFileUploadState getState() { return (CubaFileUploadState) super.getState(); } @Override protected CubaFileUploadState getState(boolean markAsDirty) { return (CubaFileUploadState) super.getState(markAsDirty); } @Override public int getTabIndex() { return getState(false).tabIndex; } @Override public void setTabIndex(int tabIndex) { if (getTabIndex() != tabIndex) { getState().tabIndex = tabIndex; } } /** * Returns the icon's alt text. * * @return String with the alt text */ public String getIconAlternateText() { return getState(false).iconAltText; } public void setIconAlternateText(String iconAltText) { if (!Objects.equals(getIconAlternateText(), iconAltText)) { getState().iconAltText = iconAltText; } } /** * Return HTML rendering setting * * @return <code>true</code> if the caption text is to be rendered as HTML, <code>false</code> otherwise */ public boolean isHtmlContentAllowed() { return getState(false).captionAsHtml; } /** * Set whether the caption text is rendered as HTML or not. You might need to re-theme button to allow higher * content than the original text style. * <p> * If set to true, the captions are passed to the browser as html and the developer is responsible for ensuring no * harmful html is used. If set to false, the content is passed to the browser as plain text. * * @param htmlContentAllowed <code>true</code> if caption is rendered as HTML, <code>false</code> otherwise */ public void setHtmlContentAllowed(boolean htmlContentAllowed) { if (isHtmlContentAllowed() != htmlContentAllowed) { getState().captionAsHtml = htmlContentAllowed; } } /** * Sets the component's icon and alt text. * <p> * An alt text is shown when an image could not be loaded, and read by assistive devices. * * @param icon the icon to be shown with the component's caption. * @param iconAltText String to use as alt text */ public void setIcon(Resource icon, String iconAltText) { super.setIcon(icon); getState().iconAltText = iconAltText; } public boolean isMultiSelect() { return getState(false).multiSelect; } public void setMultiSelect(boolean multiSelect) { if (isMultiSelect() != multiSelect) { getState().multiSelect = multiSelect; } } public String getUnableToUploadFileMessage() { return getState(false).unableToUploadFileMessage; } public void setUnableToUploadFileMessage(String message) { if (!Objects.equals(getUnableToUploadFileMessage(), message)) { getState().unableToUploadFileMessage = message; } } public String getProgressWindowCaption() { return getState(false).progressWindowCaption; } public void setProgressWindowCaption(String progressWindowCaption) { if (!Objects.equals(getProgressWindowCaption(), progressWindowCaption)) { getState().progressWindowCaption = progressWindowCaption; } } public String getCancelButtonCaption() { return getState(false).cancelButtonCaption; } public void setCancelButtonCaption(String cancelButtonCaption) { if (!Objects.equals(getCancelButtonCaption(), cancelButtonCaption)) { getState().cancelButtonCaption = cancelButtonCaption; } } @Override public String getAccept() { return getState(false).accept; } /** * Note: this is just a hint for browser, user may select files that do not meet this property * * @param accept mime types, comma separated */ @Override public void setAccept(String accept) { if (!Objects.equals(accept, getAccept())) { getState().accept = accept; } } public Set<String> getPermittedExtensions() { return getState(false).permittedExtensions; } public void setPermittedExtensions(Set<String> permittedExtensions) { getState().permittedExtensions = permittedExtensions; } public double getFileSizeLimit() { return getState(false).fileSizeLimit; } /** * @param fileSizeLimit file size limit in bytes */ public void setFileSizeLimit(long fileSizeLimit) { getState().fileSizeLimit = fileSizeLimit; } public Receiver getReceiver() { return receiver; } public void setReceiver(Receiver receiver) { this.receiver = receiver; } public Component getDropZone() { return (Component) getState(false).dropZone; } public void setDropZone(Component component) { if (getDropZone() != component) { getState().dropZone = component; } } public void setPasteZone(Component component) { if (getPasteZone() != component) { getState().pasteZone = component; } } public Component getPasteZone() { return (Component) getState(false).pasteZone; } public String getDropZonePrompt() { return getState(false).dropZonePrompt; } public void setDropZonePrompt(String dropZonePrompt) { if (!Objects.equals(getDropZonePrompt(), dropZonePrompt)) { getState().dropZonePrompt = dropZonePrompt; } } protected com.vaadin.server.StreamVariable getStreamVariable() { if (streamVariable == null) { streamVariable = new com.vaadin.server.StreamVariable() { private StreamingStartEvent lastStartedEvent; @Override public boolean listenProgress() { return false; } @Override public void onProgress(StreamingProgressEvent event) { } @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(); mimeTypes.put(event.getFileName(), event.getMimeType()); contentLength = event.getContentLength(); lastStartedEvent = event; if (hasInvalidExtension(event.getFileName())) { Logger log = LoggerFactory.getLogger(CubaFileUpload.class); log.warn("Unable to start upload. File extension is not allowed."); interruptUpload(); return; } double fileSizeLimit = getFileSizeLimit(); if (fileSizeLimit > 0 && event.getContentLength() > fileSizeLimit) { Logger log = LoggerFactory.getLogger(CubaFileUpload.class); log.warn( "Unable to start upload. File size limit exceeded, but client-side checks ignored."); interruptUpload(); return; // here client sends file to us bypassing client-side checks, just stop uploading } fireStarted(event.getFileName(), event.getMimeType()); } private boolean hasInvalidExtension(String name) { if (getPermittedExtensions() != null && !getPermittedExtensions().isEmpty()) { if (name.lastIndexOf(".") > 0) { String fileExtension = name.substring(name.lastIndexOf("."), name.length()); return !getPermittedExtensions().contains(fileExtension.toLowerCase()); } else { return true; } } return false; } @Override public void streamingFinished(StreamingEndEvent event) { endUpload(); } @Override public void streamingFailed(StreamingErrorEvent event) { Exception exception = event.getException(); if (exception instanceof FileUploadHandler.UploadInterruptedException) { endUpload(); } 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); } } }; } return streamVariable; } /** * Go into upload state. This is to prevent double uploading on same * component. * <p> * 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. */ protected void startUpload() { if (isUploading) { throw new IllegalStateException("uploading already started"); } isUploading = true; setUploadingErrorHandler(); } /** * 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. */ protected void interruptUpload() { if (isUploading) { interrupted = true; } } /** * Go into state where new uploading can begin. * <p> * Warning: this is an internal method used by the framework and should not be used by user of the Upload component. */ protected void endUpload() { isUploading = false; contentLength = -1; interrupted = false; markAsDirty(); resetUploadingErrorHandler(); } @Override public void paintContent(PaintTarget target) throws PaintException { // Post file to this stream variable target.addVariable(this, "uploadUrl", getStreamVariable()); } @Override public void changeVariables(Object source, Map<String, Object> variables) { } protected void fireStarted(String fileName, String MIMEType) { fireEvent(new StartedEvent(this, fileName, MIMEType, contentLength)); } protected void fireNoInputStream(String fileName, String MIMEType, long length) { fireEvent(new NoInputStreamEvent(this, fileName, MIMEType, length)); } protected void fireNoOutputStream(String fileName, String MIMEType, long length) { fireEvent(new NoOutputStreamEvent(this, fileName, MIMEType, length)); } protected void fireUploadInterrupted(String fileName, String MIMEType, long length, Exception e) { fireEvent(new FailedEvent(this, fileName, MIMEType, length, e)); } protected void fireUploadSuccess(String fileName, String MIMEType, long length) { fireEvent(new SucceededEvent(this, fileName, MIMEType, length)); } protected void fireFileSizeLimitExceeded(String fileName) { fireEvent(new FileSizeLimitExceededEvent(this, fileName)); } protected void fireFileExtensionNotAllowed(String fileName) { fireEvent(new FileExtensionNotAllowedEvent(this, fileName)); } protected void fireQueueUploadFinished() { fireEvent(new QueueFinishedEvent(this)); } /** * Interface that must be implemented by the upload receivers to provide the CubaFileUpload component an output * stream to write the uploaded data. */ 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. */ OutputStream receiveUpload(String fileName, String mimeType); } /** * CubaFileUpload.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. */ public static class FinishedEvent extends Component.Event { /** * Length of the received file. */ private final long contentLength; /** * 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 contentLength the contentLength of the received file. */ public FinishedEvent(CubaFileUpload source, String fileName, String MIMEType, long contentLength) { super(source); type = MIMEType; this.fileName = fileName; this.contentLength = contentLength; } /** * Uploads where the event occurred. * * @return the Source of the event. */ public CubaFileUpload getUpload() { return (CubaFileUpload) 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 contentLength of the file. * * @return the contentLength. */ public long getContentLength() { return contentLength; } } /** * CubaFileUpload.FailedEvent event is sent when the upload is received, but the reception is interrupted for some * reason. */ public static class FailedEvent extends FinishedEvent { private Exception reason = null; public FailedEvent(CubaFileUpload source, String fileName, String MIMEType, long length, Exception reason) { this(source, fileName, MIMEType, length); this.reason = reason; } public FailedEvent(CubaFileUpload 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; } } public static class FileSizeLimitExceededEvent extends Component.Event { private String fileName; /** * @param source the source of the file. * @param fileName the received file name. */ public FileSizeLimitExceededEvent(CubaFileUpload source, String fileName) { super(source); this.fileName = fileName; } public String getFileName() { return fileName; } } public static class FileExtensionNotAllowedEvent extends Component.Event { private String fileName; /** * @param source the source of the file. * @param fileName the received file name. */ public FileExtensionNotAllowedEvent(CubaFileUpload source, String fileName) { super(source); this.fileName = fileName; } public String getFileName() { return fileName; } } /** * FailedEvent that indicates that an output stream could not be obtained. */ public static class NoOutputStreamEvent extends FailedEvent { public NoOutputStreamEvent(CubaFileUpload source, String fileName, String MIMEType, long length) { super(source, fileName, MIMEType, length); } } /** * FailedEvent that indicates that an input stream could not be obtained. */ public static class NoInputStreamEvent extends FailedEvent { public NoInputStreamEvent(CubaFileUpload source, String fileName, String MIMEType, long length) { super(source, fileName, MIMEType, length); } } /** * CubaFileUpload.SucceededEvent event is sent when the upload is received successfully. */ public static class SucceededEvent extends FinishedEvent { public SucceededEvent(CubaFileUpload source, String fileName, String MIMEType, long length) { super(source, fileName, MIMEType, length); } } /** * CubaFileUpload.StartedEvent event is sent when the upload is started to received. */ public static class StartedEvent extends Component.Event { private final String fileName; private final String type; /** * Length of the received file. */ private final long contentLength; public StartedEvent(CubaFileUpload source, String fileName, String MIMEType, long contentLength) { super(source); this.fileName = fileName; type = MIMEType; this.contentLength = contentLength; } /** * Uploads where the event occurred. * * @return the Source of the event. */ public CubaFileUpload getUpload() { return (CubaFileUpload) 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 contentLength of the file that is being uploaded */ public long getContentLength() { return contentLength; } } /** * CubaFileUpload.StartedEvent event is sent when the queue upload is finished. */ public static class QueueFinishedEvent extends Component.Event { /** * Constructs a new event with the specified source component. * * @param source the source component of the event */ public QueueFinishedEvent(CubaFileUpload source) { super(source); } } /** * Receives the events when the upload starts. */ public interface StartedListener extends Serializable { /** * Upload has started. * * @param event the Upload started event. */ void uploadStarted(StartedEvent event); } /** * Receives the events when the uploads are ready. */ public interface FinishedListener extends Serializable { /** * Upload has finished. * * @param event the Upload finished event. */ void uploadFinished(FinishedEvent event); } public interface QueueFinishedListener extends Serializable { /** * Upload has finished. * * @param event the Upload finished event. */ void queueUploadFinished(QueueFinishedEvent event); } /** * Receives events when the uploads are finished, but unsuccessful. */ public interface FailedListener extends Serializable { /** * Upload has finished unsuccessfully. * * @param event the Upload failed event. */ void uploadFailed(FailedEvent event); } /** * Receives events when the uploads are successfully finished. */ public interface SucceededListener extends Serializable { /** * Upload successful. * * @param event the Upload successful event. */ void uploadSucceeded(SucceededEvent event); } /** * Receives events when the file size is greater than {@link #getFileSizeLimit()}. */ public interface FileSizeLimitExceededListener extends Serializable { void fileSizeLimitExceeded(FileSizeLimitExceededEvent e); } /** * Receives events when the file extension is not included in {@link #getPermittedExtensions()}. */ public interface FileExtensionNotAllowedListener extends Serializable { void fileExtensionNotAllowed(FileExtensionNotAllowedEvent e); } private static final Method UPLOAD_FINISHED_METHOD = ReflectTools.findMethod(FinishedListener.class, "uploadFinished", FinishedEvent.class); private static final Method QUEUE_UPLOAD_FINISHED_METHOD = ReflectTools.findMethod(QueueFinishedListener.class, "queueUploadFinished", QueueFinishedEvent.class); private static final Method UPLOAD_FAILED_METHOD = ReflectTools.findMethod(FailedListener.class, "uploadFailed", FailedEvent.class); private static final Method UPLOAD_STARTED_METHOD = ReflectTools.findMethod(StartedListener.class, "uploadStarted", StartedEvent.class); private static final Method UPLOAD_SUCCEEDED_METHOD = ReflectTools.findMethod(SucceededListener.class, "uploadSucceeded", SucceededEvent.class); private static final Method FILESIZE_LIMIT_EXCEEDED_METHOD = ReflectTools.findMethod( FileSizeLimitExceededListener.class, "fileSizeLimitExceeded", FileSizeLimitExceededEvent.class); private static final Method EXTENSION_NOT_ALLOWED_METHOD = ReflectTools.findMethod( FileExtensionNotAllowedListener.class, "fileExtensionNotAllowed", FileExtensionNotAllowedEvent.class); public void addStartedListener(StartedListener listener) { addListener(StartedEvent.class, listener, UPLOAD_STARTED_METHOD); } public void removeStartedListener(StartedListener listener) { removeListener(StartedEvent.class, listener, UPLOAD_STARTED_METHOD); } public void addFinishedListener(FinishedListener listener) { addListener(FinishedEvent.class, listener, UPLOAD_FINISHED_METHOD); } public void removeFinishedListener(FinishedListener listener) { removeListener(FinishedEvent.class, listener, UPLOAD_FINISHED_METHOD); } public void addFailedListener(FailedListener listener) { addListener(FailedEvent.class, listener, UPLOAD_FAILED_METHOD); } public void removeFailedListener(FailedListener listener) { removeListener(FailedEvent.class, listener, UPLOAD_FAILED_METHOD); } public void addSucceededListener(SucceededListener listener) { addListener(SucceededEvent.class, listener, UPLOAD_SUCCEEDED_METHOD); } public void removeSucceededListener(SucceededListener listener) { removeListener(SucceededEvent.class, listener, UPLOAD_SUCCEEDED_METHOD); } public void addFileSizeLimitExceededListener(FileSizeLimitExceededListener listener) { addListener(FileSizeLimitExceededEvent.class, listener, FILESIZE_LIMIT_EXCEEDED_METHOD); } public void addFileExtensionNotAllowedListener(FileExtensionNotAllowedListener listener) { addListener(FileExtensionNotAllowedEvent.class, listener, EXTENSION_NOT_ALLOWED_METHOD); } public void removeFileExtensionNotAllowedListener(FileExtensionNotAllowedListener listener) { removeListener(FileExtensionNotAllowedEvent.class, listener, EXTENSION_NOT_ALLOWED_METHOD); } public void removeFileSizeLimitExceededListener(FileSizeLimitExceededListener listener) { removeListener(FileSizeLimitExceededEvent.class, listener, FILESIZE_LIMIT_EXCEEDED_METHOD); } public void addQueueUploadFinishedListener(QueueFinishedListener listener) { addListener(QueueFinishedEvent.class, listener, QUEUE_UPLOAD_FINISHED_METHOD); } public void removeQueueUploadFinishedListener(QueueFinishedListener listener) { removeListener(QueueFinishedEvent.class, listener, QUEUE_UPLOAD_FINISHED_METHOD); } }