从p:remoteCommand的oncomplete处理程序调用一个JavaScript函数——用一些JavaScript代码模拟相同的代码。

时间:2022-06-21 20:02:47

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.

警告:尽管这个问题包含了大量的Java代码片段,但它只是针对JavaScript/jQuery和一些原始的东西(只是 ),正如开头部分所提到的那样。


I am receiving a JSON message from WebSockets (Java EE 7 / JSR 356 WebSocket API) as follows.

我正在接收来自WebSockets (Java EE 7 / JSR 356 WebSocket API)的JSON消息,如下所示。

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.

在上述代码中,事件。数据包含JSON字符串{“jsonMessage”:“updateModel”}。因此,msg将包含一个字符串值,它是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>).

窗口(味精)();导致与 相关联的JavaScript函数调用(该函数反过来调用actionListener="#{bean)。“与 相关联”。

<p:remoteCommand name="updateModel"
                 actionListener="#{bean.remoteAction}" 
                 oncomplete="notifyAll()"
                 process="@this"
                 update="@none"/>

update="@none" is not necessarily needed.

不需要更新="@none"。


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>.

收到此消息后,我需要将此更新通知所有相关的客户机。我使用下面的JavaScript函数来完成,这与上述 的oncomplete处理程序相关联。

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.

注意,变量jsonMsg已经在第一个代码段中分配了一个值——它是一个全局变量。sendMessage()是另一个JavaScript函数,它实际上通过WebSockets向所有关联的客户机发送这个更新通知,这个问题不需要。


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)?

因此,notifyAll()函数可以直接通过一些JavaScript代码来调用(它现在连接到 和预期的JavaScript代码(或者甚至是其他东西)中,应该模拟这个oncomplete),这基本上消除了依赖全局JavaScript变量(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.

例如,当一个管理员对一个名为Category的JPA实体进行一些更改(通过DML操作)时,会触发实体监听器,从而引发一个CDI事件,如下所示。

@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).

不用说,实体类别被指定为注释@ entitylistener (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.

只有一个侧重点(完全偏离主题):在前面的代码片段中通过JNDI查找获得BeanManager的实例是暂时的。GlassFish服务器4.1有焊缝版本2.2.2最终未能注入CDI事件javax.enterprise.event。事件 ,应该按如下方式注入。

@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.

这个事件在web项目中被观察到如下。

@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).

在这里,管理员使用他自己的端点(AdminPush.sendAll(“updateModel”);手动调用其中)。

@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.

这里只允许管理员使用这个端点。所有其他用户都无法使用onOpen()方法中的条件检查创建WebSocket会话。

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.

session.getAsyncRemote().sendText(消息);在foreach循环内部,向管理员发送一个通知(以JSON消息的形式),以处理实体类别中的这些更改。

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}".

如第一个代码片段所示,窗口[msg]();调用一个操作方法(通过 ,如前面所示)与应用程序范围的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.

所有其他用户/客户端(在不同的面板上,除了管理面板之外)应该只在actionListener="#{realTimeMenuManagedBean "时被通知。remoteAction}“在操作方法完成之前完全不能发生”,应通过 的oncomplate事件处理程序通知。这就是为什么要采取两个不同的端点的原因。


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.

当一条消息通过 完成时,将使用@OnMessage进行注释。

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).

这些客户机使用下面的JavaScript代码从上面的应用程序scoped bean中获取新值(从数据库管理员已经对bean进行了充分的查询)。因此,没有必要对每个单独的客户端(除了admin)再进行可笑的查询。因此,它是一个应用程序范围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.

Where parentMenu——由这个 更新的组件是一个容器JSF组件 的id,其中包含一个简单的CSS菜单,其中包含一组 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).

这个问题的答案正是基于 (关于具体的问题,唯一的问题是在这个问题的引言部分中声明了对全局JavaScript变量的依赖)。

1 个解决方案

#1


1  

I don't think I understood every aspect of your problem, but anyway I try to help a bit. Note that I do not know PrimeFaces, so all I did was reading the docs.

我不认为我理解了你的问题的每个方面,但无论如何,我试着去帮助一点。请注意,我不知道黄金面孔,所以我所做的只是阅读文档。

What I understand is, that you try to get rid of the global variable. But I am afraid, I do not think this is possible.

