More intricate ways to control WebSocket connections
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Fri, 23 Aug 2013 22:23:30 +0200
changeset 250b5c23b27facf
parent 249 18430f810b7e
child 251 d44121b6a897
More intricate ways to control WebSocket connections
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
ko-fx/src/main/java/org/apidesign/html/kofx/LoadWS.java
     1.1 --- a/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java	Fri Aug 23 09:42:19 2013 +0200
     1.2 +++ b/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java	Fri Aug 23 22:23:30 2013 +0200
     1.3 @@ -34,6 +34,7 @@
     1.4  @Model(className = "WebSocketik", properties = {
     1.5      @Property(name = "fetched", type = Person.class),
     1.6      @Property(name = "fetchedCount", type = int.class),
     1.7 +    @Property(name = "open", type = int.class),
     1.8      @Property(name = "fetchedResponse", type = String.class),
     1.9      @Property(name = "fetchedSex", type = Sex.class, array = true)
    1.10  })
    1.11 @@ -43,7 +44,11 @@
    1.12      
    1.13      @OnReceive(url = "{url}", data = Sex.class, method = "WebSocket", onError = "error")
    1.14      static void querySex(WebSocketik model, Person data) {
    1.15 -        model.setFetched(data);
    1.16 +        if (data == null) {
    1.17 +            model.setOpen(1);
    1.18 +        } else {
    1.19 +            model.setFetched(data);
    1.20 +        }
    1.21      }
    1.22      
    1.23      @KOTest public void connectUsingWebSocket() throws Throwable {
    1.24 @@ -58,12 +63,23 @@
    1.25              js.applyBindings();
    1.26  
    1.27              js.setFetched(null);
    1.28 -            js.querySex(url, Sex.FEMALE);
    1.29 +            
    1.30 +            // connects to the server
    1.31 +            js.querySex(url, null);
    1.32          }
    1.33          
    1.34          if (bailOutIfNotSupported(js)) {
    1.35              return;
    1.36          }
    1.37 +        
    1.38 +        if (js.getOpen() == 0) {
    1.39 +            throw new InterruptedException();
    1.40 +        }
    1.41 +        if (js.getOpen() == 1) {
    1.42 +            // send a query to the server
    1.43 +            js.querySex(url, Sex.FEMALE);
    1.44 +            js.setOpen(2);
    1.45 +        }
    1.46      
    1.47          Person p = js.getFetched();
    1.48          if (p == null) {
    1.49 @@ -72,6 +88,17 @@
    1.50          
    1.51          assert "Mitar".equals(p.getFirstName()) : "Unexpected: " + p.getFirstName();
    1.52          assert Sex.FEMALE.equals(p.getSex()) : "Expecting FEMALE: " + p.getSex();
    1.53 +
    1.54 +        if (js.getOpen() == 2) {
    1.55 +            // close the socket
    1.56 +            js.querySex(url, null);
    1.57 +            js.setOpen(3);
    1.58 +        }
    1.59 +        
    1.60 +        if (js.getFetchedResponse() == null) {
    1.61 +            throw new InterruptedException();
    1.62 +        }
    1.63 +        assert "null".equals(js.getFetchedResponse()) : "Should be null: " + js.getFetchedResponse();
    1.64      }
    1.65      
    1.66      @KOTest public void errorUsingWebSocket() throws Throwable {
    1.67 @@ -85,7 +112,11 @@
    1.68      }
    1.69      
    1.70      static void error(WebSocketik model, Exception ex) {
    1.71 -        model.setFetchedResponse(ex.getClass() + ":" + ex.getMessage());
    1.72 +        if (ex != null) {
    1.73 +            model.setFetchedResponse(ex.getClass() + ":" + ex.getMessage());
    1.74 +        } else {
    1.75 +            model.setFetchedResponse("null");
    1.76 +        }
    1.77      }
    1.78      
    1.79      private static BrwsrCtx newContext() {
     2.1 --- a/json/src/main/java/org/apidesign/html/json/impl/JSON.java	Fri Aug 23 09:42:19 2013 +0200
     2.2 +++ b/json/src/main/java/org/apidesign/html/json/impl/JSON.java	Fri Aug 23 22:23:30 2013 +0200
     2.3 @@ -37,6 +37,9 @@
     2.4   * @author Jaroslav Tulach <jtulach@netbeans.org>
     2.5   */
     2.6  public final class JSON {
     2.7 +    /** represents null exception value */
     2.8 +    public static final Exception NULL = new NullPointerException();
     2.9 +    
    2.10      private JSON() {
    2.11      }
    2.12  
    2.13 @@ -132,6 +135,16 @@
    2.14      public static Character charValue(Object val) {
    2.15          return (Character)val;
    2.16      }
    2.17 +    
    2.18 +    public static Exception excValue(Object val) {
    2.19 +        if (val == NULL) {
    2.20 +            return null;
    2.21 +        }
    2.22 +        if (val instanceof Exception) {
    2.23 +            return (Exception)val;
    2.24 +        }
    2.25 +        return new Exception(val.toString());
    2.26 +    }
    2.27  
    2.28      public static Boolean boolValue(Object val) {
    2.29          if (val instanceof String) {
    2.30 @@ -196,6 +209,9 @@
    2.31          return read(c, modelClazz, tr.toJSON((InputStream)data));
    2.32      }
    2.33      public static <T> T read(BrwsrCtx c, Class<T> modelClazz, Object data) {
    2.34 +        if (data == null) {
    2.35 +            return null;
    2.36 +        }
    2.37          if (modelClazz == String.class) {
    2.38              return modelClazz.cast(data.toString());
    2.39          }
     3.1 --- a/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java	Fri Aug 23 09:42:19 2013 +0200
     3.2 +++ b/json/src/main/java/org/apidesign/html/json/impl/ModelProcessor.java	Fri Aug 23 22:23:30 2013 +0200
     3.3 @@ -952,6 +952,12 @@
     3.4                  error("The method needs to have one @Model class as parameter", e);
     3.5              }
     3.6              String n = e.getSimpleName().toString();
     3.7 +            if ("WebSocket".equals(onR.method())) {
     3.8 +                body.append("  /** Performs WebSocket communication. Call with <code>null</code> data parameter\n");
     3.9 +                body.append("  * to open the connection (even if not required). Call with non-null data to\n");
    3.10 +                body.append("  * send messages to server. Call again with <code>null</code> data to close the socket.\n");
    3.11 +                body.append("  */\n");
    3.12 +            }
    3.13              body.append("  public void ").append(n).append("(");
    3.14              StringBuilder urlBefore = new StringBuilder();
    3.15              StringBuilder urlAfter = new StringBuilder();
    3.16 @@ -996,7 +1002,7 @@
    3.17                      return false;
    3.18                  }
    3.19                  body.append("          ").append(clazz.getSimpleName()).append(".").append(onR.onError()).append("(");
    3.20 -                body.append(className).append(".this, (Exception)value); return;\n");
    3.21 +                body.append(className).append(".this, org.apidesign.html.json.impl.JSON.excValue(value)); return;\n");
    3.22              }
    3.23              body.append(
    3.24                  "        }\n"
     4.1 --- a/json/src/main/java/org/apidesign/html/json/spi/JSONCall.java	Fri Aug 23 09:42:19 2013 +0200
     4.2 +++ b/json/src/main/java/org/apidesign/html/json/spi/JSONCall.java	Fri Aug 23 22:23:30 2013 +0200
     4.3 @@ -23,6 +23,7 @@
     4.4  
     4.5  import java.io.IOException;
     4.6  import java.io.OutputStream;
     4.7 +import org.apidesign.html.json.impl.JSON;
     4.8  
     4.9  /** Description of a JSON call request that is supposed to be processed
    4.10   * by {@link Transfer#loadJSON(org.apidesign.html.json.spi.JSONCall)} implementors.
    4.11 @@ -88,7 +89,7 @@
    4.12      }
    4.13      
    4.14      public void notifyError(Throwable error) {
    4.15 -        this.result[0] = error;
    4.16 +        this.result[0] = error == null ? JSON.NULL : error;
    4.17          this.whenDone.run();
    4.18      }
    4.19  }
     5.1 --- a/ko-fx/src/main/java/org/apidesign/html/kofx/LoadWS.java	Fri Aug 23 09:42:19 2013 +0200
     5.2 +++ b/ko-fx/src/main/java/org/apidesign/html/kofx/LoadWS.java	Fri Aug 23 22:23:30 2013 +0200
     5.3 @@ -59,8 +59,14 @@
     5.4                  call.notifyError(new UnsupportedOperationException("WebSocket API is not supported"));
     5.5                  return;
     5.6              }
     5.7 +        } else {
     5.8 +            if (!call.isDoOutput()) {
     5.9 +                close(load.ws);
    5.10 +            }
    5.11          }
    5.12 -        load.push(call);
    5.13 +        if (call.isDoOutput()) {
    5.14 +            load.push(call);
    5.15 +        }
    5.16      }
    5.17      
    5.18      private synchronized void push(JSONCall call) {
    5.19 @@ -78,9 +84,15 @@
    5.20          }
    5.21      }
    5.22  
    5.23 -    synchronized void onOpen(Object ev) {
    5.24 -        Deque<JSONCall> p = pending;
    5.25 -        pending = null;
    5.26 +    void onOpen(Object ev) {
    5.27 +        Deque<JSONCall> p;
    5.28 +        synchronized (this) {
    5.29 +            p = pending;
    5.30 +            pending = null;
    5.31 +        }
    5.32 +        if (!call.isDoOutput()) {
    5.33 +            call.notifySuccess(null);
    5.34 +        }
    5.35          for (JSONCall c : p) {
    5.36              push(c);
    5.37          }
    5.38 @@ -105,13 +117,18 @@
    5.39          call.notifyError(new Exception(ev.toString()));
    5.40      }
    5.41  
    5.42 +    void onClose(boolean wasClean, int code, String reason) {
    5.43 +        call.notifyError(null);
    5.44 +    }
    5.45 +
    5.46      @JavaScriptBody(args = { "back", "url" }, javacall = true, body = ""
    5.47          + "if (window.WebSocket) {"
    5.48          + "  try {"
    5.49          + "    var ws = new window.WebSocket(url);"
    5.50          + "    ws.onopen = function(ev) { back.@org.apidesign.html.kofx.LoadWS::onOpen(Ljava/lang/Object;)(ev); };"
    5.51          + "    ws.onmessage = function(ev) { back.@org.apidesign.html.kofx.LoadWS::onMessage(Ljava/lang/Object;Ljava/lang/String;)(ev, ev.data); };"
    5.52 -        + "    ws.onclose = function(ev) { back.@org.apidesign.html.kofx.LoadWS::onError(Ljava/lang/Object;)(ev); };"
    5.53 +        + "    ws.onerror = function(ev) { back.@org.apidesign.html.kofx.LoadWS::onError(Ljava/lang/Object;)(ev); };"
    5.54 +        + "    ws.onclose = function(ev) { back.@org.apidesign.html.kofx.LoadWS::onClose(ZILjava/lang/String;)(ev.wasClean, ev.code, ev.reason); };"
    5.55          + "    return ws;"
    5.56          + "  } catch (ex) {"
    5.57          + "    return null;"
    5.58 @@ -130,4 +147,8 @@
    5.59      )
    5.60      private void send(Object ws, String msg) {
    5.61      }
    5.62 +
    5.63 +    @JavaScriptBody(args = { "ws" }, body = "ws.close();")
    5.64 +    private static void close(Object ws) {
    5.65 +    }
    5.66  }