From 90cb0c5ccf0e463ddc6111366218ce33d9695fbf Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Fri, 16 May 2025 13:59:24 +0300 Subject: [PATCH 01/12] feat: Use UploadHandler in documentation fixes #4304 --- articles/components/upload/file-handling.adoc | 230 ++++++++++++------ articles/flow/advanced/stream-resources.adoc | 26 -- articles/flow/advanced/upload-resources.adoc | 34 +++ 3 files changed, 195 insertions(+), 95 deletions(-) delete mode 100644 articles/flow/advanced/stream-resources.adoc create mode 100644 articles/flow/advanced/upload-resources.adoc diff --git a/articles/components/upload/file-handling.adoc b/articles/components/upload/file-handling.adoc index ebae6bd2a8..d388d3d6ef 100644 --- a/articles/components/upload/file-handling.adoc +++ b/articles/components/upload/file-handling.adoc @@ -14,118 +14,210 @@ The uploading of files may be handled either with Vaadin Flow using Java, or wit == Handling Uploaded Files in Flow -The Java Flow Upload component provides an API to handle directly uploaded file data, without having to set up an endpoint or a servlet. It uses a [classname]`Receiver` implementation to write the incoming file data into an [classname]`OutputStream`. +The Java Flow Upload component provides an API to handle directly uploaded file data, without having to set up an endpoint or a servlet. It uses a [classname]`UploadHandler` implementation to handle the incoming file data. -The following built-in implementations of [classname]`Receiver` are available: +The [classname]`UploadHandler` is a functional interface that only requires the [methodame]`handleUploadRequest(UploadEvent)` to be implemented for receiving data or use one of the built-in implementations. -- [classname]`MemoryBuffer`; -- [classname]`MultiFileMemoryBuffer`; -- [classname]`FileBuffer`; and -- [classname]`MultiFileBuffer`. +The following built-in implementations of [classname]`UploadHandler` are available: -These are described in the sub-sections that follow. +- [classname]`InMemoryUploadHandler`, stores uploaded files in memory +- [classname]`FileUploadHandler`, stores uploaded files to the file system +- [classname]`TemporaryFileUploadHandler`, stores uploaded files to temporary files +These are described in the sub-sections that follow. +All of the build-in implementations extend [classname]`TransferProgressAwareHandler` and support <>. -=== MemoryBuffer +=== InMemoryUploadHandler -The [classname]`MemoryBuffer` can handle a single file upload at once. It does so by writing the file data into an in-memory buffer. +The [classname]`InMemoryUploadHandler` stores uploaded files in memory as byte arrays. -Using [classname]`MemoryBuffer` will configure the component so that only a single file can be selected. +The handler is given a `successhandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload. +The `successhandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and a `byte[]` that contains the actual data from the upload. [source,java] ---- -MemoryBuffer memoryBuffer = new MemoryBuffer(); +InMemoryUploadHandler inMemoryHandler = UploadHandler.inMemory( + (metadata, data) -> { + // Get other information about the file. + String fileName = event.getFileName(); + String mimeType = event.getMIMEType(); + long contentLength = event.getContentLength(); -Upload upload = new Upload(memoryBuffer); -upload.addSucceededListener(event -> { - // Read the data from the buffer. - InputStream fileData = memoryBuffer.getInputStream(); + // Do something with the file data... + }); - // Get other information about the file. - String fileName = event.getFileName(); - String mimeType = event.getMIMEType(); - long contentLength = event.getContentLength(); - // Do something with the file data... -}); +Upload upload = new Upload(inMemoryHandler); ---- +=== FileUploadHandler + +The [classname]`FileUploadHandler` stores the upload as a file to the file system. -=== MultiFileMemoryBuffer +The handler is given a `successhandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload and a [classname]`FileFactory`. +The `successhandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and the actual [classname]`File` where the data was written. -The [classname]`MultiFileMemoryBuffer` is the same as [classname]`MemoryBuffer`, but it can handle multiple file uploads at once. It writes the file data into a set of in-memory buffers. +The file used is defined with [classname]`FileFactory` that gets the filename for the upload and should return the [classname]`File` to store the data into. [source,java] ---- -MultiFileMemoryBuffer multiFileMemoryBuffer = new MultiFileMemoryBuffer(); - -Upload upload = new Upload(multiFileMemoryBuffer); -upload.addSucceededListener(event -> { - // Determine which file was uploaded - String fileName = event.getFileName(); - - // Read the data for that specific file. - InputStream fileData = multiFileMemoryBuffer - .getInputStream(fileName); - - // Get other information about the file. - String mimeType = event.getMIMEType(); - long contentLength = event.getContentLength(); +SerializableBiConsumer successHandler = (metadata, file) -> + System.out.printf("File saved to: %s%n", file.getAbsolutePath()); +FileFactory fileFactory = (fileName) -> new File("/path/to/uploads", fileName); +FileUploadHandler fileHandler = UploadHandler.toFile(successHandler, fileFactory); - // Do something with the file data... -}); +Upload upload = new Upload(fileHandler); ---- +=== TemporaryFileUploadHandler -=== FileBuffer +The [classname]`TemporaryFileUploadHandler` works the same as [classname]`FileUploadHandler`, except that instead of taking in a [classname]`FileFactory`, it stores the data into a temporary file in the system default temporary-file directory. -The [classname]`FileBuffer` can handle a single file upload at once. It saves the file on the file system, in the current working directory of the Java application. - -Using [classname]`FileBuffer` will configure the component so that only a single file can be selected. +The handler is given a `successhandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload and a [classname]`FileFactory`. +The `successhandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and the actual [classname]`File` where the data was written. [source,java] ---- -FileBuffer fileBuffer = new FileBuffer(); +SerializableBiConsumer successHandler = (metadata, file) -> + System.out.printf("File saved to: %s%n", file.getAbsolutePath()); -Upload upload = new Upload(fileBuffer); -upload.addSucceededListener(event -> { - // Get information about the file that was - // written to the file system. - FileData savedFileData = fileBuffer.getFileData(); - String absolutePath = savedFileData.getFile().getAbsolutePath(); +TemporaryFileUploadHandler temporaryFileHandler = UploadHandler.toTempFile(successHandler); - System.out.printf("File saved to: %s%n", absolutePath); -}); +Upload upload = new Upload(temporaryFileHandler); ---- +=== Custom UploadHandler Implementations -=== MultiFileBuffer +For more advanced use cases, you can provide custom implementations for [classname]`UploadHandler`. +You might do this, for example, to save files into a specific directory, or to upload them to cloud storage. -The [classname]`MultiFileBuffer` works the same as [classname]`FileBuffer`, except that it can handle multiple file uploads at once. For each file, it saves the file on the file system, in the current working directory of the Java application. +[classname]`UploadHandler` is a [annotationname]`FuncionalInterface` so it can be implemented or just be a lambda expression. [source,java] ---- -MultiFileBuffer fileBuffer = new MultiFileBuffer(); - -Upload upload = new Upload(fileBuffer); -upload.addSucceededListener(event -> { - // Determine which file was uploaded successfully - String uploadFileName = event.getFileName(); - - // Get information for that specific file - FileData savedFileData = multiFileBuffer - .getFileData(uploadFileName); - String absolutePath = savedFileData.getFile().getAbsolutePath(); +public class CustomUploadHandler implements UploadHandler { + @Override + public void handleUploadRequest(UploadEvent event) { + try (InputStream inputStream = event.getInputStream()) { + // Process the uploaded file + // ... + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void responseHandled(boolean success, VaadinResponse response) { + // Set your own custom response value for success/fail by overriding this method. + // Default responses are 200 ok for success and 500 internal server error for failure + } +} +---- - System.out.printf("File saved to: %s%n", absolutePath); -}); +[source,java] +---- +UploadHandler uploadHandler = (event) -> { + try (InputStream inputStream = event.getInputStream()) { + // Process the uploaded file + // ... + } catch (IOException e) { + throw new UncheckedIOException(e); + } +}; ---- +== Upload Progress Tracking -=== Custom Receiver Implementations +To add progress tracking to a custom upload handler, you can extend [classname]`TransferProgressAwareHandler`: -For more advanced use cases, you can provide custom implementations for [classname]`Receiver` or [classname]`MultiFileReceiver`. You might do this, for example, to save files into a specific directory, or to upload them to cloud storage. +[source,java] +---- +public class CustomUploadHandler + extends TransferProgressAwareHandler + implements UploadHandler { + @Override + public void handleUploadRequest(UploadEvent event) { + try (InputStream inputStream = event.getInputStream(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) { + // Use the TransferProgressListener.transfer method to copy the data + // to notify progress listeners + TransferProgressListener.transfer( + inputStream, + outputStream, + getTransferContext(event), + getListeners()); + // Process the data + byte[] data = outputStream.toByteArray(); + // ... + } catch (IOException e) { + // Notify listeners of the error + notifyError(event, e); + throw new UncheckedIOException(e); + } + } + @Override + protected TransferContext getTransferContext(UploadEvent event) { + return new TransferContext( + event.getRequest(), + event.getResponse(), + event.getSession(), + event.getFileName(), + event.getOwningElement(), + event.getFileSize()); + } +} +---- +With this you can add a [classname]`TransferProgressListener` to the handler or use the shortcut methods for handling specific progress events. +[[add-progress-listener]] +.Add a TransferProgressListener +[source,java] +---- +TransferProgressListener progressListener = new TransferProgressListener() { + @Override + public void onStart(TransferContext context) { + System.out.println("Upload started"); + } + + @Override + public void onProgress(TransferContext context, + long transferredBytes, long totalBytes) { + double percentage = (double) transferredBytes / totalBytes * 100; + System.out.printf("Upload progress: %.2f%%\n", percentage); + } + + @Override + public void onError(TransferContext context, IOException reason) { + System.out.println("Upload failed"); + } + + @Override + public void onComplete(TransferContext context, + long transferredBytes) { + System.out.println("Upload completed successfully"); + } + }; + +uploadHandler.addTransferProgressListener(progressListener); +---- + +.Add progress handlers through shortcuts +[source,java] +---- +CustomUploadHandler uploadHandler = new CustomUploadHandler() + .whenStart(() -> System.out.println("Upload started")) + .onProgress((transferredBytes, totalBytes) -> { + double percentage = (double) transferredBytes / totalBytes * 100; + System.out.printf("Upload progress: %.2f%%\n", percentage); + }) + .whenComplete((success) -> { + if (success) { + System.out.println("Upload completed successfully"); + } else { + System.out.println("Upload failed"); + } + }); +---- == Handling Upload Requests in Lit and React diff --git a/articles/flow/advanced/stream-resources.adoc b/articles/flow/advanced/stream-resources.adoc deleted file mode 100644 index 7dff4317d8..0000000000 --- a/articles/flow/advanced/stream-resources.adoc +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: StreamReceiver -page-title: How to use stream resources in Vaadin Flow -description: Using StreamReceiver to receive an incoming data stream. -meta-description: Learn to generate and serve dynamic content using stream resources in Vaadin Flow. -order: 130 ---- - - -= Using StreamReceiver to Receive an Incoming Data Stream - -To receive an upload from the client, you need to register a [classname]`StreamReceiver` that accepts a URL to handle receiving an upload stream. - -To create a [classname]`StreamReceiver`, you first need to create a [classname]`StreamVariable` that monitors and controls terminal upload during the time it's being streamed. - -Then the stream can be registered through the [classname]`Element` API. - -[source,java] ----- -StreamReceiver streamReceiver = new StreamReceiver( - getElement().getNode(), "upload", getStreamVariable()); -getElement().setAttribute("target", streamReceiver); ----- - - -[discussion-id]`E14BB11E-3462-484B-A2A8-394B842A79DC` diff --git a/articles/flow/advanced/upload-resources.adoc b/articles/flow/advanced/upload-resources.adoc new file mode 100644 index 0000000000..006c9f461a --- /dev/null +++ b/articles/flow/advanced/upload-resources.adoc @@ -0,0 +1,34 @@ +--- +title: UploadHandler +page-title: How to use UploadHandler in Vaadin Flow +description: Using UploadHandler to receive an incoming data stream. +meta-description: Learn to receive content using ElementRequestHandler in Vaadin Flow. +order: 130 +--- + + += Using UploadHandler to Receive an Incoming Data Stream + +To receive an upload from the client, you need to register an [classname]`UploadHandler` that accepts a URL to handle receiving an upload stream. + +To create an [classname]`UploadHandler`, you need implement the [methodname]`handleUploadRequest` to handle the upload or use one of the provided UploadHandlers. +The existing [classname]`UploadHandler` implementations are accessible through the static methods in [classname]`UploadHandler`, e.g. `UploadHandler.toTempFile(SerializableBiConsumer successHandler)` + +[NOTE] +When receiving a multipart upload the [methodname]`handleUploadRequest` will be called separately for each file in the upload. + +Then the handler can be registered through the [classname]`Element` API after registering it to the [classname]`StreamResourceRegistration`. + +[source,java] +---- +List outputFiles = new ArrayList<>(1); +UploadHandler uploadHandler = UploadHandler.toTempFile((uploadMetadata, file) -> outputFiles.add(file)); + +StreamRegistration streamRegistration = VaadinSession.getCurrent() + .getResourceRegistry() + .registerResource(uploadHandler, getElement()); + +getElement().setAttribute("target", streamRegistration); +---- + +[discussion-id]`4482D0BF-E742-4FEA-A888-854B758FE576` From 0740ed9facb357f716ed6660b01d3df13d2b8951 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Fri, 16 May 2025 15:01:27 +0300 Subject: [PATCH 02/12] Fix some code samples to use UploadHandler fix sample code in asciidoc --- articles/components/upload/file-handling.adoc | 23 +++++++---- .../demo/component/upload/UploadAllFiles.java | 7 +++- .../upload/UploadAutoUploadDisabled.java | 7 +++- .../demo/component/upload/UploadBasic.java | 23 ++++++----- .../component/upload/UploadDragAndDrop.java | 13 ++++-- .../component/upload/UploadDropLabel.java | 7 +++- .../component/upload/UploadFileBuffer.java | 41 +++++++------------ 7 files changed, 65 insertions(+), 56 deletions(-) diff --git a/articles/components/upload/file-handling.adoc b/articles/components/upload/file-handling.adoc index d388d3d6ef..8067bbd6de 100644 --- a/articles/components/upload/file-handling.adoc +++ b/articles/components/upload/file-handling.adoc @@ -31,19 +31,20 @@ All of the build-in implementations extend [classname]`TransferProgressAwareHand The [classname]`InMemoryUploadHandler` stores uploaded files in memory as byte arrays. -The handler is given a `successhandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload. -The `successhandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and a `byte[]` that contains the actual data from the upload. +The handler is given a `successHandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload. +The `successHandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and a `byte[]` that contains the actual data from the upload. [source,java] ---- InMemoryUploadHandler inMemoryHandler = UploadHandler.inMemory( (metadata, data) -> { // Get other information about the file. - String fileName = event.getFileName(); - String mimeType = event.getMIMEType(); - long contentLength = event.getContentLength(); + String fileName = metadata.fileName(); + String mimeType = metadata.contentType(); + long contentLength = metadata.contentLength(); // Do something with the file data... + // processFile(data, fileName); }); @@ -54,8 +55,8 @@ Upload upload = new Upload(inMemoryHandler); The [classname]`FileUploadHandler` stores the upload as a file to the file system. -The handler is given a `successhandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload and a [classname]`FileFactory`. -The `successhandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and the actual [classname]`File` where the data was written. +The handler is given a `successHandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload and a [classname]`FileFactory`. +The `successHandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and the actual [classname]`File` where the data was written. The file used is defined with [classname]`FileFactory` that gets the filename for the upload and should return the [classname]`File` to store the data into. @@ -73,8 +74,8 @@ Upload upload = new Upload(fileHandler); The [classname]`TemporaryFileUploadHandler` works the same as [classname]`FileUploadHandler`, except that instead of taking in a [classname]`FileFactory`, it stores the data into a temporary file in the system default temporary-file directory. -The handler is given a `successhandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload and a [classname]`FileFactory`. -The `successhandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and the actual [classname]`File` where the data was written. +The handler is given a `successHandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload and a [classname]`FileFactory`. +The `successHandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and the actual [classname]`File` where the data was written. [source,java] ---- @@ -102,6 +103,8 @@ public class CustomUploadHandler implements UploadHandler { // Process the uploaded file // ... } catch (IOException e) { + // The thrown exception will be caught and will fire + // a responseHandled(false, response) method call. throw new UncheckedIOException(e); } } @@ -121,6 +124,8 @@ UploadHandler uploadHandler = (event) -> { // Process the uploaded file // ... } catch (IOException e) { + // The thrown exception will be caught and will fire + // a responseHandled(false, response) method call. throw new UncheckedIOException(e); } }; diff --git a/src/main/java/com/vaadin/demo/component/upload/UploadAllFiles.java b/src/main/java/com/vaadin/demo/component/upload/UploadAllFiles.java index 2ce6affe28..de4662a361 100644 --- a/src/main/java/com/vaadin/demo/component/upload/UploadAllFiles.java +++ b/src/main/java/com/vaadin/demo/component/upload/UploadAllFiles.java @@ -7,14 +7,17 @@ import com.vaadin.flow.component.upload.Upload; import com.vaadin.flow.component.upload.receivers.MultiFileMemoryBuffer; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.streams.InMemoryUploadHandler; +import com.vaadin.flow.server.streams.UploadHandler; @Route("upload-all-files") public class UploadAllFiles extends Div { public UploadAllFiles() { - MultiFileMemoryBuffer buffer = new MultiFileMemoryBuffer(); + InMemoryUploadHandler inMemoryHandler = UploadHandler.inMemory( + (metadata, data) -> {}); // tag::snippet[] - Upload upload = new Upload(buffer); + Upload upload = new Upload(inMemoryHandler); upload.setAutoUpload(false); Button uploadAllButton = new Button("Upload All Files"); diff --git a/src/main/java/com/vaadin/demo/component/upload/UploadAutoUploadDisabled.java b/src/main/java/com/vaadin/demo/component/upload/UploadAutoUploadDisabled.java index 4fe9a1ad87..e9436cb2e9 100644 --- a/src/main/java/com/vaadin/demo/component/upload/UploadAutoUploadDisabled.java +++ b/src/main/java/com/vaadin/demo/component/upload/UploadAutoUploadDisabled.java @@ -5,14 +5,17 @@ import com.vaadin.flow.component.upload.Upload; import com.vaadin.flow.component.upload.receivers.MultiFileMemoryBuffer; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.streams.InMemoryUploadHandler; +import com.vaadin.flow.server.streams.UploadHandler; @Route("upload-auto-upload-disabled") public class UploadAutoUploadDisabled extends Div { public UploadAutoUploadDisabled() { - MultiFileMemoryBuffer buffer = new MultiFileMemoryBuffer(); + InMemoryUploadHandler inMemoryHandler = UploadHandler.inMemory( + (metadata, data) -> {}); // tag::snippet[] - Upload upload = new Upload(buffer); + Upload upload = new Upload(inMemoryHandler); upload.setAutoUpload(false); UploadExamplesI18N i18n = new UploadExamplesI18N(); diff --git a/src/main/java/com/vaadin/demo/component/upload/UploadBasic.java b/src/main/java/com/vaadin/demo/component/upload/UploadBasic.java index d28b425a5a..4982098d9f 100644 --- a/src/main/java/com/vaadin/demo/component/upload/UploadBasic.java +++ b/src/main/java/com/vaadin/demo/component/upload/UploadBasic.java @@ -5,6 +5,8 @@ import com.vaadin.flow.component.upload.Upload; import com.vaadin.flow.component.upload.receivers.MultiFileMemoryBuffer; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.streams.InMemoryUploadHandler; +import com.vaadin.flow.server.streams.UploadHandler; import java.io.InputStream; @@ -13,16 +15,17 @@ public class UploadBasic extends Div { public UploadBasic() { // tag::snippet[] - MultiFileMemoryBuffer buffer = new MultiFileMemoryBuffer(); - Upload upload = new Upload(buffer); - - upload.addSucceededListener(event -> { - String fileName = event.getFileName(); - InputStream inputStream = buffer.getInputStream(fileName); - - // Do something with the file data - // processFile(inputStream, fileName); - }); + InMemoryUploadHandler inMemoryHandler = UploadHandler.inMemory( + (metadata, data) -> { + // Get other information about the file. + String fileName = metadata.fileName(); + String mimeType = metadata.contentType(); + long contentLength = metadata.contentLength(); + + // Do something with the file data... + // processFile(data, fileName); + }); + Upload upload = new Upload(inMemoryHandler); // end::snippet[] upload.getElement() // hidden-source-line diff --git a/src/main/java/com/vaadin/demo/component/upload/UploadDragAndDrop.java b/src/main/java/com/vaadin/demo/component/upload/UploadDragAndDrop.java index 1b56730373..5bad7d758a 100644 --- a/src/main/java/com/vaadin/demo/component/upload/UploadDragAndDrop.java +++ b/src/main/java/com/vaadin/demo/component/upload/UploadDragAndDrop.java @@ -7,19 +7,24 @@ import com.vaadin.flow.component.upload.Upload; import com.vaadin.flow.component.upload.receivers.MultiFileMemoryBuffer; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.streams.InMemoryUploadHandler; +import com.vaadin.flow.server.streams.UploadHandler; @Route("upload-drag-and-drop") public class UploadDragAndDrop extends Div { public UploadDragAndDrop() { - MultiFileMemoryBuffer buffer1 = new MultiFileMemoryBuffer(); - MultiFileMemoryBuffer buffer2 = new MultiFileMemoryBuffer(); + + InMemoryUploadHandler inMemoryHandler1 = UploadHandler.inMemory( + (metadata, data) -> {}); + InMemoryUploadHandler inMemoryHandler2 = UploadHandler.inMemory( + (metadata, data) -> {}); // tag::snippet[] - Upload dropEnabledUpload = new Upload(buffer1); + Upload dropEnabledUpload = new Upload(inMemoryHandler1); dropEnabledUpload.setDropAllowed(true); - Upload dropDisabledUpload = new Upload(buffer2); + Upload dropDisabledUpload = new Upload(inMemoryHandler2); dropDisabledUpload.setDropAllowed(false); // end::snippet[] diff --git a/src/main/java/com/vaadin/demo/component/upload/UploadDropLabel.java b/src/main/java/com/vaadin/demo/component/upload/UploadDropLabel.java index 723c3cf7ee..38df085c58 100644 --- a/src/main/java/com/vaadin/demo/component/upload/UploadDropLabel.java +++ b/src/main/java/com/vaadin/demo/component/upload/UploadDropLabel.java @@ -10,14 +10,17 @@ import com.vaadin.flow.component.upload.Upload; import com.vaadin.flow.component.upload.receivers.MultiFileMemoryBuffer; import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.streams.InMemoryUploadHandler; +import com.vaadin.flow.server.streams.UploadHandler; @Route("upload-drop-label") public class UploadDropLabel extends Div { public UploadDropLabel() { - MultiFileMemoryBuffer buffer = new MultiFileMemoryBuffer(); + InMemoryUploadHandler inMemoryHandler = UploadHandler.inMemory( + (metadata, data) -> {}); // tag::snippet[] - Upload upload = new Upload(buffer); + Upload upload = new Upload(inMemoryHandler); Span dropLabel = createDropLabel(); Icon dropIcon = VaadinIcon.CLOUD_UPLOAD_O.create(); diff --git a/src/main/java/com/vaadin/demo/component/upload/UploadFileBuffer.java b/src/main/java/com/vaadin/demo/component/upload/UploadFileBuffer.java index 77cad59b31..8d1e259e4e 100644 --- a/src/main/java/com/vaadin/demo/component/upload/UploadFileBuffer.java +++ b/src/main/java/com/vaadin/demo/component/upload/UploadFileBuffer.java @@ -1,43 +1,30 @@ package com.vaadin.demo.component.upload; +import java.io.File; + import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.upload.Upload; import com.vaadin.flow.component.upload.receivers.FileBuffer; import com.vaadin.flow.component.upload.receivers.FileData; import com.vaadin.flow.component.upload.receivers.MultiFileBuffer; +import com.vaadin.flow.server.streams.FileUploadHandler; +import com.vaadin.flow.server.streams.InMemoryUploadHandler; +import com.vaadin.flow.server.streams.UploadHandler; public class UploadFileBuffer extends Div { public UploadFileBuffer() { // tag::snippet[] /* Example for FileBuffer */ - FileBuffer fileBuffer = new FileBuffer(); - Upload singleFileUpload = new Upload(fileBuffer); - - singleFileUpload.addSucceededListener(event -> { - // Get information about the file that was written to the file - // system - FileData savedFileData = fileBuffer.getFileData(); - String absolutePath = savedFileData.getFile().getAbsolutePath(); - - System.out.printf("File saved to: %s%n", absolutePath); - }); - - /* Example for MultiFileBuffer */ - MultiFileBuffer multiFileBuffer = new MultiFileBuffer(); - Upload multiFileUpload = new Upload(multiFileBuffer); - - multiFileUpload.addSucceededListener(event -> { - // Determine which file was uploaded successfully - String uploadFileName = event.getFileName(); - // Get information for that specific file - FileData savedFileData = multiFileBuffer - .getFileData(uploadFileName); - String absolutePath = savedFileData.getFile().getAbsolutePath(); - - System.out.printf("File saved to: %s%n", absolutePath); - }); + /* Handles both single and multifile upload */ + FileUploadHandler fileHandler = UploadHandler.toFile( + (metadata, file) -> { + System.out.printf("File saved to: %s%n", + file.getAbsolutePath()); + }, fileName -> new File(System.getProperty("java.io.tmpdir"), + fileName)); + Upload fileUpload = new Upload(fileHandler); // end::snippet[] - add(singleFileUpload, multiFileUpload); + add(fileUpload); } } From 87f5197df5c13ade2618f6fe224396de399d17df Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Mon, 19 May 2025 11:08:27 +0300 Subject: [PATCH 03/12] Remove addTransferProgressListener vaadin/flow#21443 makes the method protected so it shouldn't be mentioned in the doc. --- articles/components/upload/file-handling.adoc | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/articles/components/upload/file-handling.adoc b/articles/components/upload/file-handling.adoc index 8067bbd6de..96e6f380d8 100644 --- a/articles/components/upload/file-handling.adoc +++ b/articles/components/upload/file-handling.adoc @@ -172,41 +172,9 @@ public class CustomUploadHandler } } ---- -With this you can add a [classname]`TransferProgressListener` to the handler or use the shortcut methods for handling specific progress events. +With this you can add the fluent methods to add handling for specific progress events. [[add-progress-listener]] -.Add a TransferProgressListener -[source,java] ----- -TransferProgressListener progressListener = new TransferProgressListener() { - @Override - public void onStart(TransferContext context) { - System.out.println("Upload started"); - } - - @Override - public void onProgress(TransferContext context, - long transferredBytes, long totalBytes) { - double percentage = (double) transferredBytes / totalBytes * 100; - System.out.printf("Upload progress: %.2f%%\n", percentage); - } - - @Override - public void onError(TransferContext context, IOException reason) { - System.out.println("Upload failed"); - } - - @Override - public void onComplete(TransferContext context, - long transferredBytes) { - System.out.println("Upload completed successfully"); - } - }; - -uploadHandler.addTransferProgressListener(progressListener); ----- - -.Add progress handlers through shortcuts [source,java] ---- CustomUploadHandler uploadHandler = new CustomUploadHandler() From 4758609947ee970f57bcb0d72f3a4ff42585536c Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Tue, 20 May 2025 11:00:34 +0300 Subject: [PATCH 04/12] Add note about single-file receivers --- articles/components/upload/file-handling.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/articles/components/upload/file-handling.adoc b/articles/components/upload/file-handling.adoc index 96e6f380d8..5b1b792976 100644 --- a/articles/components/upload/file-handling.adoc +++ b/articles/components/upload/file-handling.adoc @@ -27,6 +27,10 @@ The following built-in implementations of [classname]`UploadHandler` are availab These are described in the sub-sections that follow. All of the build-in implementations extend [classname]`TransferProgressAwareHandler` and support <>. +[NOTE] +The [classname]`Receiver` had implementations for single and multiple file upload. +[classname]`UploadHandler` handles both version so `Upload.setMaxFiles(1)` needs to be called manually for single-file uploads. + === InMemoryUploadHandler The [classname]`InMemoryUploadHandler` stores uploaded files in memory as byte arrays. From 8e077958911b4fd44c0a1ee6efc72dc20dd7458e Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Wed, 21 May 2025 11:39:21 +0300 Subject: [PATCH 05/12] Small fixes to some explanations. --- articles/components/upload/file-handling.adoc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/articles/components/upload/file-handling.adoc b/articles/components/upload/file-handling.adoc index 5b1b792976..3efc46126b 100644 --- a/articles/components/upload/file-handling.adoc +++ b/articles/components/upload/file-handling.adoc @@ -14,9 +14,9 @@ The uploading of files may be handled either with Vaadin Flow using Java, or wit == Handling Uploaded Files in Flow -The Java Flow Upload component provides an API to handle directly uploaded file data, without having to set up an endpoint or a servlet. It uses a [classname]`UploadHandler` implementation to handle the incoming file data. +The Java Flow Upload component provides an API to handle directly uploaded file data, without having to set up an endpoint or a servlet. It uses an [classname]`UploadHandler` implementation to handle the incoming file data. -The [classname]`UploadHandler` is a functional interface that only requires the [methodame]`handleUploadRequest(UploadEvent)` to be implemented for receiving data or use one of the built-in implementations. +[classname]`UploadHandler` is a functional interface that only requires the [methodame]`handleUploadRequest(UploadEvent)` to be implemented for receiving data or use one of the built-in implementations. The following built-in implementations of [classname]`UploadHandler` are available: @@ -35,7 +35,8 @@ The [classname]`Receiver` had implementations for single and multiple file uploa The [classname]`InMemoryUploadHandler` stores uploaded files in memory as byte arrays. -The handler is given a `successHandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload. +The handler is given a `successHandler` of type `SerializableBiConsumer successHandler`. +This `successHandler` is called for each successful uploaded file separately. The `successHandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and a `byte[]` that contains the actual data from the upload. [source,java] @@ -59,7 +60,8 @@ Upload upload = new Upload(inMemoryHandler); The [classname]`FileUploadHandler` stores the upload as a file to the file system. -The handler is given a `successHandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload and a [classname]`FileFactory`. +The handler is given a `successHandler` of type `SerializableBiConsumer successHandler` and a [classname]`FileFactory`. +This `successHandler` is called for each successful uploaded file separately. The `successHandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and the actual [classname]`File` where the data was written. The file used is defined with [classname]`FileFactory` that gets the filename for the upload and should return the [classname]`File` to store the data into. @@ -78,7 +80,8 @@ Upload upload = new Upload(fileHandler); The [classname]`TemporaryFileUploadHandler` works the same as [classname]`FileUploadHandler`, except that instead of taking in a [classname]`FileFactory`, it stores the data into a temporary file in the system default temporary-file directory. -The handler is given a `successHandler` of type `SerializableBiConsumer successHandler` that is called for each successful upload and a [classname]`FileFactory`. +The handler is given a `successHandler` of type `SerializableBiConsumer successHandler`. +This `successHandler` is called for each successful uploaded file separately. The `successHandler` callback contains [classname]`UploadMetadata` with information on the uploaded file and the actual [classname]`File` where the data was written. [source,java] From 149b48c66a8da62b13b94d6326afc81a9a8ea8d3 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Thu, 22 May 2025 14:10:17 +0300 Subject: [PATCH 06/12] More code samples. Update according to changes. fix typos --- articles/components/upload/file-handling.adoc | 69 +++++++++++++++++-- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/articles/components/upload/file-handling.adoc b/articles/components/upload/file-handling.adoc index 3efc46126b..bb4131004e 100644 --- a/articles/components/upload/file-handling.adoc +++ b/articles/components/upload/file-handling.adoc @@ -22,7 +22,7 @@ The following built-in implementations of [classname]`UploadHandler` are availab - [classname]`InMemoryUploadHandler`, stores uploaded files in memory - [classname]`FileUploadHandler`, stores uploaded files to the file system -- [classname]`TemporaryFileUploadHandler`, stores uploaded files to temporary files +- [classname]`TemporaryFileUploadHandler`, stores uploaded files to temporary filestly even when These are described in the sub-sections that follow. All of the build-in implementations extend [classname]`TransferProgressAwareHandler` and support <>. @@ -97,9 +97,9 @@ Upload upload = new Upload(temporaryFileHandler); === Custom UploadHandler Implementations For more advanced use cases, you can provide custom implementations for [classname]`UploadHandler`. -You might do this, for example, to save files into a specific directory, or to upload them to cloud storage. +You might do this, for example, to upload files to cloud storage or to load data into memory first, validate and save to file. -[classname]`UploadHandler` is a [annotationname]`FuncionalInterface` so it can be implemented or just be a lambda expression. +[classname]`UploadHandler` is a [annotationname]`FunctionalInterface` so it can be implemented or just be a lambda expression. [source,java] ---- @@ -140,6 +140,64 @@ UploadHandler uploadHandler = (event) -> { == Upload Progress Tracking +The built-in implementations support [classname]`TransferProgressListeners` which can be added through the fluent API directly to the handler for specific events + +[[add-progress-listener]] +[source,java] +---- +UploadHandler.toTempFile( + (metadata, file) -> System.out.printf("File saved to: %s%n", + file.getAbsolutePath())) + .whenStart(() -> System.out.println("Upload started")) + .onProgress((transferredBytes, totalBytes) -> { + double percentage = (double) transferredBytes / totalBytes * 100; + System.out.printf("Upload progress: %.2f%%\n", percentage); + }).whenComplete((success) -> { + if (success) { + System.out.println("Upload completed successfully"); + } else { + System.out.println("Upload failed"); + } + }); +---- + +or giving a TransferProgressListener through the factory methods as a parameter. + +[source,java] +---- +TransferProgressListener progressListener = new TransferProgressListener() { + @Override + public void onStart(TransferContext context) { + Assert.assertEquals(165000, context.contentLength()); + Assert.assertEquals("download", context.fileName()); + invocations.add("onStart"); + } + + @Override + public void onProgress(TransferContext context, + long transferredBytes, long totalBytes) { + double percentage = (double) transferredBytes / totalBytes * 100; + System.out.printf("Upload progress: %.2f%%\n", percentage); + } + + @Override + public void onComplete(TransferContext context, + long transferredBytes) { + System.out.println("Upload completed successfully"); + } + + @Override + public void onError(TransferContext context, + IOException reason) { + System.out.println("Upload failed"); + } + }; + +UploadHandler.toTempFile( + (metadata, file) -> System.out.printf("File saved to: %s%n", + file.getAbsolutePath()), progressListener); +---- + To add progress tracking to a custom upload handler, you can extend [classname]`TransferProgressAwareHandler`: [source,java] @@ -151,9 +209,9 @@ public class CustomUploadHandler public void handleUploadRequest(UploadEvent event) { try (InputStream inputStream = event.getInputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) { - // Use the TransferProgressListener.transfer method to copy the data + // Use the TransferUtil.transfer method to copy the data // to notify progress listeners - TransferProgressListener.transfer( + TransferUtil.transfer( inputStream, outputStream, getTransferContext(event), @@ -181,7 +239,6 @@ public class CustomUploadHandler ---- With this you can add the fluent methods to add handling for specific progress events. -[[add-progress-listener]] [source,java] ---- CustomUploadHandler uploadHandler = new CustomUploadHandler() From 398b9a3defcefff3ae2178691581e69f1a27d085 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Fri, 23 May 2025 09:49:24 +0300 Subject: [PATCH 07/12] add samples to upload-resources document --- articles/flow/advanced/upload-resources.adoc | 90 +++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/articles/flow/advanced/upload-resources.adoc b/articles/flow/advanced/upload-resources.adoc index 006c9f461a..be81ff35ac 100644 --- a/articles/flow/advanced/upload-resources.adoc +++ b/articles/flow/advanced/upload-resources.adoc @@ -28,7 +28,95 @@ StreamRegistration streamRegistration = VaadinSession.getCurrent() .getResourceRegistry() .registerResource(uploadHandler, getElement()); -getElement().setAttribute("target", streamRegistration); +getElement().setAttribute("target", streamRegistration.getResource()); ---- +The element can be a form on the client that sends file data using form submission: + +[source,html] +---- +