Skip to content

Instantly share code, notes, and snippets.

@brunoborges
Last active December 16, 2015 07:24
Show Gist options
  • Save brunoborges/aa2c3827e01ed82d43ff to your computer and use it in GitHub Desktop.
Save brunoborges/aa2c3827e01ed82d43ff to your computer and use it in GitHub Desktop.

CDI and JSR 356 WebSockets

Objective: Auto-connect WebSockets on client-side (Java SE) application with CDI and Interceptors.

Problem: Because CDI implementations may provide a proxied bean, some things may not work as expected. See below for code and explanations.

STEP 1: Define a ws client with @ClientEndpoint, and the server-endpoint location with an Interceptor annotation (value is @Nonbinding)

@Dependent
@ClientEndpoint
@AutoConnectServerEndpoint("ws://echo.websocket.org")
public EchoWSClient {

  @OnOpen
  public void onOpen(Session p) {
    try {
      p.getBasicRemote().sendText("Hello!");
    } catch (IOException e) {}
  }

  @OnMessage
  public void onMessage(String message) {
    System.out.println(String.format("%s %s", "Received message: ", message));
  }
}

STEP 2: Create an interceptor to auto-connect this object when @Inject'ed.

@Interceptor
@AutoConnectServerEndpoint("")
public class WebSocketAutoConnectInterceptor {

    @Inject // injected by a CDI producer with ContainerProvider.getWebSocketContainer()
    private WebSocketContainer wsContainer;

    private final Map<Object, Session> openSessions = Collections.synchronizedMap(new HashMap<>());

    @AroundConstruct
    public void aroundConstructor(InjectionContext ic) {
        ic.proceed(); // proceed with construction to get target object
        Object client = ic.getTarget();

        // because of WEBSOCKET_SPEC-229 we can't do this check.
        // @CliendEndpoint is not @Inherited.
        /*
         * if (client.getClass().isAnnotationPresent(ClientEndpoint.class) == false) {
         * throw new IllegalArgumentException("Bean intercepted is not a @CliendEndpoint");
         * }
         */
        AutoConnectServerEndpoint annotation = client
                .getClass()
                .getDeclaredAnnotation(AutoConnectServerEndpoint.class);
        String serverURI = annotation.value();

        Session session;
        try {
            session = wsContainer.connectToServer(client, URI.create(serverURI));
            openSessions.put(client, session);
        } catch (DeploymentException | IOException ex) {
        }
    }

    @PreDestroy
    public void disconnect(InvocationContext invocationContext) {
        System.out.println("wsclient interceptor disconnect() called");
        try {
            Session session = openSessions.get(invocationContext.getTarget());
            if (session.isOpen()) {
                session.close();
            }
        } catch (IOException ex) {
            Logger.getLogger(WebSocketAutoConnectInterceptor.class.getName())
                    .log(Level.SEVERE, null, ex);
        }
    }

}

STEP 3: Inject the object in a another bean and it should work, right?

@ApplicationScoped
public class EchoSampleApplication {

    @Inject
    private EchoWSClient echo;
    
    public void sendMessage() {
        echo.send("Echoing...");
    }

}

PROBLEMS FOUND HERE

  1. JSR 356 annotations are not @Inherited, and thus requires special attention by implementations. Problem already reported as WEBSOCKET_SPEC-229.

  2. The @AroundConstruct method accesses the intercepted object client but this is a proxy. Both implementations (Tyrus and Undertow) fail in connecting the object (see call to ''connectToServer'').

EXPERIMENT of @Inherited annotations in JSR 356

I tweaked the JSR 356 API by modifying the annotations to be @Inherited (all of them). Tyrus then succeeded in finding @ClientEndpoint in the proxy class. But later it failed when looking for the @OnOpen/@OnMessage methods, because the proxy class doesn't have them.

WHY UNDERTOW FAILED

// Interceptor connect() method called
Sep 03, 2014 9:50:59 PM fx.javaee.web.ws.WebSocketClientInterceptor lambda$connect$1
SEVERE: null
javax.websocket.DeploymentException: UT003026: class fx.javaee.samples.billpayment.PaymentsWebSocketClient$Proxy$_$$_WeldSubclass is not a valid client endpoint type
	at io.undertow.websockets.jsr.ServerWebSocketContainer.connectToServer(ServerWebSocketContainer.java:135)
	at fx.javaee.web.ws.WebSocketClientInterceptor.lambda$connect$1(WebSocketClientInterceptor.java:48)
	at fx.javaee.web.ws.WebSocketClientInterceptor$$Lambda$64/1101930191.accept(Unknown Source)
	at java.util.Optional.ifPresent(Optional.java:159)
	at fx.javaee.web.ws.WebSocketClientInterceptor.connect(WebSocketClientInterceptor.java:43)