我所理解的是,你试图摆脱全局变量。但我恐怕这是不可能的。

The problem here is, that PrimeFaces does not allow you to pass something transparently from your invocation of the remote call further to the oncomplete call (except you pass it to a Java code of the Bean and then back to the UI, and this usually is not what you want).

这里的问题是,PrimeFaces不允许你通过一些透明地从你的远程调用进一步oncomplete调用的调用(除非你把它传给一个Bean的Java代码,然后再回到UI,这通常并不是你想要的东西)。

However, I hope, you can come very close to it.

但是,我希望,你可以非常接近它。

Part 1, JS returns early

Please also note that there probably is some misconception about Java and JavaScript.

请注意,关于Java和JavaScript可能存在一些误解。

Java is multithreaded and runs several commands in parallel, while JavaScript is singlethreaded and usually never waits for something to complete. Doing things asychronously is mandatory to get a responsive Web-UI.

Java是多线程的,并行地运行几个命令,而JavaScript是单线程的,通常从不等待完成。要想获得一个响应性的web用户界面,必须按时间顺序进行操作。

Hence your remoteCommand invocation (seen from the JS side) will (usually, async case) return long before the oncomplete handler will be invoked. That means, if window[msg]() returns, you are not finished with the remoteCommand yet.

因此,在调用oncomplete处理程序之前,您的remoteCommand调用(从JS端可以看到)将(通常是async案例)返回。这意味着,如果window[msg]()返回,那么您还没有完成remoteCommand。

So what you want to manage with following code

因此,您需要使用以下代码来管理。

if (window[msg]) {
    window[msg]();

    //Do something to call notifyAll() on oncomplete of remote command.
    dosomethinghere();
}

will fail. dosomethinghere() will not be invoked when the remoteCommand returned (as JS does not want to wait for some event, which might never happen). This means, dosomethinghere() will be invoked when the Ajax-request was just opened to the remote (to the Java application).

将会失败。dosomethinghere()将不会在remoteCommand返回时调用(因为JS不希望等待某些事件,这可能永远不会发生)。这意味着,当ajax请求刚刚打开到远程(到Java应用程序)时,将调用dosomethinghere()。

To run something after the Ajax call finished, this must be done in the oncomplete routine (or onsuccess). This is why it's there.

要在Ajax调用完成后运行一些东西,必须在oncomplete例程(或onsuccess)中完成。这就是它存在的原因。

Part 2, validate msg

Please note something different about window[msg](). This can be considered a bit dangerous if you cannot trust the pushed message completely. window[msg]() essentially runs any function named with the contents of the variable msg. For example if msg happen to be close then window.close() will be run, which probably is not what you want.

请注意窗口[msg]()的不同之处。如果不能完全信任被推送的消息,这可能会被认为有点危险。窗口[msg]()本质上是运行一个以变量msg的内容命名的函数。例如,如果msg恰好是关闭的,那么window.close()将会运行,这可能不是您想要的。

You should make sure, msg is one expected word, and decline all other words. Example code for this:

你应该确保,msg是一个预期的词,并且拒绝所有其他的词。示例代码:

var validmsg = { updateModel:1, rc:1 }

[..]

if (validmsg[msg] && window[msg])
  window[msg]();

Part 3: How to handle multiple JSON messages in parallel

The global variable has some drawback. There is only one. If you happen to receive another JSON message on the WebSocket while the previous message still is processing in the remoteCommand, this will overwrite the previous message. So the notifyAll() will see the newer message twice, the old one is lost.

全局变量有一些缺点。只有一个。如果您碰巧收到了WebSocket上的另一个JSON消息,而之前的消息仍然在remoteCommand中处理,那么这将覆盖前面的消息。因此notifyAll()将会看到更新的消息两次,旧的消息丢失。

A classical race condition. What you must do is, to create something like a registry to register all the messages, and then pass some value to notifyAll() to tell, which of the registered messages shall be processed.

一个经典的竞态条件。您必须做的是,创建一个类似注册表的东西来注册所有的消息,然后将一些值传递给notifyAll()来告知,哪些已注册的消息应该被处理。

With only a little change, you can either parallely (here) or serially (Part 4) process the messages.

