A little context
Instances: there is a unique Session instance per client-server pair i.e. one instance of Session is created for a each client which connects to the WebSocket server endpoint. In short, the number of unique Session instances is equal to number of connected clients
Source: https://abhirockzz.gitbooks.io/java-websocket-api-handbook/content/lifecycle_and_concurrency_semantics.html
For more details: https://tyrus-project.github.io/documentation/1.13.1/index/lifecycle.html
A suggestion is to use a static
variables like
// @ApplicationScope
@ServerEndpoint("/item")
public class ws{
// something like
private static final Set<javax.websocket.Session> ALL_SESSIONS = new HashSet<>();
// ...
}
An example can be found here. It's an option but I do not think it solves your injection issue.
Another option is to leverage the javax.websocket.Session#getOpenedSessions()
method such as this chat example. But once again, it does not solve the injection issue.
Your example
You are using both websocket and REST. As I understand, the flow is:
- User A, B, C are connected
- User A submits a request to
citas/cita
and receives the REST response
- At the same time, A, B, C receive a websocket notification
So, as you wrote, on one hand, you have
@Path("citas")
// ...
public class CitaResource{
// ...
}
and
// @ApplicationScope -> commented as irrelevant in your situation
@ServerEndpoint("/item")
public class ws{
// ...
}
In the example, there is one instance of CitaResource
when user A made the request and three instances of ws
as A, B, C are connected. However, you were right about the injection: you need to have something injected in CitaResource
but you need a bean that is always available and as you noticed, websocket instances are not a good option and which session the container must inject?
A websocket sessions handler
The solution is to use an application scoped bean to handle all the existing sessions. I got it from Oracle tutorial. It goes like this:
// com.softcase.citasmanager.websocket.SessionHandler
@ApplicatedScoped
@Named // optional
public class MySessionHandler{
private final Set<Session> ALL_SESSIONS;
// or use a map if you need to identify the
// the session by a key. This example uses Set
// private final Map<String, Session> ALL_SESSIONS;
public MySessionHandler(){
ALL_SESSIONS = new HashSet<>();
}
// manage sessions
public void addSession(Session session){
this.ALL_SESSIONS.add(session);
}
public void removeSession(Session session){
this.ALL_SESSIONS.remove(session);
}
// send messages to all instances:
public void sendMessage(String message){
this.ALL_SESSIONS.stream()
// optional
.filter(s -> s.isOpen())
// or whatever method you want to send a message
.forEach( s -> s.getBasicRemote().sendText(message);
}
// or if you want to target a specific session
// hence my questions in comments
public void sendMessage(String message, String target){
this.ALL_SESSIONS..stream()
// identity the target session
.filter(s -> s.equals(target))
// optional
.filter(s -> s.isOpen())
.forEach( s -> s.getBasicRemote().sendText(message);
}
}
Note:
- I optionally check that the stored session is still opened.
isOpen()
is not mandatory but it might avoid some errors
- Think the session handler as the "captain": it knows everything about the websocket sessions whereas the sessions themselves do not know about each other.
However, you need to adapt your endpoint to make the session handler efficient:
// com.softcase.citasmanager.websocket.WsCita
@ServerEndpoint
public class WsCita{
// there is no need to declare the session as attribute
// private Session session;
// ApplicatedScoped so always defined
@Inject
private MySessionHandler handler;
@OnOpen
public void open(Session session){
handler.addSession(session); // "Aye cap'tain, reporting from duty!"
// your stuff
}
@OnClose
public void close(Session session, CloseReason closeReason){
handler.removeSession(session); // "Cya cap'tain, that's all for today!"
// your stuff
}
// your OnMessage and other stuff here
}
Now we have set our websocket architecture, what now?
- You have one instance of
WsCita
per client. At any time, there might be zero, one or more instances.
MySessionHandler
knows this information and is @ApplicatedScoped
so it is safe to inject it
The REST endpoint then changes to:
@Path("citas")
// ...
public class citaResource {
@Inject
com.softcase.citasmanager.websocket.SessionHandler handler;
@GET
// ...
public Response cita() {
// REST processing
// ...
// Websocket processing:
// - Handler is always here for you
// - Handler knows which websocket sessions to send the message to.
// The RestController is not aware of the recipients
handler.sendMessage("Your Item was created");
}
}
Please note that I put the websocket processing after the REST processing as you may not always send the message (e.g. creation or whatever exception).
Misc
Unrelated to your questions but I have some comments about your code:
- Classes name are CamelCase starting with capitalized letter per Oracle recommendation
- Avoid generic name for your classes such as
Ws
. I renamed it WsCita
for the example