Undertow failed similarly to Tyrus because CDI (Weld) provided a proxied object, not the actual object.

WHY TYRUS FAILED (even with @Inherited annotations)

// Interceptor connect() method called
Sep 03, 2014 10:00:23 PM org.glassfish.tyrus.core.AnnotatedEndpoint onError
INFO: Unhandled exception in endpoint fx.javaee.samples.billpayment.PaymentsWebSocketClient$Proxy$_$$_WeldSubclass.
java.lang.NoSuchMethodError: fx.javaee.samples.billpayment.PaymentsWebSocketClient.onOpen$$super(Ljavax/websocket/Session;)V
	at fx.javaee.samples.billpayment.PaymentsWebSocketClient$Proxy$_$$_WeldSubclass.onOpen$$super(Unknown Source)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at org.jboss.weld.util.reflection.Reflections.invokeAndUnwrap(Reflections.java:414)
	at org.jboss.weld.bean.proxy.CombinedInterceptorAndDecoratorStackMethodHandler.invoke(CombinedInterceptorAndDecoratorStackMethodHandler.java:66)
	at fx.javaee.samples.billpayment.PaymentsWebSocketClient$Proxy$_$$_WeldSubclass.onOpen(Unknown Source)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

NECESSARY CLASSES TO RUN THIS

@Inherited
@InterceptorBinding
@Retention(RUNTIME)
@Target({TYPE})
public @interface AutoConnectServerEndpoint {
  @Nonbinding public String value();
}
@Dependent
public class WebSocketClientProducer {

    private WebSocketContainer client;

    @Produces
    public WebSocketContainer create() {
        if (client == null) {
            client = ContainerProvider.getWebSocketContainer();
        }
        return client;
    }

}
@marabacioglu
Copy link