只需稍微改变一下,就可以并行(这里)或串行(第4部分)处理消息。

First, create a counter to be able to distinguish the messages. Also an object to store all the messages. And we declare all valid messages we expect (see Part 2):

首先,创建一个计数器来区分消息。也是一个存储所有消息的对象。我们声明所有我们期望的有效消息(见第2部分):

var jsonMsgNr = 0;
var jsonMessages = {};
var validmsg = { updateModel:1 }

Now add a message each time we receive one:

现在,我们每收到一条信息,就添加一条信息:

if (window.WebSocket) {
    var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");

    ws.onmessage = function (event) {
        var jsonMsg = event.data;
        var json = JSON.parse(jsonMsg);        
        var msg=json["jsonMessage"];

        if (validmsg[msg] && window[msg]) {
            var nr = ++jsonMsgNr;
            jsonMessages[nr] = { jsonMsg:jsonMsg, json:json };

To be able to pass the nr to NotifyAll() an additional parameter needs to be passed to the Bean. Let's call it msgNr:

为了能够将nr传递给NotifyAll(),需要将一个额外的参数传递给Bean。我们叫它msgNr:

            // Following might look a bit different on older PrimeFaces
            window[msg]([{name:'msgNr', value:nr}]);
        }
    }
}

Perhaps have a look into https://*.com/a/7221579/490291 for more on passing values this way.

也许您可以查看https://*.com/a/7221579/490291,了解更多关于通过这种方式传递值的信息。

The remoteAction bean now gets an additional parameter msgNr passed, which must be passed back via Ajax.

remoteAction bean现在获得一个额外的参数msgNr,它必须通过Ajax返回。

Unfortunately I have no idea (sorry) how this looks in Java. So make sure, your answer to the AjaxCall copies the msgNr out again.

不幸的是,我不知道(不好意思)Java的这个样子。所以,你要确保,你对AjaxCall的回答再次将msgNr复制出来。

Also, as the documentation is quiet about this subject, I am not sure how the parameters are passed back to the oncomplete handler. According to the JavaScript debugger, notifyAll() gets 3 parameters: xhdr, payload, and pfArgs. Unfortunately I was not able to setup a test case to find out how things look like.

另外,由于文档对这个主题很安静,所以我不确定参数是如何传递给oncomplete处理程序的。根据JavaScript调试器,notifyAll()得到3个参数:xhdr、有效负载和pfArgs。不幸的是,我无法设置一个测试用例来找出事情的样子。

Hence the function looks a bit like (bear with me, please):

因此这个函数看起来有点像(请耐心听我说):

function notifyAll(x, data, pfArgs) {
   var nr = ???; // find out how to extract msgNr from data

   var jsonMsg = jsonMessages[nr].jsonMsg;
   var json = jsonMessages[nr].json;
   jsonMessages[nr] = null;  // free memory

   sendMessage(jsonMsg);

   dosomething(json);
}

If you split this into two functions, then you can invoke the notifyAll() from other parts in your application:

如果将其拆分为两个函数,则可以从应用程序的其他部分调用notifyAll():

function notifyAll(x, data, unk) {
   var nr = ???; // find out how to extract msgNr from data

   realNotifyAll(nr);
}

function realNotifyAll(nr) {
  if (!(nr in jsonMessages)) return;

  var jsonMsg = jsonMessages[nr].jsonMsg;
  var json = jsonMessages[nr].json;
  delete jsonMessages[nr];  // free memory

  sendMessage(jsonMsg);

  dosomething(json);
}

Some things here are a bit redundant. For example you perhaps do not need the json element in jsonMessages or want to parse the json again to spare some memory in case the json is very big. However the code is meant not to be optimal but to be easy to adjust to your needs.

这里有些东西是多余的。例如,您可能在jsonmessage中不需要json元素,或者希望再次解析json以备存一些内存,以防json非常大。然而,代码不是最佳的,而是易于根据您的需要进行调整。

Part 4: serialize requests

Now to the changes to serialize things. That's quite easy by adding some semaphore. Semaphores in JavaScript are just variables. This is because there is only one global thread.

现在来看看序列化的变化。添加一些信号量是很容易的。在JavaScript中的信号量只是变量。这是因为只有一个全局线程。

