Caution : Although this question covers long textual information with a mess of Java code snippets, it is merely targeted to JavaScript/jQuery and a bit of PrimeFaces stuff (just <p:remoteCommand>
) as mentioned in the introductory part in the beginning.
I am receiving a JSON message from WebSockets (Java EE 7 / JSR 356 WebSocket API) as follows.
if (window.WebSocket) {
var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");
ws.onmessage = function (event) {
jsonMsg=event.data;
var json = JSON.parse(jsonMsg);
var msg=json["jsonMessage"];
if (window[msg]) {
window[msg](); //It is literally interpreted as a function - updateModel();
}
};
}
In the above code, event.data
contains a JSON string {"jsonMessage":"updateModel"}
. Thus, msg
will contain a string value which is updateModel
.
In the following segment of code,
if (window[msg]) {
window[msg](); //It is literally interpreted as a JavaScript function - updateModel();
}
window[msg]();
causes a JavaScript function associated with a <p:remoteCommand>
to be invoked (which in turn invokes an actionListener="#{bean.remoteAction}"
associated with the <p:remoteCommand>
).
<p:remoteCommand name="updateModel"
actionListener="#{bean.remoteAction}"
oncomplete="notifyAll()"
process="@this"
update="@none"/>
update="@none"
is not necessarily needed.
After receiving this message, I need to notify all the associated clients about this update. I use the following JavaScript function to do so which is associated with the oncomplete
handler of the above <p:remoteCommand>
.
var jsonMsg;
function notifyAll() {
if(jsonMsg) {
sendMessage(jsonMsg);
}
}
Notice that the variable jsonMsg
is already assigned a value in the first snippet - it is a global variable. sendMessage()
is another JavaScript function that actually sends a notification about this update to all the associated clients through WebSockets which is not needed in this question.
This works well but is there a way to do some magic in the following condition
if (window[msg]) {
window[msg]();
//Do something to call notifyAll() on oncomplete of remote command.
}
so that the notifyAll()
function can be invoked through some JavaScript code directly (which is currently attached to oncomplete
of <p:remoteCommand>
and the expected JavaScript code (or even something else) should simulate this oncomplete
) basically eliminating the need to depend upon a global JavaScript variable (jsonMSg
)?
Edit : The problem I am trying to solve (it may be considered to be additional information).
When an admin for example, makes some changes (by means of DML operations) to a JPA entity named Category
, entity listeners are triggered which in turn causes a CDI event to be raised as follows.
@ApplicationScoped
public class CategoryListener {
@PostPersist
@PostUpdate
@PostRemove
public void onChange(Category category) throws NamingException {
BeanManager beanManager = (BeanManager) InitialContext.doLookup("java:comp/BeanManager");
beanManager.fireEvent(new CategoryChangeEvent(category));
}
}
Needless to say that the entity Category
is designated with the annotation @EntityListeners(CategoryListener.class)
.
Just one side note (completely off topic) : Getting an instance of BeanManager
through a JNDI look-up as done in the preceding code snippet is temporary. The GlassFish Server 4.1 having the Weld version 2.2.2 final fails to inject the CDI event javax.enterprise.event.Event<T>
which is supposed to be injected as follows.
@Inject
private Event<CategoryChangeEvent> event;
And then, the event can be fired as follows with reference to the relevant code snippet above.
event.fire(new CategoryChangeEvent(category));
This event is observed in the web project as follows.
@ApplicationScoped
public class RealTimeUpdate {
public void onCategoryChange(@Observes CategoryChangeEvent event) {
AdminPush.sendAll("updateModel");
}
}
Where an admin uses his own end-point as follows (AdminPush.sendAll("updateModel");
is invoked manually therein).
@ServerEndpoint(value = "/AdminPush", configurator = ServletAwareConfig.class)
public final class AdminPush {
private static final Set<Session> sessions = new LinkedHashSet<Session>();
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
if (Boolean.valueOf((String) config.getUserProperties().get("isAdmin"))) {
sessions.add(session);
}
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
}
private static JsonObject createJsonMessage(String message) {
return JsonProvider.provider().createObjectBuilder().add("jsonMessage", message).build();
}
public static void sendAll(String text) {
synchronized (sessions) {
String message = createJsonMessage(text).toString();
for (Session session : sessions) {
if (session.isOpen()) {
session.getAsyncRemote().sendText(message);
}
}
}
}
}
Here only an admin is allowed to use this end-point. All other users are prevented from creating a WebSocket session using a conditional check in the onOpen()
method.
session.getAsyncRemote().sendText(message);
inside the foreach
loop sends a notification (in the form of a JSON message) to the admin about these changes made in the entity Category
.
As shown in the first code snippet, window[msg]();
invokes an action method (through a <p:remoteCommand>
as shown earlier) associated with an application scoped bean - actionListener="#{realTimeMenuManagedBean.remoteAction}"
.
@Named
@ApplicationScoped
public class RealTimeMenuManagedBean {
@Inject
private ParentMenuBeanLocal service;
private List<Category> category;
private final Map<Long, List<SubCategory>> categoryMap = new LinkedHashMap<Long, List<SubCategory>>();
// Other lists and maps as and when required for a dynamic CSS menu.
public RealTimeMenuManagedBean() {}
@PostConstruct
private void init() {
populate();
}
private void populate() {
categoryMap.clear();
category = service.getCategoryList();
for (Category c : category) {
Long catId = c.getCatId();
categoryMap.put(catId, service.getSubCategoryList(catId));
}
}
// This method is invoked through the above-mentioned <p:remoteCommand>.
public void remoteAction() {
populate();
}
// Necessary accessor methods only.
}
All other users/clients (who are on a different panel - other than the admin panel) should only be notified when actionListener="#{realTimeMenuManagedBean.remoteAction}"
finishes in its entirely - must not happen before the action method finishes - should be notified through the oncomplate
event handler of <p:remoteCommand>
. This is the reason why two different end-points have been taken.
Those other users use their own end-point:
@ServerEndpoint("/Push")
public final class Push {
private static final Set<Session> sessions = new LinkedHashSet<Session>();
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
}
@OnMessage
public void onMessage(String text) {
synchronized (sessions) {
for (Session session : sessions) {
if (session.isOpen()) {
session.getAsyncRemote().sendText(text);
}
}
}
}
}
The method annotated with @OnMessage
comes to play, when a message is sent through oncomplete
of <p:remoteCommand>
as shown above.
Those clients use the following JavaScript code to just fetch the new values from the above-mentioned application scoped bean (the bean was already queried adequately by the admin from the database. Thus, there is no need to ridiculously query it again by each and every individual client separately (other than the admin). Hence, it is an application scoped bean).
if (window.WebSocket) {
var ws = new WebSocket("wss://localhost:8181/ContextPath/Push");
ws.onmessage = function (event) {
var json = JSON.parse(event.data);
var msg = json["jsonMessage"];
if (window[msg]) {
window[msg]();
}
};
$(window).on('beforeunload', function () {
ws.close();
});
}
In conjunction with the following <p:remoteCommand>
.
<p:remoteCommand name="updateModel"
process="@this"
update="parentMenu"/>
Where parentMenu
- the component to be updated by this <p:remoteCommand>
is an id
of a container JSF component <h:panelGroup>
which contains a plain CSS menu with a bunch of <ui:repeat>
s.
Hope this makes the scenario clearer.
Update :
This question has been answered precisely here based on <p:remoteCommand>
(As to the concrete question, the sole question was to remove a dependency upon a global JavaScript variable as stated in the introductory part of this question).