0

I have a FileCreator class that implements StreamResourceWriter interface and MainErrorHandler class that implements ErrorHandler. I'm using the MainErrorHandler class as a centralized Exception handler in my project which mostly logs the exception and shows a notification to the user. The problem is that StreamResourceWriter.accept() method runs in a non UI thread and when an Exception is thrown it is directed to the ErrorHandler which then fails to show a notification due to "IllegalStateException: UI instance is not available". Is there a way to show a notification window to the user from MainErrorHandler when FileCreator throws an error in accept() method?

Below FileCreator snippet.

public class FileCreator implements StreamResourceWriter {
    @Override
    public void accept(OutputStream stream, VaadinSession session) throws IOException {
        // Run in a non ui thread.
        // Writes to OutputStream but an Exception might be thrown during this process
    }
}

Below MainErrorHandler snippet.

/**
 * Centralized error handler
 */
public class MainErrorHandler implements ErrorHandler {
    private static final Logger log = LoggerFactory.getLogger(MainErrorHandler.class);
    @Override
    public void error(ErrorEvent event) {
        log.error("Error occurred", event.getThrowable());
        //Cannot show a notification if ErrorEvent came from FileCreator.
        //Will get an IllegalStateException: UI instance is not available.
        Notification.show("Error occurred");
        //Tried UI.getCurrent but it returns null if ErrorEvent came from FileCreator.
        UI.getCurrent();
    }
}

Using Vaadin 13.0.1.

Edit

One way to solve this issue is to pass UI reference to FileCreator directly. Below an example.

public class FileCreator implements StreamResourceWriter {
    private UI ui;
    //Pass UI reference directly
    public FileCreator(UI ui){
       this.ui = ui;                                                        
    }
    @Override
    public void accept(OutputStream stream, VaadinSession session) throws IOException {
       try{
        // Run in a non ui thread.
        // Writes to OutputStream but an Exception might be thrown during this process
       }catch(Exception e){
           //I don't like this since have to catch all exceptions and have to call ErrorHandeler directly with a UI reference. Also what if somewhere in code ErrorHandler is changed and is not of type MainErrorHandler.
           ((MainErrorHandler)VaadinSession.getCurrent().getErrorHandler()).error(e, ui);
       }
    }
}

As I said in comments I really don't like this approach either since I am forced to catch all Exceptions, have to cast ErrorHandler to MainErrorHandler and calling it directly.

Julius Koljonen
  • 223
  • 3
  • 9

1 Answers1

0

There is a way, but it's not perfect.

You can get all UI instances via VaadinSession.getCurrent().getUIs().

To filter out the inactive/detached UIs you can check if ui.getSession() returns a VaadinSession (so, not null). The JavaDoc of getSession says:

The method will return null if the UI is not currently attached to a VaadinSession.

Then you can invoke the access method on each of the UIs and create and show the notification inside the UI-context.

for(UI ui : VaadinSession.getCurrent().getUIs()) {
    // Filtering out detached/inactive UIs
    if (ui.getSession() != null) {
        ui.access(() -> {
            // create Notification here
        });
    }   

I said it's not perfect because you have to keep in mind that the user can have several UIs opened at the same time(e.g. multiple tabs).

froemijojo
  • 91
  • 1
  • 4
  • Hi froemijojo, thank you for your answer. I tried the approach you suggested and got it to work somewhat but there are two limitations that prevents me using it. #1 I get multiple notifications on the same tab somewhere around 4-5 notifications even when I have only one tab active and #2 it seems that I have to manually lock the session before executing the code snippet since otherwise I get IllegalArgumentException "VaadinSession not locked". I'll continue to investigate this approach and other approaches. – Julius Koljonen Mar 25 '19 at 09:43
  • @JuliusKoljonen i edited my answer, filtering out the detached/inactive UIs should help with locking the session explicitly, because the access method of an attached UI should do that already. Did this also help with the multiple Notification problem? – froemijojo Mar 25 '19 at 15:10
  • Hi @froemijojo, thanks again for your reply and edit! Your code example works now quite well: no multiple notifications in one tab and no locking issues but as you said if user has multiple tabs open the error notification is shown in all of the open tabs/UIs. I wish for a solution where the error notification is shown only in one UI from where the file download was started. Is there a way to get the UI reference from where occurred exception originated from in your solution? – Julius Koljonen Mar 26 '19 at 08:20
  • Hmmmm just realised that StreamResourceWriter's accept method is called without session lock and I get "IllegalStateException: Cannot access state in VaadinSession or UI without locking the session" when calling VaadinSession.getCurrent().getUIs(). That's why the interface has VaadinSession session as a parameter so that implementor can lock/unlock the session. Your code snippet works when a StreamResource is instantiated with InputStreamFactory instead of StreamResourceWriter. – Julius Koljonen Mar 26 '19 at 08:40
  • @JuliusKoljonen you could wrap the Exception into a custom Exception Type which also has a Reference to the UI of the filecreator and check inside your MainErrorHandler if it's that ExceptionType. But as far as i can tell you won't get around storing the UI instance at construction time inside the File Creator, otherwise there's no way you could know in which UI it was created. – froemijojo Mar 26 '19 at 11:18