vaadin / portlet

Portlet support for Vaadin Flow
https://vaadin.com
Other
2 stars 3 forks source link

Make Push work with Portlets #200

Open mshabarov opened 2 years ago

mshabarov commented 2 years ago

Vaadin Push functionality doesn't work with Vaadin 14 Portlet integration.

The following code used to enable Push in a portlet application:

public class MyPortlet extends VaadinPortlet<MyView> {

    @Override
    public WebComponentExporter<MyView> create() {
        return new MyExporter();
    }

    @Push
    private class MyExporter extends PortletWebComponentExporter {
        public MyExporter() {
            super(MyPortlet.this.getPortletTag());
        }
    }
}

doesn't work in both Apache Pluto and Liferay 7 (tested with 7.3) containers.

Screenshot 2022-02-16 at 14 59 14
[Refresh Thread: Equinox Container: 80949cc8-3195-4193-bd29-0c65469ba3b7] ERROR org.atmosphere.cpr.DefaultAsyncSupportResolver - Real error: Unable to configure jsr356 at that stage. ServerContainer is null
java.lang.IllegalStateException: Unable to configure jsr356 at that stage. ServerContainer is null
        at org.atmosphere.container.JSR356AsyncSupport.<init>(JSR356AsyncSupport.java:53)
        at org.atmosphere.container.JSR356AsyncSupport.<init>(JSR356AsyncSupport.java:42)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
        at org.atmosphere.cpr.DefaultAsyncSupportResolver.newCometSupport(DefaultAsyncSupportResolver.java:237)
        at org.atmosphere.cpr.DefaultAsyncSupportResolver.resolveWebSocket(DefaultAsyncSupportResolver.java:308)
        at org.atmosphere.cpr.DefaultAsyncSupportResolver.resolve(DefaultAsyncSupportResolver.java:294)
        at org.atmosphere.cpr.AtmosphereFramework.autoDetectContainer(AtmosphereFramework.java:2092)
        at org.atmosphere.cpr.AtmosphereFramework.init(AtmosphereFramework.java:914)
        at org.atmosphere.cpr.AtmosphereFramework.init(AtmosphereFramework.java:838)
        at com.vaadin.flow.server.communication.PushRequestHandler.initAtmosphere(PushRequestHandler.java:222)
        at com.vaadin.flow.server.communication.JSR356WebsocketInitializer.initAtmosphereForVaadinServlet(JSR356WebsocketInitializer.java:186)
        at com.vaadin.flow.server.communication.JSR356WebsocketInitializer.init(JSR356WebsocketInitializer.java:151)

Jsr356_exception.txt

and also with the failed request to vaadinPush-min.js:

image

In the Liferay case, the ServerContainer implementation cannot be found in the given ServletContext, here is a call sequence:

  1. Servlet container calls Vaadin's JSR356WebsocketInitializer::contextInitialized
  2. Then PushRequestHandler::initAtmosphere is called and the atmosphere framework is being customized
  3. Atmosphere framework is being initialized then via AtmosphereFramework::init
  4. Atmosphere framework tries to instantiate a JSR356AsyncSupport but throws because the servlerContainer is not found:

    ServerContainer container = (ServerContainer) ctx.getAttribute(ServerContainer.class.getName());
    
        if (container == null) {
            if (ctx.getServerInfo().contains("WebLogic")) {
                logger.error("{} must use JDK 1.8+ with WebSocket", ctx.getServerInfo());
            }
            throw new IllegalStateException("Unable to configure jsr356 at that stage. ServerContainer is null");
        }

    Liferay 7.3 provides a WebSocket implementation already, according to https://help.liferay.com/hc/en-us/articles/360018161191-Liferay-WebSocket-Whiteboard , but it seems the servlet context the atmosphere framework is looking into, has no server container, but it is available in the OSGi context, for instance if the Liferay puts the ServerContainer into one ServletContext object, but the portlet uses another object (this is my raw guess).

Vaadin OSGi integration has a similar Push issue, so the problem might be in OSGi specifics.

This ticket is an investigation ticket and doesn't contain any certain suggestions about the feature support implementation so far.

While it is an issue for a Vaadin Portlet and if it an option for you to replace server-to-client async updates in your project, you can use client-to-server async updates like shown below:

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        super.onAttach(attachEvent);
        attachEvent.getUI().setPollInterval(POLL_INTERVAL);
        pollListenerRegistration = attachEvent.getUI().addPollListener(event -> {
                 // do UI components updates when the client sends an async request to server
        });
    }

    @Override
    protected void onDetach(DetachEvent detachEvent) {
        if (pollListenerRegistration != null) {
            pollListenerRegistration.remove();
            pollListenerRegistration = null;
            detachEvent.getUI().setPollInterval(-1);
        }
        super.onDetach(detachEvent);
    }

    // do UI updates in the background thread:
    ui.access(() -> {
          // update UI components
          textField.setValue("foo");
    }); 
mcollovati commented 1 year ago

The issue was triaged and currently added to the backlog priority queue for further investigation

tltv commented 1 year ago

Running Liferay with -Dvaadin.disable.automatic.servlet.registration=true helps to get rid of some extra noise like reported in #222 when Push is disabled. Verified with simple test cases in main branch with Vaadin 23.3. Vaadin 14 has the same property so it should help with Vaadin portlet version 1.0.0 too.

tltv commented 1 year ago

Findings regarding enabling Push for portlets with main branch (Vaadin 23.3) and Liferay 7.4:

  1. As written in description, WebSocket init fails to IllegalStateException from JSR356AsyncSupport. Its a result from ServletContext#getAttribute(ServerContainer.class.getName()) call which ends up (through proxies and wrappers) to org.eclipse.equinox.http.servlet.internal.ServletContextAdaptor#getAttribute(String) which can't find the ServerContainer even though it is available in the 'real' ServletContext that this object wraps.

Could this be a bug in Equinox or maybe a missing OSGi configuration in portlet app? Needs more investigation to answer these. Forcing ServerContainer to be available for JSR356AsyncSupport lead to other error so this is not the only obstacle.

  1. WebSockets can be suppressed with org.atmosphere.websocket.suppressJSR356=true init parameter for the Atmosphere which will end up using Long Polling instead and initialization works without errors. Looks like Push with Long polling could work easier. It would make sense to start to make Long polling work first.

  2. Portlet's client side is not setup with correct paths regarding Push client (vaadinPush-min.js), so this may need changes in Flow's Push Java API to make it possible to change paths easier for portlets. This issue makes it currently harder to verify if Push is setup correctly and works when suppressJSR356 is enabled.

  3. There's also open question if we really need to create Vaadin servlet instance at all for portlet just to get Flow's Push feature to work or should we write completely new initialization code for the portlet's Push instead and disable automatic Vaadin servlet creation permanently.