Hi Bruno, I think I am experiencing the problem you described. Stack trace is below:
09:18:38,408 ERROR [stderr](default task-34) javax.websocket.DeploymentException: UT003026: class tr.com.frekansbilisim.aurora.view.BorsaEmirController$Proxy$_$$WeldSubclass is not a valid client endpoint type
09:18:38,408 ERROR [stderr](default task-34) at io.undertow.websockets.jsr.ServerWebSocketContainer.connectToServer(ServerWebSocketContainer.java:136)
09:18:38,408 ERROR [stderr](default task-34) at tr.com.frekansbilisim.aurora.view.BorsaEmirController.init(BorsaEmirController.java:101)
09:18:38,408 ERROR [stderr](default task-34) at tr.com.frekansbilisim.aurora.view.BorsaEmirController$Proxy$
$$WeldSubclass.init(Unknown Source)
09:18:38,408 ERROR [stderr](default task-34) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
09:18:38,408 ERROR [stderr](default task-34) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
09:18:38,408 ERROR [stderr](default task-34) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
09:18:38,408 ERROR [stderr](default task-34) at java.lang.reflect.Method.invoke(Method.java:497)
09:18:38,408 ERROR [stderr](default task-34) at org.jboss.weld.interceptor.proxy.SimpleInterceptionChain.interceptorChainCompleted(SimpleInterceptionChain.java:52)
09:18:38,409 ERROR [stderr](default task-34) at org.jboss.weld.interceptor.chain.AbstractInterceptionChain.invokeNextInterceptor(AbstractInterceptionChain.java:83)
09:18:38,409 ERROR [stderr](default task-34) at org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.executeInterception(InterceptorMethodHandler.java:48)
09:18:38,409 ERROR [stderr](default task-34) at org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.invoke(InterceptorMethodHandler.java:41)
09:18:38,409 ERROR [stderr](default task-34) at org.jboss.weld.bean.proxy.CombinedInterceptorAndDecoratorStackMethodHandler.invoke(CombinedInterceptorAndDecoratorStackMethodHandler.java:53)
09:18:38,409 ERROR [stderr](default task-34) at tr.com.frekansbilisim.aurora.view.BorsaEmirController$Proxy$
$$WeldSubclass.init(Unknown Source)
09:18:38,409 ERROR [stderr](default task-34) at tr.com.frekansbilisim.aurora.view.BorsaEmirController$Proxy$
$$_WeldClientProxy.init(Unknown Source)
09:18:38,409 ERROR [stderr](default task-34) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
09:18:38,409 ERROR [stderr](default task-34) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
09:18:38,409 ERROR [stderr](default task-34) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
09:18:38,409 ERROR [stderr](default task-34) at java.lang.reflect.Method.invoke(Method.java:497)
09:18:38,410 ERROR [stderr](default task-34) at com.sun.el.parser.AstValue.invoke(AstValue.java:275)
09:18:38,410 ERROR [stderr](default task-34) at com.sun.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:304)
09:18:38,410 ERROR [stderr](default task-34) at org.jboss.weld.util.el.ForwardingMethodExpression.invoke(ForwardingMethodExpression.java:40)
09:18:38,410 ERROR [stderr](default task-34) at org.jboss.weld.el.WeldMethodExpression.invoke(WeldMethodExpression.java:50)
09:18:38,410 ERROR [stderr](default task-34) at org.jboss.weld.util.el.ForwardingMethodExpression.invoke(ForwardingMethodExpression.java:40)
09:18:38,410 ERROR [stderr](default task-34) at org.jboss.weld.el.WeldMethodExpression.invoke(WeldMethodExpression.java:50)
09:18:38,410 ERROR [stderr](default task-34) at com.sun.faces.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:105)
09:18:38,410 ERROR [stderr](default task-34) at com.sun.faces.facelets.tag.jsf.core.DeclarativeSystemEventListener.processEvent(EventHandler.java:128)
09:18:38,410 ERROR [stderr](default task-34) at javax.faces.component.UIComponent$ComponentSystemEventListenerAdapter.processEvent(UIComponent.java:2585)
09:18:38,410 ERROR [stderr](default task-34) at javax.faces.event.SystemEvent.processListener(SystemEvent.java:108)
09:18:38,411 ERROR [stderr](default task-34) at javax.faces.event.ComponentSystemEvent.processListener(ComponentSystemEvent.java:118)
09:18:38,411 ERROR [stderr](default task-34) at com.sun.faces.application.ApplicationImpl.processListeners(ApplicationImpl.java:2187)
09:18:38,411 ERROR [stderr](default task-34) at com.sun.faces.application.ApplicationImpl.invokeComponentListenersFor(ApplicationImpl.java:2135)
09:18:38,411 ERROR [stderr](default task-34) at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:289)
09:18:38,411 ERROR [stderr](default task-34) at com.sun.faces.application.ApplicationImpl.publishEvent(ApplicationImpl.java:247)
09:18:38,411 ERROR [stderr](default task-34) at org.jboss.as.jsf.injection.weld.ForwardingApplication.publishEvent(ForwardingApplication.java:299)
09:18:38,411 ERROR [stderr](default task-34) at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:107)
09:18:38,411 ERROR [stderr](default task-34) at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
09:18:38,411 ERROR [stderr](default task-34) at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:219)
09:18:38,412 ERROR [stderr](default task-34) at javax.faces.webapp.FacesServlet.service(FacesServlet.java:647)
09:18:38,412 ERROR [stderr](default task-34) at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85)
09:18:38,412 ERROR [stderr](default task-34) at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:61)
09:18:38,412 ERROR [stderr](default task-34) at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
09:18:38,412 ERROR [stderr](default task-34) at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
09:18:38,412 ERROR [stderr](default task-34) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25)
09:18:38,412 ERROR [stderr](default task-34) at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:113)
09:18:38,412 ERROR [stderr](default task-34) at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:56)
09:18:38,412 ERROR [stderr](default task-34) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25)
09:18:38,412 ERROR [stderr](default task-34) at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:45)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:61)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:58)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:70)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:76)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25)
09:18:38,413 ERROR [stderr](default task-34) at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:240)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:227)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:73)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:146)
09:18:38,413 ERROR [stderr](default task-34) at io.undertow.server.Connectors.executeRootHandler(Connectors.java:177)
09:18:38,414 ERROR [stderr](default task-34) at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:727)
09:18:38,414 ERROR [stderr](default task-34) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
09:18:38,414 ERROR [stderr](default task-34) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
09:18:38,414 ERROR [stderr](default task-34) at java.lang.Thread.run(Thread.java:745)

I can share the code with you. As a summary it is a CDI Bean and the relevant part of the code is below:

@ClientEndpoint
@nAmed("borsaEmirController")
@ViewScoped
public class BorsaEmirController extends BaseCrudController {
...
@OverRide
public void init() {

    if (FacesContext.getCurrentInstance().isPostback()) {
        return;
    }
    super.init();
    try {
        String fixWebSocket = "ws://127.0.0.1:8190/fixinitiator";
        URI webSocketUri = new URI(fixWebSocket);
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
        container.connectToServer(this, webSocketUri);
    } catch (URISyntaxException | DeploymentException | IOException e) {
        e.printStackTrace();
    }

.......
}

.......
@onopen
public void onOpen(final Session userSession) {
this.userSession = userSession;
}

@OnClose
public void onClose(final Session userSession, final CloseReason reason) {
    this.userSession = null;
}

@OnMessage
public void onMessage(final String message) {
    System.out.println(message);
}

.......
}

I have some other classes that work well with the same WebSocket Endpoint. The ones that work well are not proxied. I don't see any significant difference in the code to cause this problem. You mention that "CDI implementations may provide a proxied bean" . What do you mean with "may"? Is there something beyond our control that we should consider as random? And even more important, do you have a solution?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment