Introducing WSTransfer and making it work on JDK8
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Sun, 25 Aug 2013 12:18:25 +0200
changeset 2585f446d17f6fa
parent 257 189752078751
child 259 7bdeb18a95ad
Introducing WSTransfer and making it work on JDK8
json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java
json/src/main/java/org/apidesign/html/json/impl/JSON.java
json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java
json/src/main/java/org/apidesign/html/json/spi/JSONCall.java
json/src/main/java/org/apidesign/html/json/spi/WSTransfer.java
json/src/test/java/net/java/html/json/WebSocketCallTest.java
ko-fx/src/main/java/org/apidesign/html/kofx/FXContext.java
ko-fx/src/main/java/org/apidesign/html/kofx/LoadJSON.java
ko-fx/src/main/java/org/apidesign/html/kofx/LoadWS.java
ko-fx/src/test/java/org/apidesign/html/kofx/KOFx.java
ko-fx/src/test/java/org/apidesign/html/kofx/KnockoutFXTest.java
     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