1.1 --- a/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java Sun Aug 25 12:18:02 2013 +0200
1.2 +++ b/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java Sun Aug 25 12:18:25 2013 +0200
1.3 @@ -106,10 +106,23 @@
1.4 js.applyBindings();
1.5
1.6 js.setFetched(null);
1.7 - js.querySex("http://wrong.protocol", Sex.MALE);
1.8 + js.querySex("http://wrong.protocol", null);
1.9
1.10 assert js.getFetchedResponse() != null : "Error reported";
1.11 }
1.12 +
1.13 + @KOTest public void haveToOpenTheWebSocket() throws Throwable {
1.14 + js = Models.bind(new WebSocketik(), newContext());
1.15 + js.applyBindings();
1.16 +
1.17 + js.setFetched(null);
1.18 + try {
1.19 + js.querySex("http://wrong.protocol", Sex.MALE);
1.20 + assert false : "Should throw an exception";
1.21 + } catch (IllegalStateException ex) {
1.22 + assert ex.getMessage().contains("not open") : "Expecting 'not open' msg: " + ex.getMessage();
1.23 + }
1.24 + }
1.25
1.26 static void error(WebSocketik model, Exception ex) {
1.27 if (ex != null) {
2.1 --- a/json/src/main/java/org/apidesign/html/json/impl/JSON.java Sun Aug 25 12:18:02 2013 +0200
2.2 +++ b/json/src/main/java/org/apidesign/html/json/impl/JSON.java Sun Aug 25 12:18:25 2013 +0200
2.3 @@ -31,6 +31,7 @@
2.4 import org.apidesign.html.json.spi.PropertyBinding;
2.5 import org.apidesign.html.json.spi.Technology;
2.6 import org.apidesign.html.json.spi.Transfer;
2.7 +import org.apidesign.html.json.spi.WSTransfer;
2.8
2.9 /**
2.10 *
2.11 @@ -50,6 +51,11 @@
2.12 return t == null ? EmptyTech.EMPTY : t;
2.13 }
2.14
2.15 + static WSTransfer<?> findWSTransfer(BrwsrCtx c) {
2.16 + WSTransfer<?> t = Contexts.find(c, WSTransfer.class);
2.17 + return t == null ? EmptyTech.EMPTY : t;
2.18 + }
2.19 +
2.20 public static void runInBrowser(BrwsrCtx c, Runnable runnable) {
2.21 findTechnology(c).runSafe(runnable);
2.22 }
2.23 @@ -149,6 +155,65 @@
2.24 Transfer t = findTransfer(c);
2.25 t.loadJSON(call);
2.26 }
2.27 + public static WS openWS(
2.28 + BrwsrCtx c, RcvrJSON r, String url, Object data
2.29 + ) {
2.30 + WS ws = WSImpl.create(findWSTransfer(c), r);
2.31 + ws.send(url, data);
2.32 + return ws;
2.33 + }
2.34 +
2.35 + public static abstract class WS {
2.36 + private WS() {
2.37 + }
2.38 +
2.39 + public abstract void send(String url, Object model);
2.40 + }
2.41 +
2.42 + private static final class WSImpl<Socket> extends WS {
2.43 +
2.44 + private final WSTransfer<Socket> trans;
2.45 + private final RcvrJSON rcvr;
2.46 + private Socket socket;
2.47 + private String prevURL;
2.48 +
2.49 + private WSImpl(WSTransfer<Socket> trans, RcvrJSON rcvr) {
2.50 + this.trans = trans;
2.51 + this.rcvr = rcvr;
2.52 + }
2.53 +
2.54 + static <Socket> WS create(WSTransfer<Socket> t, RcvrJSON r) {
2.55 + return new WSImpl<Socket>(t, r);
2.56 + }
2.57 +
2.58 + @Override
2.59 + public void send(String url, Object data) {
2.60 + Socket s = socket;
2.61 + if (s == null) {
2.62 + if (data != null) {
2.63 + throw new IllegalStateException("WebSocket is not opened yet. Call with null data, was: " + data);
2.64 + }
2.65 + JSONCall call = PropertyBindingAccessor.createCall(rcvr, url, null, "WebSocket", null);
2.66 + socket = trans.open(url, call);
2.67 + prevURL = url;
2.68 + return;
2.69 + }
2.70 + if (data == null) {
2.71 + trans.close(s);
2.72 + socket = null;
2.73 + return;
2.74 + }
2.75 + if (!prevURL.equals(url)) {
2.76 + throw new IllegalStateException(
2.77 + "Can't call to different URL " + url + " was: " + prevURL + "!"
2.78 + + " Close the socket by calling it will null data first!"
2.79 + );
2.80 + }
2.81 + JSONCall call = PropertyBindingAccessor.createCall(rcvr, prevURL, null, "WebSocket", data);
2.82 + trans.send(s, call);
2.83 + }
2.84 +
2.85 + }
2.86
2.87 private static final Map<Class,FromJSON<?>> froms;
2.88 static {
2.89 @@ -223,7 +288,8 @@
2.90 }
2.91 }
2.92
2.93 - private static final class EmptyTech implements Technology<Object>, Transfer {
2.94 + private static final class EmptyTech
2.95 + implements Technology<Object>, Transfer, WSTransfer<Void> {
2.96 private static final EmptyTech EMPTY = new EmptyTech();
2.97
2.98 @Override
2.99 @@ -278,6 +344,19 @@
2.100 public synchronized void runSafe(Runnable r) {
2.101 r.run();
2.102 }
2.103 +
2.104 + @Override
2.105 + public Void open(String url, JSONCall onReply) {
2.106 + return null;
2.107 + }
2.108 +
2.109 + @Override
2.110 + public void send(Void socket, JSONCall data) {
2.111 + }
2.112 +
2.113 + @Override
2.114 + public void close(Void socket) {
2.115 + }
2.116 }
2.117
2.118 }
3.1 --- a/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java Sun Aug 25 12:18:02 2013 +0200
3.2 +++ b/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java Sun Aug 25 12:18:25 2013 +0200
3.3 @@ -982,79 +982,182 @@
3.4 }
3.5 }
3.6 body.append(") {\n");
3.7 + boolean webSocket = onR.method().equals("WebSocket");
3.8 + if (webSocket) {
3.9 + if (generateWSReceiveBody(body, onR, e, clazz, className, expectsList, modelClass, n, args, urlBefore, jsonpVarName, urlAfter, dataMirror)) {
3.10 + return false;
3.11 + }
3.12 + body.append(" }\n");
3.13 + body.append(" private org.apidesign.html.json.impl.JSON.WS ws_" + e.getSimpleName() + ";\n");
3.14 + } else {
3.15 + if (generateJSONReceiveBody(body, onR, e, clazz, className, expectsList, modelClass, n, args, urlBefore, jsonpVarName, urlAfter, dataMirror)) {
3.16 + return false;
3.17 + }
3.18 + body.append(" }\n");
3.19 + }
3.20 + }
3.21 + return true;
3.22 + }
3.23 +
3.24 + private boolean generateJSONReceiveBody(StringWriter body, OnReceive onR, ExecutableElement e, Element clazz, String className, boolean expectsList, String modelClass, String n, List<String> args, StringBuilder urlBefore, String jsonpVarName, StringBuilder urlAfter, String dataMirror) {
3.25 + body.append(
3.26 + " class ProcessResult extends org.apidesign.html.json.impl.RcvrJSON {\n" +
3.27 + " @Override\n" +
3.28 + " public void onError(org.apidesign.html.json.impl.RcvrJSON.MsgEvnt ev) {\n" +
3.29 + " Exception value = ev.getException();\n"
3.30 + );
3.31 + if (onR.onError().isEmpty()) {
3.32 body.append(
3.33 - " class ProcessResult extends org.apidesign.html.json.impl.RcvrJSON {\n" +
3.34 - " @Override\n" +
3.35 - " public void onError(org.apidesign.html.json.impl.RcvrJSON.MsgEvnt ev) {\n" +
3.36 - " Exception value = ev.getException();\n"
3.37 - );
3.38 - if (onR.onError().isEmpty()) {
3.39 - body.append(
3.40 " value.printStackTrace();\n"
3.41 );
3.42 - } else {
3.43 - if (!findOnError(e, ((TypeElement)clazz), onR.onError(), className)) {
3.44 - return false;
3.45 - }
3.46 - body.append(" ").append(clazz.getSimpleName()).append(".").append(onR.onError()).append("(");
3.47 - body.append(className).append(".this, value);\n");
3.48 + } else {
3.49 + if (!findOnError(e, ((TypeElement)clazz), onR.onError(), className)) {
3.50 + return true;
3.51 }
3.52 + body.append(" ").append(clazz.getSimpleName()).append(".").append(onR.onError()).append("(");
3.53 + body.append(className).append(".this, value);\n");
3.54 + }
3.55 + body.append(
3.56 + " }\n" +
3.57 + " @Override\n" +
3.58 + " public void onMessage(org.apidesign.html.json.impl.RcvrJSON.MsgEvnt ev) {\n"
3.59 + );
3.60 + if (expectsList) {
3.61 body.append(
3.62 - " }\n" +
3.63 - " @Override\n" +
3.64 - " public void onMessage(org.apidesign.html.json.impl.RcvrJSON.MsgEvnt ev) {\n"
3.65 + " " + modelClass + "[] arr = new " + modelClass + "[ev.dataSize()];\n"
3.66 + );
3.67 + } else {
3.68 + body.append(
3.69 + " " + modelClass + "[] arr = { null };\n"
3.70 + );
3.71 + }
3.72 + body.append(
3.73 + " ev.dataRead(context, " + modelClass + ".class, arr);\n"
3.74 );
3.75 - if (expectsList) {
3.76 - body.append(
3.77 - " " + modelClass + "[] arr = new " + modelClass + "[ev.dataSize()];\n"
3.78 - );
3.79 - } else {
3.80 - body.append(
3.81 - " " + modelClass + "[] arr = { null };\n"
3.82 - );
3.83 - }
3.84 - body.append(
3.85 - " ev.dataRead(context, " + modelClass + ".class, arr);\n"
3.86 - );
3.87 - {
3.88 - body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
3.89 - String sep = "";
3.90 - for (String arg : args) {
3.91 - body.append(sep);
3.92 - body.append(arg);
3.93 - sep = ", ";
3.94 - }
3.95 - body.append(");\n");
3.96 - }
3.97 - body.append(
3.98 - " }\n" +
3.99 - " }\n"
3.100 - );
3.101 - body.append(" ProcessResult pr = new ProcessResult();\n");
3.102 - body.append(" org.apidesign.html.json.impl.JSON.loadJSON(context, pr,\n ");
3.103 - body.append(urlBefore).append(", ");
3.104 - if (jsonpVarName != null) {
3.105 - body.append(urlAfter);
3.106 - } else {
3.107 - body.append("null");
3.108 - }
3.109 - if (!"GET".equals(onR.method()) || dataMirror != null) {
3.110 - body.append(", \"").append(onR.method()).append('"');
3.111 - if (dataMirror != null) {
3.112 - body.append(", data");
3.113 - } else {
3.114 - body.append(", null");
3.115 - }
3.116 - } else {
3.117 - body.append(", null, null");
3.118 + {
3.119 + body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
3.120 + String sep = "";
3.121 + for (String arg : args) {
3.122 + body.append(sep);
3.123 + body.append(arg);
3.124 + sep = ", ";
3.125 }
3.126 body.append(");\n");
3.127 -// body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
3.128 -// body.append(wrapParams(e, null, className, "ev", "data"));
3.129 -// body.append(");\n");
3.130 - body.append(" }\n");
3.131 }
3.132 - return true;
3.133 + body.append(
3.134 + " }\n" +
3.135 + " }\n"
3.136 + );
3.137 + body.append(" ProcessResult pr = new ProcessResult();\n");
3.138 + body.append(" org.apidesign.html.json.impl.JSON.loadJSON(context, pr,\n ");
3.139 + body.append(urlBefore).append(", ");
3.140 + if (jsonpVarName != null) {
3.141 + body.append(urlAfter);
3.142 + } else {
3.143 + body.append("null");
3.144 + }
3.145 + if (!"GET".equals(onR.method()) || dataMirror != null) {
3.146 + body.append(", \"").append(onR.method()).append('"');
3.147 + if (dataMirror != null) {
3.148 + body.append(", data");
3.149 + } else {
3.150 + body.append(", null");
3.151 + }
3.152 + } else {
3.153 + body.append(", null, null");
3.154 + }
3.155 + body.append(");\n");
3.156 + return false;
3.157 + }
3.158 +
3.159 + private boolean generateWSReceiveBody(StringWriter body, OnReceive onR, ExecutableElement e, Element clazz, String className, boolean expectsList, String modelClass, String n, List<String> args, StringBuilder urlBefore, String jsonpVarName, StringBuilder urlAfter, String dataMirror) {
3.160 + body.append(
3.161 + " class ProcessResult extends org.apidesign.html.json.impl.RcvrJSON {\n" +
3.162 + " @Override\n" +
3.163 + " public void onOpen(org.apidesign.html.json.impl.RcvrJSON.MsgEvnt ev) {\n"
3.164 + );
3.165 + body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
3.166 + {
3.167 + String sep = "";
3.168 + for (String arg : args) {
3.169 + body.append(sep);
3.170 + if (arg.startsWith("arr")) {
3.171 + body.append("null");
3.172 + } else {
3.173 + body.append(arg);
3.174 + }
3.175 + sep = ", ";
3.176 + }
3.177 + }
3.178 + body.append(");\n");
3.179 + body.append(
3.180 + " }\n" +
3.181 + " @Override\n" +
3.182 + " public void onError(org.apidesign.html.json.impl.RcvrJSON.MsgEvnt ev) {\n" +
3.183 + " Exception value = ev.getException();\n"
3.184 + );
3.185 + if (onR.onError().isEmpty()) {
3.186 + body.append(
3.187 + " value.printStackTrace();\n"
3.188 + );
3.189 + } else {
3.190 + if (!findOnError(e, ((TypeElement)clazz), onR.onError(), className)) {
3.191 + return true;
3.192 + }
3.193 + body.append(" ").append(clazz.getSimpleName()).append(".").append(onR.onError()).append("(");
3.194 + body.append(className).append(".this, value);\n");
3.195 + }
3.196 + body.append(
3.197 + " }\n" +
3.198 + " @Override\n" +
3.199 + " public void onMessage(org.apidesign.html.json.impl.RcvrJSON.MsgEvnt ev) {\n"
3.200 + );
3.201 + if (expectsList) {
3.202 + body.append(
3.203 + " " + modelClass + "[] arr = new " + modelClass + "[ev.dataSize()];\n"
3.204 + );
3.205 + } else {
3.206 + body.append(
3.207 + " " + modelClass + "[] arr = { null };\n"
3.208 + );
3.209 + }
3.210 + body.append(
3.211 + " ev.dataRead(context, " + modelClass + ".class, arr);\n"
3.212 + );
3.213 + {
3.214 + body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
3.215 + String sep = "";
3.216 + for (String arg : args) {
3.217 + body.append(sep);
3.218 + body.append(arg);
3.219 + sep = ", ";
3.220 + }
3.221 + body.append(");\n");
3.222 + }
3.223 + body.append(
3.224 + " }\n"
3.225 + );
3.226 + if (!onR.onError().isEmpty()) {
3.227 + body.append(
3.228 + " @Override\n"
3.229 + + " public void onClose(org.apidesign.html.json.impl.RcvrJSON.MsgEvnt ev) {\n"
3.230 + );
3.231 + body.append(" ").append(clazz.getSimpleName()).append(".").append(onR.onError()).append("(");
3.232 + body.append(className).append(".this, null);\n");
3.233 + body.append(
3.234 + " }\n"
3.235 + );
3.236 + }
3.237 + body.append(" }\n");
3.238 + body.append(" if (this.ws_").append(e.getSimpleName()).append(" == null) {\n");
3.239 + body.append(" ProcessResult pr = new ProcessResult();\n");
3.240 + body.append(" this.ws_").append(e.getSimpleName());
3.241 + body.append("= org.apidesign.html.json.impl.JSON.openWS(context, pr,\n ");
3.242 + body.append(urlBefore).append(", data);\n");
3.243 + body.append(" } else {\n");
3.244 + body.append(" this.ws_").append(e.getSimpleName()).append(".send(").append(urlBefore).append(", data);\n");
3.245 + body.append(" }\n");
3.246 + return false;
3.247 }
3.248
3.249 private CharSequence wrapParams(
4.1 --- a/json/src/main/java/org/apidesign/html/json/spi/JSONCall.java Sun Aug 25 12:18:02 2013 +0200
4.2 +++ b/json/src/main/java/org/apidesign/html/json/spi/JSONCall.java Sun Aug 25 12:18:25 2013 +0200
4.3 @@ -83,11 +83,19 @@
4.4 }
4.5
4.6 public void notifySuccess(Object result) {
4.7 - RcvrJSON.MsgEvnt.createMessage(result).dispatch(whenDone);
4.8 + if (result == null) {
4.9 + RcvrJSON.MsgEvnt.createOpen().dispatch(whenDone);
4.10 + } else {
4.11 + RcvrJSON.MsgEvnt.createMessage(result).dispatch(whenDone);
4.12 + }
4.13 }
4.14
4.15 public void notifyError(Throwable error) {
4.16 - RcvrJSON.MsgEvnt.createError(error).dispatch(whenDone);
4.17 + if (error == null) {
4.18 + RcvrJSON.MsgEvnt.createClose().dispatch(whenDone);
4.19 + } else {
4.20 + RcvrJSON.MsgEvnt.createError(error).dispatch(whenDone);
4.21 + }
4.22 }
4.23
4.24 public String getMessage() {
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/json/src/main/java/org/apidesign/html/json/spi/WSTransfer.java Sun Aug 25 12:18:25 2013 +0200
5.3 @@ -0,0 +1,62 @@
5.4 +/**
5.5 + * HTML via Java(tm) Language Bindings
5.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
5.7 + *
5.8 + * This program is free software: you can redistribute it and/or modify
5.9 + * it under the terms of the GNU General Public License as published by
5.10 + * the Free Software Foundation, version 2 of the License.
5.11 + *
5.12 + * This program is distributed in the hope that it will be useful,
5.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
5.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5.15 + * GNU General Public License for more details. apidesign.org
5.16 + * designates this particular file as subject to the
5.17 + * "Classpath" exception as provided by apidesign.org
5.18 + * in the License file that accompanied this code.
5.19 + *
5.20 + * You should have received a copy of the GNU General Public License
5.21 + * along with this program. Look for COPYING file in the top folder.
5.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
5.23 + */
5.24 +package org.apidesign.html.json.spi;
5.25 +
5.26 +import net.java.html.BrwsrCtx;
5.27 +import org.apidesign.html.context.spi.Contexts;
5.28 +
5.29 +/** Interface for providers of WebSocket protocol. Register into a
5.30 + * {@link BrwsrCtx context} in your own {@link Contexts.Provider}
5.31 + *
5.32 + * @author Jaroslav Tulach <jtulach@netbeans.org>
5.33 + * @param <WebSocket> internal implementation type representing the socket
5.34 + * @since 0.5
5.35 + */
5.36 +public interface WSTransfer<WebSocket> {
5.37 + /** Initializes a web socket. The <code>callback</code> object should
5.38 + * have mostly empty values: {@link JSONCall#isDoOutput()} should be
5.39 + * <code>false</code> and thus there should be no {@link JSONCall#getMessage()}.
5.40 + * The method of connection {@link JSONCall#getMethod()} is "WebSocket".
5.41 + * Once the connection is open call {@link JSONCall#notifySuccess(java.lang.Object) notifySuccess(null)}.
5.42 + * When the server sends some data then, pass them to
5.43 + * {@link JSONCall#notifySuccess(java.lang.Object) notifySuccess} method
5.44 + * as well. If there is an error call {@link JSONCall#notifyError(java.lang.Throwable)}.
5.45 + *
5.46 + * @param url the URL to connect to
5.47 + * @param callback a way to provide results back to the client
5.48 + * @return your object representing the established web socket
5.49 + */
5.50 + public WebSocket open(String url, JSONCall callback);
5.51 +
5.52 + /** Sends data to the server. The most important value
5.53 + * of the <code>data</code> parameter is {@link JSONCall#getMessage()},
5.54 + * rest can be ignored.
5.55 + *
5.56 + * @param socket internal representation of the socket
5.57 + * @param data the message to be sent
5.58 + */
5.59 + public void send(WebSocket socket, JSONCall data);
5.60 +
5.61 + /** A request to close the socket.
5.62 + * @param socket internal representation of the socket
5.63 + */
5.64 + public void close(WebSocket socket);
5.65 +}
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/json/src/test/java/net/java/html/json/WebSocketCallTest.java Sun Aug 25 12:18:25 2013 +0200
6.3 @@ -0,0 +1,34 @@
6.4 +/**
6.5 + * HTML via Java(tm) Language Bindings
6.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
6.7 + *
6.8 + * This program is free software: you can redistribute it and/or modify
6.9 + * it under the terms of the GNU General Public License as published by
6.10 + * the Free Software Foundation, version 2 of the License.
6.11 + *
6.12 + * This program is distributed in the hope that it will be useful,
6.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
6.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6.15 + * GNU General Public License for more details. apidesign.org
6.16 + * designates this particular file as subject to the
6.17 + * "Classpath" exception as provided by apidesign.org
6.18 + * in the License file that accompanied this code.
6.19 + *
6.20 + * You should have received a copy of the GNU General Public License
6.21 + * along with this program. Look for COPYING file in the top folder.
6.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
6.23 + */
6.24 +package net.java.html.json;
6.25 +
6.26 +/**
6.27 + *
6.28 + * @author Jaroslav Tulach <jtulach@netbeans.org>
6.29 + */
6.30 +@Model(className = "WebSocketCallTestMode", properties = {
6.31 + @Property(name = "nic", type = int.class)
6.32 +})
6.33 +public class WebSocketCallTest {
6.34 + @OnReceive(method = "WebSocket", data = Person.class, url="{url}")
6.35 + static void wsCall(WebSocketCallTestMode model, Person data) {
6.36 + }
6.37 +}
7.1 --- a/ko-fx/src/main/java/org/apidesign/html/kofx/FXContext.java Sun Aug 25 12:18:02 2013 +0200
7.2 +++ b/ko-fx/src/main/java/org/apidesign/html/kofx/FXContext.java Sun Aug 25 12:18:25 2013 +0200
7.3 @@ -33,6 +33,7 @@
7.4 import org.apidesign.html.json.spi.PropertyBinding;
7.5 import org.apidesign.html.json.spi.Technology;
7.6 import org.apidesign.html.json.spi.Transfer;
7.7 +import org.apidesign.html.json.spi.WSTransfer;
7.8 import org.openide.util.lookup.ServiceProvider;
7.9
7.10 /** This is an implementation package - just
7.11 @@ -45,7 +46,7 @@
7.12 */
7.13 @ServiceProvider(service = Contexts.Provider.class)
7.14 public final class FXContext
7.15 -implements Technology<JSObject>, Transfer, Contexts.Provider {
7.16 +implements Technology<JSObject>, Transfer, Contexts.Provider, WSTransfer<LoadWS> {
7.17 static final Logger LOG = Logger.getLogger(FXContext.class.getName());
7.18 private static Boolean javaScriptEnabled;
7.19
7.20 @@ -66,9 +67,18 @@
7.21 if (isJavaScriptEnabled()) {
7.22 context.register(Technology.class, this, 100);
7.23 context.register(Transfer.class, this, 100);
7.24 + if (areWebSocketsSupported()) {
7.25 + context.register(WSTransfer.class, this, 100);
7.26 + } else {
7.27 + throw new IllegalStateException();
7.28 + }
7.29 }
7.30 }
7.31
7.32 + final boolean areWebSocketsSupported() {
7.33 + return LoadWS.isSupported();
7.34 + }
7.35 +
7.36 @Override
7.37 public JSObject wrapModel(Object model) {
7.38 return (JSObject) Knockout.createBinding(model).koData();
7.39 @@ -128,4 +138,19 @@
7.40 public void runSafe(Runnable r) {
7.41 Platform.runLater(r);
7.42 }
7.43 +
7.44 + @Override
7.45 + public LoadWS open(String url, JSONCall onReply) {
7.46 + return new LoadWS(onReply, url);
7.47 + }
7.48 +
7.49 + @Override
7.50 + public void send(LoadWS socket, JSONCall data) {
7.51 + socket.send(data);
7.52 + }
7.53 +
7.54 + @Override
7.55 + public void close(LoadWS socket) {
7.56 + socket.close();
7.57 + }
7.58 }
8.1 --- a/ko-fx/src/main/java/org/apidesign/html/kofx/LoadJSON.java Sun Aug 25 12:18:02 2013 +0200
8.2 +++ b/ko-fx/src/main/java/org/apidesign/html/kofx/LoadJSON.java Sun Aug 25 12:18:25 2013 +0200
8.3 @@ -81,11 +81,8 @@
8.4 }
8.5
8.6 public static void loadJSON(JSONCall call) {
8.7 - if ("WebSocket".equals(call.getMethod())) { // NOI18N
8.8 - LoadWS.send(call);
8.9 - } else {
8.10 - REQ.execute(new LoadJSON((call)));
8.11 - }
8.12 + assert !"WebSocket".equals(call.getMethod());
8.13 + REQ.execute(new LoadJSON((call)));
8.14 }
8.15
8.16 @Override
9.1 --- a/ko-fx/src/main/java/org/apidesign/html/kofx/LoadWS.java Sun Aug 25 12:18:02 2013 +0200
9.2 +++ b/ko-fx/src/main/java/org/apidesign/html/kofx/LoadWS.java Sun Aug 25 12:18:25 2013 +0200
9.3 @@ -20,12 +20,6 @@
9.4 */
9.5 package org.apidesign.html.kofx;
9.6
9.7 -import java.io.ByteArrayOutputStream;
9.8 -import java.io.IOException;
9.9 -import java.util.ArrayDeque;
9.10 -import java.util.Deque;
9.11 -import java.util.HashMap;
9.12 -import java.util.Map;
9.13 import net.java.html.js.JavaScriptBody;
9.14 import org.apidesign.html.json.spi.JSONCall;
9.15 import org.json.JSONArray;
9.16 @@ -38,64 +32,34 @@
9.17 * @author Jaroslav Tulach <jtulach@netbeans.org>
9.18 */
9.19 final class LoadWS {
9.20 - private static final Map<String,LoadWS> CONNECTIONS = new HashMap<String, LoadWS>();
9.21 + private static final boolean SUPPORTED = isWebSocket();
9.22 private final Object ws;
9.23 - private Deque<JSONCall> pending = new ArrayDeque<JSONCall>();
9.24 private final JSONCall call;
9.25
9.26 - private LoadWS(JSONCall first, String url) {
9.27 + LoadWS(JSONCall first, String url) {
9.28 call = first;
9.29 ws = initWebSocket(this, url);
9.30 - }
9.31 -
9.32 - static void send(JSONCall call) {
9.33 - String url = call.composeURL(null);
9.34 - LoadWS load = CONNECTIONS.get(url);
9.35 - if (load == null) {
9.36 - load = new LoadWS(call, url);
9.37 - if (load.ws != null) {
9.38 - CONNECTIONS.put(url, load);
9.39 - } else {
9.40 - call.notifyError(new UnsupportedOperationException("WebSocket API is not supported"));
9.41 - return;
9.42 - }
9.43 - } else {
9.44 - if (!call.isDoOutput()) {
9.45 - close(load.ws);
9.46 - }
9.47 - }
9.48 - if (call.isDoOutput()) {
9.49 - load.push(call);
9.50 + if (ws == null) {
9.51 + first.notifyError(new IllegalArgumentException("Wrong URL: " + url));
9.52 }
9.53 }
9.54
9.55 + static boolean isSupported() {
9.56 + return SUPPORTED;
9.57 + }
9.58 +
9.59 + void send(JSONCall call) {
9.60 + push(call);
9.61 + }
9.62 +
9.63 private synchronized void push(JSONCall call) {
9.64 - if (pending != null) {
9.65 - pending.add(call);
9.66 - } else {
9.67 - try {
9.68 - ByteArrayOutputStream os = new ByteArrayOutputStream();
9.69 - call.writeData(os);
9.70 - String msg = new String(os.toByteArray(), "UTF-8");
9.71 - send(ws, msg);
9.72 - } catch (IOException ex) {
9.73 - call.notifyError(ex);
9.74 - }
9.75 - }
9.76 + send(ws, call.getMessage());
9.77 }
9.78
9.79 void onOpen(Object ev) {
9.80 - Deque<JSONCall> p;
9.81 - synchronized (this) {
9.82 - p = pending;
9.83 - pending = null;
9.84 - }
9.85 if (!call.isDoOutput()) {
9.86 call.notifySuccess(null);
9.87 }
9.88 - for (JSONCall c : p) {
9.89 - push(c);
9.90 - }
9.91 }
9.92
9.93 void onMessage(Object ev, String data) {
9.94 @@ -120,6 +84,11 @@
9.95 void onClose(boolean wasClean, int code, String reason) {
9.96 call.notifyError(null);
9.97 }
9.98 +
9.99 + @JavaScriptBody(args = {}, body = "if (window.WebSocket) return true; else return false;")
9.100 + private static boolean isWebSocket() {
9.101 + return false;
9.102 + }
9.103
9.104 @JavaScriptBody(args = { "back", "url" }, javacall = true, body = ""
9.105 + "if (window.WebSocket) {"
9.106 @@ -151,4 +120,8 @@
9.107 @JavaScriptBody(args = { "ws" }, body = "ws.close();")
9.108 private static void close(Object ws) {
9.109 }
9.110 +
9.111 + void close() {
9.112 + close(ws);
9.113 + }
9.114 }
10.1 --- a/ko-fx/src/test/java/org/apidesign/html/kofx/KOFx.java Sun Aug 25 12:18:02 2013 +0200
10.2 +++ b/ko-fx/src/test/java/org/apidesign/html/kofx/KOFx.java Sun Aug 25 12:18:25 2013 +0200
10.3 @@ -34,6 +34,7 @@
10.4 private final Method m;
10.5 private Object result;
10.6 private Object inst;
10.7 + private int count;
10.8
10.9 KOFx(Method m) {
10.10 this.m = m;
10.11 @@ -72,9 +73,11 @@
10.12 } catch (InvocationTargetException ex) {
10.13 Throwable r = ex.getTargetException();
10.14 if (r instanceof InterruptedException) {
10.15 - notify = false;
10.16 - Platform.runLater(this);
10.17 - return;
10.18 + if (count++ < 10000) {
10.19 + notify = false;
10.20 + Platform.runLater(this);
10.21 + return;
10.22 + }
10.23 }
10.24 result = r;
10.25 } catch (Exception ex) {
11.1 --- a/ko-fx/src/test/java/org/apidesign/html/kofx/KnockoutFXTest.java Sun Aug 25 12:18:02 2013 +0200
11.2 +++ b/ko-fx/src/test/java/org/apidesign/html/kofx/KnockoutFXTest.java Sun Aug 25 12:18:25 2013 +0200
11.3 @@ -39,6 +39,7 @@
11.4 import org.apidesign.html.context.spi.Contexts;
11.5 import org.apidesign.html.json.spi.Technology;
11.6 import org.apidesign.html.json.spi.Transfer;
11.7 +import org.apidesign.html.json.spi.WSTransfer;
11.8 import org.apidesign.html.json.tck.KOTest;
11.9 import org.apidesign.html.json.tck.KnockoutTCK;
11.10 import org.json.JSONException;
11.11 @@ -118,10 +119,13 @@
11.12 @Override
11.13 public BrwsrCtx createContext() {
11.14 FXContext fx = new FXContext();
11.15 - return Contexts.newBuilder().
11.16 + Contexts.Builder cb = Contexts.newBuilder().
11.17 register(Technology.class, fx, 10).
11.18 - register(Transfer.class, fx, 10).
11.19 - build();
11.20 + register(Transfer.class, fx, 10);
11.21 + if (fx.areWebSocketsSupported()) {
11.22 + cb.register(WSTransfer.class, fx, 10);
11.23 + }
11.24 + return cb.build();
11.25 }
11.26
11.27 @Override