var jsonMsgNr = 0;
var jsonMessages = {};
var validmsg = { updateModel:1 }
var jsonMsgNrLast = 0;           // ADDED

if (window.WebSocket) {
    var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");

    ws.onmessage = function (event) {
        var jsonMsg = event.data;
        var json = JSON.parse(jsonMsg);        
        var msg=json["jsonMessage"];

        if (validmsg[msg] && window[msg]) {
            var nr = ++jsonMsgNr;
            jsonMessages[nr] = { jsonMsg:jsonMsg, json:json };

            if (!jsonMsgNrLast) {    // ADDED
                jsonMsgNrLast = nr;  // ADDED
                window[msg]([{name:'msgNr', value:nr}]);
            }
        }
    }
}

function realNotifyAll(nr) {
  if (!(nr in jsonMessages)) return;

  var jsonMsg = jsonMessages[nr].jsonMsg;
  var json = jsonMessages[nr].json;
  delete jsonMessages[nr];  // free memory

  sendMessage(jsonMsg);

  dosomething(json);

  // Following ADDED
  nr++;
  jsonMsgNrLast = 0;
  if (nr in jsonMessages)
    {
      jsonMsgNrLast = nr;
      window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);
    }
}

Note: jsonMsgNrLast could be just a flag (true/false). However having the current processed number in a variable perhaps can help somewhere else.

注意:jsonMsgNrLast可能只是一个标志(真/假)。但是,在变量中使用当前处理的数字可能会对其他地方有所帮助。

Having said that, there is a starvation problem in case something fails in sendMessage or dosomething. So perhaps you can interleave it a bit:

话虽如此,如果sendMessage或dosomething发生故障,就会出现饥饿问题。所以也许你可以穿插一些:

function realNotifyAll(nr) {
  if (!(nr in jsonMessages)) return;

  var jsonMsg = jsonMessages[nr].jsonMsg;
  var json = jsonMessages[nr].json;
  delete jsonMessages[nr];  // free memory

  nr++;
  jsonMsgNrLast = 0;
  if (nr in jsonMessages)
    {
      jsonMsgNrLast = nr;
      // Be sure you are async here!
      window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);
    }

  // Moved, but now must not rely on jsonMsgNrLast:
  sendMessage(jsonMsg);
  dosomething(json);
}

This way the AJAX request is already send out while sendMessage is running. If now dosomething has a JavaScript error or similar, the messages are still processed correctly.

这样,当sendMessage运行时,AJAX请求就已经发送出去了。如果现在dosomething有一个JavaScript错误或类似,消息仍然正确处理。

Please note: All this was typed in without any tests. There might be syntax errors or worse. Sorry, I tried my best. If you find a bug, edit is your friend.

请注意:所有这些都是在没有任何测试的情况下输入的。可能存在语法错误或更糟的情况。对不起,我尽力了。如果你发现了一个错误,编辑是你的朋友。

Part 5: Direct Invocation from JS

Now, with all this in place and a serialized Run, you can always invoke the previous notifyAll() using realNotifyAll(jsonMsgNrLast). Or you can display the jsonMessages in a list and choose any arbitrary number.

现在,在所有这些就绪和序列化运行之后,您可以使用realNotifyAll(jsonMsgNrLast)调用前面的notifyAll()。或者您可以在列表中显示jsonMessages并选择任意数量。

By skipping the call to window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]); (and above window[msg]([{name:'msgNr', value:nr}]);) you also can halt the Bean processing and run it on-demand using the usual JQuery callbacks. For this create a function and change the code a bit again:

通过跳过对窗口的调用[jsonMessages[nr].json。味精]([{名称:“msgNr”,价值:nr }));您还可以使用常用的JQuery回调来停止Bean处理并按需运行。为此,创建一个函数并再次更改代码:

var jsonMsgNr = 0;
var jsonMessages = {};
var validmsg = { updateModel:1 }
var jsonMsgNrLast = 0;
var autoRun = true;        // ADDED, set false control through GUI

if (window.WebSocket) {
    var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");

    ws.onmessage = function (event) {
        var jsonMsg = event.data;
        var json = JSON.parse(jsonMsg);        

        if (validmsg[msg] && window[msg]) {
            var nr = ++jsonMsgNr;
            jsonMessages[nr] = { jsonMsg:jsonMsg, json:json };

            updateGuiPushList(nr, 1);

            if (autoRun && !jsonMsgNrLast) {
                runRemote(nr);
            }
        }
    }
}

