Search code examples
jakarta-eejsf-2websocketview-scope

Share data between JSF @ViewScoped and WebSocket @ServerEndpoint


I'm using JSR356 web sockets and want to add some functions and values to my Server End Point class instead of creating another ManagedBean.

Also i'd like to keep bean properties between requests, so i annotated my Server End Point with annotation @ViewScoped. And now it looks like this:

@Named
@ViewScoped
@ServerEndpoint(value = "/session", encoders = ChatMessageEncoder.class, decoders = ChatMessageDecoder.class)
public class ChatEndpoint implements Serializable {

    @EJB
    private LanguageHelper languageHelper;
    private String language;

    public void filterByLanguage() {
        if (language == null)
            language = "US";
    }

    @OnOpen
    public void open(final Session session) throws IOException, EncodeException {
        // ...
    }

    @OnMessage
    public void onMessage(final Session session, final ChatMessage chatMessage) {
        // ...
    }

    @OnClose
    public void onClose(Session session) throws IOException {
        // ...
    }

    // getters & setters
}

On my view xhtml I'm trying to update property language using ajax.

 <h:form>
    <h:outputLabel value="Select languages you want to practice" for="languages"/>
    <h:selectOneMenu value="#{chatEndpoint.language}">
        <f:selectItems value="#{chatEndpoint.languageHelper.languages}" var="l"
                       itemLabel="#{l.language}" itemValue="#{l.code}"/>
        <f:ajax event="change" listener="#{chatEndpoint.filterByLanguage}" render="@form" execute="@this"/>
    </h:selectOneMenu>
</h:form>

At first when ajax invokes method filterByLanguage, then value is set to field language, but after, when I invoke method annotated with @OnOpen through javascript then language is null. I thought that viewsocoped should keep state between requests. Could anyone explain such behavior? Thanks in advance!


Solution

  • You basically ended up with 2 independently created instances of the class. One as CDI managed bean via @Named and another one as JSR356 websocket endpoint via @ServerEndpoint. It did not end up as a single shared instance as you seemed to expect. Those two do not know anything about each other and won't share anything.

    Then there's a second problem: the @ServerEndpoint has no notion whatsoever about the current JSF view state. This information is nowhere available in the WS request. Closest what you can get is the HTTP session. How to obtain the HttpSession in a @ServerEndpoint is in detail elaborated in this answer: Accessing ServletContext and HttpSession in @OnMessage of a JSR-356 @ServerEndpoint.

    You probably already know that you can access attributes of the HttpSession in JSF side via ExternalContext#getSessionMap(). If you generate some unique token in JSF side via e.g. UUID.randomUUID().toString() and use this as session attribute key and then pass along via the JSF view to the websocket as URL path or query parameter, then the websocket can just make use of it to find the associated data in the shared HTTP session.