Custom upload component implement handler (migrating from v14 to v25)

good morning, had a UploadResumable in vaadin 14
all the logic is in js (was even using null as Receiver) using GitHub - 23/resumable.js: A JavaScript library for providing multiple simultaneous, stable, fault-tolerant and resumable/restartable uploads via the HTML5 File API. · GitHub and firing {Started,Progress,Finished}Event as needed in order to execute the listenners that the client defined

we have to migrate to vaadin 25 and see that the addListener() methods are marked for removal, so wanted to know how to implement the custom UploadHandler and how to comunicate the start, progress, finish events from js code to it

You might wanna reevaluate that and go back to the official component. It was heavily improved over time. Your linked repository on the other hand looks kinda abandoned.

With UploadHandler, the lifecycle events happen through the handler instead of through the component itself. The built-in handlers support adding listeners through the whenStart, onProgress and whenComplete methods. Those are driven by the actual HTTP request of the upload rather than any events from JavaScript. You can also provide your own UploadHandler implementation and directly handle the HTTP request if you prefer full control over the whole process.

You might wanna reevaluate that and go back to the official component

I don’t see a resumable upload handler

The built-in handlers support adding listeners through the whenStart , onProgress and whenComplete methods

¿when would have onProgress been fired?

You can also provide your own UploadHandler implementation

¿how?

when the user selects a file, will go to the whenStart() method. there the stream is broken in chunks and each chunk is send to an url (this part is already done in resumable.js, ¿should rewrite it for java?)
each successful sent chunk will advance the progress, ¿how to inform to the component?
and then when all the chunk are uploaded the process is finished. ¿how to fire whenComplete?

One main architectural change between the old StreamReceiver API and the new UploadHandler API is that events are fired by the upload handler instead of by the component. This can be seen in a hello-world example that just reads the uploaded data into a byte array and then passes that array to application code:

var upload = new Upload(new InMemoryUploadHandler((metadata, bytes) -> {
    Notification.show("The last byte in " + metadata.fileName() + "  was " + bytes[bytes.length - 1]);
}));

The server-side component instance doesn’t need to know anything about ongoing uploads and the client-side counterpart already knowns all it needs to know.

The UploadHandler interface itself is just a simple request-handling method that is free to do whatever it wants as it receives requests. In your case, you might want to create an implementation that receives multiple requests and pieces them together into a single file before calling a success listener that was passed to the handler’s constructor.

public class CustomUpload
        extends TransferProgressAwareHandler<UploadEvent, CustomUpload>
    implements UploadHandler {

    @Override
    public void handleUploadRequest(UploadEvent event) throws IOException {
        // based on InMemoryUploadHandler
        this.setTransferUI(event.getUI());
        this.currentContext = this.getTransferContext(event);
        var listeners = this.getListeners();

        try (var input = event.getInputStream(); var chunk = new Chunk(this, this.chunkSize)) {
            TransferUtil.transfer(input, chunk, this.currentContext, listeners);
        } catch (IOException e) {
            this.notifyError(event, e);
            throw e;
        }
    }

    private class Chunk extends OutputStream {
        public Chunk(CCUploadHandlerSmall handler, int size) {
        }

        @Override
        public void write(int value) throws IOException {
            throw new IOException("1");
        }

        @Override
        public void write(byte[] values, int offset, int length) throws IOException {
            throw new IOException("2");
        }

        @Override
        public void flush() throws IOException {
            throw new IOException("3");
        }

        @Override
        public void close() throws IOException {
            throw new IOException("4");
        }
    }

    protected TransferContext getTransferContext(UploadEvent event) {
        // lovely to copy-paste this implementation
        return new TransferContext(
                event.getRequest(),
                event.getResponse(),
                event.getSession(),
                event.getFileName(),
                event.getOwningElement(),
                event.getFileSize());
    }
}

TransferUtil.transfer() (from vaadin) reads a chunk and send it to the stream write() function
some issues:

  • the output stream (Chunk) throws inmediately, but I see that the input stream needs to be fully consumed for the upload to end
  • no idea how to abort the upload, how to notify the java code that clicked the abort button
  • the upload fails because an exception, but can’t access the exception in the whenComplete() listener
  • no idea how to put a custom message in the web component

End in what way? The server will stop receiving more data when the input stream from the event is closed by the try-with-resources block. But the client might not notice that immediately due to buffers and latency.

No idea what abort button you’re referring to. If it’s on the client, then it would be natural to immediately abort the ongoing HTTP request from that end and then fire a DOM event so that UI logic can also know what’s happening.

The exception should be passed to the onError method of each registered TransferProgressListener. But there does indeed seem to be an omission where the exception isn’t also available through the TransferContext that you can get for the whenComplete shorthand handler. You can create a bug report at Issues · vaadin/flow · GitHub if you think it would be useful.

How did you do that previously?