function realNotifyAll(nr) {
  if (!(nr in jsonMessages)) return;

  var jsonMsg = jsonMessages[nr].jsonMsg;
  var json = jsonMessages[nr].json;
  delete jsonMessages[nr];  // free memory

  updateGuiPushList(nr, 0);

  jsonMsgNrLast = 0;
  if (autoRun)
    runRemote(nr+1);

  // Moved, but now must not rely on jsonMsgNrLast:
  sendMessage(jsonMsg);
  dosomething(json);
}

function runRemote(nr) {
  if (nr==jsonMsgNrLast) return;
  if (nr in jsonMessages)
    {
      if (jsonMsgNrLast) { alert("Whoopsie! Please wait until processing finished"); return; }
      jsonMsgNrLast = nr;

      updateGuiPushList(nr, 2);

      // Be sure you are async here!
      window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);
    }
}

Now you can start the processing with runRemote(nr) and invoke the completion function with realNotifyAll(nr).

现在可以使用runRemote(nr)启动处理,并使用realNotifyAll(nr)调用完成函数。

The function updateGuiPushList(nr, state) with state=0:finished 1=added 2=running is the callback to your GUI code which updates the on-screen list of waiting pushes to process. Set autoRun=false to stop automatic processing and autoRun=true for automatic processing.

函数updateGuiPushList(nr, state)与状态=0:完成1=添加2=运行是对GUI代码的回调,该代码更新了屏幕上等待推送的列表。设置autoRun=false,以停止自动处理,autoRun=true用于自动处理。

Note: After setting autoRun from false to true you need to trigger runRemote once with the lowest nr, of course.

注意:在将autoRun从false设置为true之后,您需要使用最低的nr触发runRemote。

#1


1  

I don't think I understood every aspect of your problem, but anyway I try to help a bit. Note that I do not know PrimeFaces, so all I did was reading the docs.

我不认为我理解了你的问题的每个方面,但无论如何,我试着去帮助一点。请注意,我不知道黄金面孔,所以我所做的只是阅读文档。

What I understand is, that you try to get rid of the global variable. But I am afraid, I do not think this is possible.

我所理解的是,你试图摆脱全局变量。但我恐怕这是不可能的。

The problem here is, that PrimeFaces does not allow you to pass something transparently from your invocation of the remote call further to the oncomplete call (except you pass it to a Java code of the Bean and then back to the UI, and this usually is not what you want).

这里的问题是,PrimeFaces不允许你通过一些透明地从你的远程调用进一步oncomplete调用的调用(除非你把它传给一个Bean的Java代码,然后再回到UI,这通常并不是你想要的东西)。

However, I hope, you can come very close to it.

但是,我希望,你可以非常接近它。

Part 1, JS returns early

Please also note that there probably is some misconception about Java and JavaScript.

请注意,关于Java和JavaScript可能存在一些误解。

Java is multithreaded and runs several commands in parallel, while JavaScript is singlethreaded and usually never waits for something to complete. Doing things asychronously is mandatory to get a responsive Web-UI.

Java是多线程的,并行地运行几个命令,而JavaScript是单线程的,通常从不等待完成。要想获得一个响应性的web用户界面,必须按时间顺序进行操作。

Hence your remoteCommand invocation (seen from the JS side) will (usually, async case) return long before the oncomplete handler will be invoked. That means, if window[msg]() returns, you are not finished with the remoteCommand yet.

因此,在调用oncomplete处理程序之前,您的remoteCommand调用(从JS端可以看到)将(通常是async案例)返回。这意味着,如果window[msg]()返回,那么您还没有完成remoteCommand。

So what you want to manage with following code

因此,您需要使用以下代码来管理。

if (window[msg]) {
    window[msg]();

    //Do something to call notifyAll() on oncomplete of remote command.
    dosomethinghere();
}

will fail. dosomethinghere() will not be invoked when the remoteCommand returned (as JS does not want to wait for some event, which might never happen). This means, dosomethinghere() will be invoked when the Ajax-request was just opened to the remote (to the Java application).

