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...");
}
}
-
JSR 356 annotations are not @Inherited, and thus requires special attention by implementations. Problem already reported as WEBSOCKET_SPEC-229.
-
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'').
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.
// 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.
// 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)
@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;
}
}
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() {
.......
}
.......
@onopen
public void onOpen(final Session userSession) {
this.userSession = userSession;
}
.......
}
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?