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>
).
窗口(味精)();导致与
<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函数来完成,这与上述
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代码来调用(它现在连接到
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]();调用一个操作方法(通过
@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}“在操作方法完成之前完全不能发生”,应通过
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).
这些客户机使用下面的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——由这个
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).
这个问题的答案正是基于
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
, andpfArgs
. 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 injsonMessages
or want to parse thejson
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
, andpfArgs
. 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 injsonMessages
or want to parse thejson
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。