将会失败。dosomethinghere()将不会在remoteCommand返回时调用(因为JS不希望等待某些事件,这可能永远不会发生)。这意味着,当ajax请求刚刚打开到远程(到Java应用程序)时,将调用dosomethinghere()。

To run something after the Ajax call finished, this must be done in the oncomplete routine (or onsuccess). This is why it's there.

要在Ajax调用完成后运行一些东西,必须在oncomplete例程(或onsuccess)中完成。这就是它存在的原因。

Part 2, validate msg

Please note something different about window[msg](). This can be considered a bit dangerous if you cannot trust the pushed message completely. window[msg]() essentially runs any function named with the contents of the variable msg. For example if msg happen to be close then window.close() will be run, which probably is not what you want.

请注意窗口[msg]()的不同之处。如果不能完全信任被推送的消息,这可能会被认为有点危险。窗口[msg]()本质上是运行一个以变量msg的内容命名的函数。例如,如果msg恰好是关闭的,那么window.close()将会运行,这可能不是您想要的。

You should make sure, msg is one expected word, and decline all other words. Example code for this:

你应该确保,msg是一个预期的词,并且拒绝所有其他的词。示例代码:

var validmsg = { updateModel:1, rc:1 }

[..]

if (validmsg[msg] && window[msg])
  window[msg]();

Part 3: How to handle multiple JSON messages in parallel

The global variable has some drawback. There is only one. If you happen to receive another JSON message on the WebSocket while the previous message still is processing in the remoteCommand, this will overwrite the previous message. So the notifyAll() will see the newer message twice, the old one is lost.

全局变量有一些缺点。只有一个。如果您碰巧收到了WebSocket上的另一个JSON消息,而之前的消息仍然在remoteCommand中处理,那么这将覆盖前面的消息。因此notifyAll()将会看到更新的消息两次,旧的消息丢失。

A classical race condition. What you must do is, to create something like a registry to register all the messages, and then pass some value to notifyAll() to tell, which of the registered messages shall be processed.

一个经典的竞态条件。您必须做的是,创建一个类似注册表的东西来注册所有的消息,然后将一些值传递给notifyAll()来告知,哪些已注册的消息应该被处理。

With only a little change, you can either parallely (here) or serially (Part 4) process the messages.

只需稍微改变一下,就可以并行(这里)或串行(第4部分)处理消息。

First, create a counter to be able to distinguish the messages. Also an object to store all the messages. And we declare all valid messages we expect (see Part 2):

首先,创建一个计数器来区分消息。也是一个存储所有消息的对象。我们声明所有我们期望的有效消息(见第2部分):

var jsonMsgNr = 0;
var jsonMessages = {};
var validmsg = { updateModel:1 }

Now add a message each time we receive one:

现在,我们每收到一条信息,就添加一条信息:

if (window.WebSocket) {
    var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");

    ws.onmessage = function (event) {
        var jsonMsg = event.data;
        var json = JSON.parse(jsonMsg);        
        var msg=json["jsonMessage"];

        if (validmsg[msg] && window[msg]) {
            var nr = ++jsonMsgNr;
            jsonMessages[nr] = { jsonMsg:jsonMsg, json:json };

To be able to pass the nr to NotifyAll() an additional parameter needs to be passed to the Bean. Let's call it msgNr:

为了能够将nr传递给NotifyAll(),需要将一个额外的参数传递给Bean。我们叫它msgNr:

            // Following might look a bit different on older PrimeFaces
            window[msg]([{name:'msgNr', value:nr}]);
        }
    }
}

Perhaps have a look into https://*.com/a/7221579/490291 for more on passing values this way.

也许您可以查看https://*.com/a/7221579/490291,了解更多关于通过这种方式传递值的信息。

The remoteAction bean now gets an additional parameter msgNr passed, which must be passed back via Ajax.

remoteAction bean现在获得一个额外的参数msgNr,它必须通过Ajax返回。

Unfortunately I have no idea (sorry) how this looks in Java. So make sure, your answer to the AjaxCall copies the msgNr out again.

不幸的是,我不知道(不好意思)Java的这个样子。所以,你要确保,你对AjaxCall的回答再次将msgNr复制出来。

Also, as the documentation is quiet about this subject, I am not sure how the parameters are passed back to the oncomplete handler. According to the JavaScript debugger, notifyAll() gets 3 parameters: xhdr, payload, and pfArgs. Unfortunately I was not able to setup a test case to find out how things look like.

另外,由于文档对这个主题很安静,所以我不确定参数是如何传递给oncomplete处理程序的。根据JavaScript调试器,notifyAll()得到3个参数:xhdr、有效负载和pfArgs。不幸的是,我无法设置一个测试用例来找出事情的样子。

Hence the function looks a bit like (bear with me, please):

因此这个函数看起来有点像(请耐心听我说):

function notifyAll(x, data, pfArgs) {
   var nr = ???; // find out how to extract msgNr from data

   var jsonMsg = jsonMessages[nr].jsonMsg;
   var json = jsonMessages[nr].json;
   jsonMessages[nr] = null;  // free memory

   sendMessage(jsonMsg);

   dosomething(json);
}

If you split this into two functions, then you can invoke the notifyAll() from other parts in your application:

如果将其拆分为两个函数,则可以从应用程序的其他部分调用notifyAll():

function notifyAll(x, data, unk) {
   var nr = ???; // find out how to extract msgNr from data

   realNotifyAll(nr);
}

function realNotifyAll(nr) {
  if (!(nr in jsonMessages)) return;

  var jsonMsg = jsonMessages[nr].jsonMsg;
  var json = jsonMessages[nr].json;
  delete jsonMessages[nr];  // free memory

  sendMessage(jsonMsg);

  dosomething(json);
}

Some things here are a bit redundant. For example you perhaps do not need the json element in jsonMessages or want to parse the json again to spare some memory in case the json is very big. However the code is meant not to be optimal but to be easy to adjust to your needs.

这里有些东西是多余的。例如,您可能在jsonmessage中不需要json元素,或者希望再次解析json以备存一些内存,以防json非常大。然而,代码不是最佳的,而是易于根据您的需要进行调整。

Part 4: serialize requests

Now to the changes to serialize things. That's quite easy by adding some semaphore. Semaphores in JavaScript are just variables. This is because there is only one global thread.

现在来看看序列化的变化。添加一些信号量是很容易的。在JavaScript中的信号量只是变量。这是因为只有一个全局线程。

var jsonMsgNr = 0;
var jsonMessages = {};
var validmsg = { updateModel:1 }
var jsonMsgNrLast = 0;           // ADDED

if (window.WebSocket) {
    var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");

    ws.onmessage = function (event) {
        var jsonMsg = event.data;
        var json = JSON.parse(jsonMsg);        
        var msg=json["jsonMessage"];

        if (validmsg[msg] && window[msg]) {
            var nr = ++jsonMsgNr;
            jsonMessages[nr] = { jsonMsg:jsonMsg, json:json };

            if (!jsonMsgNrLast) {    // ADDED
                jsonMsgNrLast = nr;  // ADDED
                window[msg]([{name:'msgNr', value:nr}]);
            }
        }
    }
}

function realNotifyAll(nr) {
  if (!(nr in jsonMessages)) return;

  var jsonMsg = jsonMessages[nr].jsonMsg;
  var json = jsonMessages[nr].json;
  delete jsonMessages[nr];  // free memory

  sendMessage(jsonMsg);

  dosomething(json);

  // Following ADDED
  nr++;
  jsonMsgNrLast = 0;
  if (nr in jsonMessages)
    {
      jsonMsgNrLast = nr;
      window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);
    }
}

Note: jsonMsgNrLast could be just a flag (true/false). However having the current processed number in a variable perhaps can help somewhere else.

注意:jsonMsgNrLast可能只是一个标志(真/假)。但是,在变量中使用当前处理的数字可能会对其他地方有所帮助。

Having said that, there is a starvation problem in case something fails in sendMessage or dosomething. So perhaps you can interleave it a bit:

话虽如此,如果sendMessage或dosomething发生故障,就会出现饥饿问题。所以也许你可以穿插一些:

function realNotifyAll(nr) {
  if (!(nr in jsonMessages)) return;

  var jsonMsg = jsonMessages[nr].jsonMsg;
  var json = jsonMessages[nr].json;
  delete jsonMessages[nr];  // free memory

  nr++;
  jsonMsgNrLast = 0;
  if (nr in jsonMessages)
    {
      jsonMsgNrLast = nr;
      // Be sure you are async here!
      window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);
    }

  // Moved, but now must not rely on jsonMsgNrLast:
  sendMessage(jsonMsg);
  dosomething(json);
}

This way the AJAX request is already send out while sendMessage is running. If now dosomething has a JavaScript error or similar, the messages are still processed correctly.

这样,当sendMessage运行时,AJAX请求就已经发送出去了。如果现在dosomething有一个JavaScript错误或类似,消息仍然正确处理。

Please note: All this was typed in without any tests. There might be syntax errors or worse. Sorry, I tried my best. If you find a bug, edit is your friend.

请注意:所有这些都是在没有任何测试的情况下输入的。可能存在语法错误或更糟的情况。对不起,我尽力了。如果你发现了一个错误,编辑是你的朋友。

Part 5: Direct Invocation from JS

Now, with all this in place and a serialized Run, you can always invoke the previous notifyAll() using realNotifyAll(jsonMsgNrLast). Or you can display the jsonMessages in a list and choose any arbitrary number.

现在,在所有这些就绪和序列化运行之后,您可以使用realNotifyAll(jsonMsgNrLast)调用前面的notifyAll()。或者您可以在列表中显示jsonMessages并选择任意数量。

By skipping the call to window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]); (and above window[msg]([{name:'msgNr', value:nr}]);) you also can halt the Bean processing and run it on-demand using the usual JQuery callbacks. For this create a function and change the code a bit again:

通过跳过对窗口的调用[jsonMessages[nr].json。味精]([{名称:“msgNr”,价值:nr }));您还可以使用常用的JQuery回调来停止Bean处理并按需运行。为此,创建一个函数并再次更改代码:

var jsonMsgNr = 0;
var jsonMessages = {};
var validmsg = { updateModel:1 }
var jsonMsgNrLast = 0;
var autoRun = true;        // ADDED, set false control through GUI

if (window.WebSocket) {
    var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");

    ws.onmessage = function (event) {
        var jsonMsg = event.data;
        var json = JSON.parse(jsonMsg);        

        if (validmsg[msg] && window[msg]) {
            var nr = ++jsonMsgNr;
            jsonMessages[nr] = { jsonMsg:jsonMsg, json:json };

            updateGuiPushList(nr, 1);

            if (autoRun && !jsonMsgNrLast) {
                runRemote(nr);
            }
        }
    }
}

function realNotifyAll(nr) {
  if (!(nr in jsonMessages)) return;

  var jsonMsg = jsonMessages[nr].jsonMsg;
  var json = jsonMessages[nr].json;
  delete jsonMessages[nr];  // free memory

  updateGuiPushList(nr, 0);

  jsonMsgNrLast = 0;
  if (autoRun)
    runRemote(nr+1);

  // Moved, but now must not rely on jsonMsgNrLast:
  sendMessage(jsonMsg);
  dosomething(json);
}

function runRemote(nr) {
  if (nr==jsonMsgNrLast) return;
  if (nr in jsonMessages)
    {
      if (jsonMsgNrLast) { alert("Whoopsie! Please wait until processing finished"); return; }
      jsonMsgNrLast = nr;

      updateGuiPushList(nr, 2);

      // Be sure you are async here!
      window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);
    }
}

Now you can start the processing with runRemote(nr) and invoke the completion function with realNotifyAll(nr).

现在可以使用runRemote(nr)启动处理,并使用realNotifyAll(nr)调用完成函数。

The function updateGuiPushList(nr, state) with state=0:finished 1=added 2=running is the callback to your GUI code which updates the on-screen list of waiting pushes to process. Set autoRun=false to stop automatic processing and autoRun=true for automatic processing.

函数updateGuiPushList(nr, state)与状态=0:完成1=添加2=运行是对GUI代码的回调,该代码更新了屏幕上等待推送的列表。设置autoRun=false,以停止自动处理,autoRun=true用于自动处理。

Note: After setting autoRun from false to true you need to trigger runRemote once with the lowest nr, of course.

注意:在将autoRun从false设置为true之后,您需要使用最低的nr触发runRemote。