Tyrus based implementation of WebSockets for JDK7
authorJaroslav Tulach <jaroslav.tulach@apidesign.org>
Sun, 25 Aug 2013 14:40:16 +0200
changeset 26023e2ad7e6d23
parent 259 7bdeb18a95ad
child 261 58b5ca16bf59
Tyrus based implementation of WebSockets for JDK7
json-tck/src/main/java/net/java/html/json/tests/Utils.java
json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java
json-tck/src/main/java/org/apidesign/html/json/tck/KnockoutTCK.java
ko-fx/src/test/java/org/apidesign/html/kofx/KnockoutFXTest.java
ko-ws-tyrus/pom.xml
ko-ws-tyrus/src/main/java/org/apidesign/html/wstyrus/TyrusContext.java
ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusDynamicHTTP.java
ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusFX.java
ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusKnockoutTest.java
ko-ws-tyrus/src/test/resources/org/apidesign/html/wstyrus/test.html
pom.xml
     1.1 --- a/json-tck/src/main/java/net/java/html/json/tests/Utils.java	Sun Aug 25 12:35:32 2013 +0200
     1.2 +++ b/json-tck/src/main/java/net/java/html/json/tests/Utils.java	Sun Aug 25 14:40:16 2013 +0200
     1.3 @@ -21,7 +21,6 @@
     1.4  package net.java.html.json.tests;
     1.5  
     1.6  import java.net.URI;
     1.7 -import java.net.URL;
     1.8  import java.util.Map;
     1.9  import java.util.ServiceLoader;
    1.10  import net.java.html.BrwsrCtx;
    1.11 @@ -85,6 +84,16 @@
    1.12          }
    1.13          throw new IllegalStateException();
    1.14      }
    1.15 +
    1.16 +    static boolean canFailWebSockets(
    1.17 +        Class<?> clazz) {
    1.18 +        for (KnockoutTCK tck : ServiceLoader.load(KnockoutTCK.class, cl(clazz))) {
    1.19 +            if (tck.canFailWebSocketTest()) {
    1.20 +                return true;
    1.21 +            }
    1.22 +        }
    1.23 +        return false;
    1.24 +    }
    1.25      
    1.26      private static ClassLoader cl(Class<?> c) {
    1.27          try {
     2.1 --- a/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java	Sun Aug 25 12:35:32 2013 +0200
     2.2 +++ b/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java	Sun Aug 25 14:40:16 2013 +0200
     2.3 @@ -140,7 +140,8 @@
     2.4          if (js.getFetchedResponse() == null) {
     2.5              return false;
     2.6          }
     2.7 -        return js.getFetchedResponse().contains("UnsupportedOperationException");
     2.8 +        return js.getFetchedResponse().contains("UnsupportedOperationException") &&
     2.9 +            Utils.canFailWebSockets(WebSocketTest.class);
    2.10      }
    2.11      
    2.12  }
     3.1 --- a/json-tck/src/main/java/org/apidesign/html/json/tck/KnockoutTCK.java	Sun Aug 25 12:35:32 2013 +0200
     3.2 +++ b/json-tck/src/main/java/org/apidesign/html/json/tck/KnockoutTCK.java	Sun Aug 25 14:40:16 2013 +0200
     3.3 @@ -97,5 +97,14 @@
     3.4          };
     3.5      }
     3.6  
     3.7 +    /** Some implementations cannot fully support web sockets and fail.
     3.8 +     * 
     3.9 +     * @return true, if UnsupportedOperationException reported from a web
    3.10 +     *    socket open operation is acceptable reply
    3.11 +     */
    3.12 +    public boolean canFailWebSocketTest() {
    3.13 +        return false;
    3.14 +    }
    3.15 +
    3.16  
    3.17  }
     4.1 --- a/ko-fx/src/test/java/org/apidesign/html/kofx/KnockoutFXTest.java	Sun Aug 25 12:35:32 2013 +0200
     4.2 +++ b/ko-fx/src/test/java/org/apidesign/html/kofx/KnockoutFXTest.java	Sun Aug 25 14:40:16 2013 +0200
     4.3 @@ -182,4 +182,15 @@
     4.4              throw new IllegalStateException(ex);
     4.5          }
     4.6      }
     4.7 +
     4.8 +    @Override
     4.9 +    public boolean canFailWebSocketTest() {
    4.10 +        try {
    4.11 +            Class.forName("java.util.function.Function");
    4.12 +            return false;
    4.13 +        } catch (ClassNotFoundException ex) {
    4.14 +            // running on JDK7, FX WebView WebSocket impl does not work
    4.15 +            return true;
    4.16 +        }
    4.17 +    }
    4.18  }
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/ko-ws-tyrus/pom.xml	Sun Aug 25 14:40:16 2013 +0200
     5.3 @@ -0,0 +1,143 @@
     5.4 +<?xml version="1.0"?>
     5.5 +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
     5.6 +    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
     5.7 +  <modelVersion>4.0.0</modelVersion>
     5.8 +  <parent>
     5.9 +    <groupId>org.apidesign</groupId>
    5.10 +    <artifactId>html</artifactId>
    5.11 +    <version>0.5-SNAPSHOT</version>
    5.12 +  </parent>
    5.13 +  <groupId>org.apidesign.html</groupId>
    5.14 +  <artifactId>ko-ws-tyrus</artifactId>
    5.15 +  <version>0.5-SNAPSHOT</version>
    5.16 +  <name>Tyrus Based WebSockets</name>
    5.17 +  <url>http://maven.apache.org</url>
    5.18 +    <build>
    5.19 +        <plugins>
    5.20 +            <plugin>
    5.21 +                <groupId>org.apache.maven.plugins</groupId>
    5.22 +                <artifactId>maven-compiler-plugin</artifactId>
    5.23 +                <version>2.3.2</version>
    5.24 +                <configuration>
    5.25 +                    <source>1.7</source>
    5.26 +                    <target>1.7</target>
    5.27 +                </configuration>
    5.28 +            </plugin>
    5.29 +        </plugins>
    5.30 +    </build>
    5.31 +    <properties>
    5.32 +    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    5.33 +  </properties>
    5.34 +  <dependencies>
    5.35 +    <!-- compile only deps -->
    5.36 +    <dependency>
    5.37 +      <groupId>org.netbeans.api</groupId>
    5.38 +      <artifactId>org-openide-util-lookup</artifactId>
    5.39 +      <type>jar</type>
    5.40 +      <scope>provided</scope>
    5.41 +    </dependency>
    5.42 +
    5.43 +    <!-- compile + runtime -->      
    5.44 +    <dependency>
    5.45 +      <groupId>${project.groupId}</groupId>
    5.46 +      <artifactId>net.java.html</artifactId>
    5.47 +      <version>${project.version}</version>
    5.48 +      <type>jar</type>
    5.49 +    </dependency>
    5.50 +    <dependency>
    5.51 +      <groupId>${project.groupId}</groupId>
    5.52 +      <artifactId>net.java.html.json</artifactId>
    5.53 +      <version>${project.version}</version>
    5.54 +      <type>jar</type>
    5.55 +    </dependency>
    5.56 +    <dependency>
    5.57 +      <groupId>org.json</groupId>
    5.58 +      <artifactId>json</artifactId>
    5.59 +      <version>20090211</version>
    5.60 +      <type>jar</type>
    5.61 +    </dependency>
    5.62 +    <dependency>
    5.63 +      <artifactId>javax.websocket-api</artifactId>
    5.64 +      <groupId>javax.websocket</groupId>
    5.65 +      <type>jar</type>
    5.66 +      <version>1.0</version>
    5.67 +    </dependency>
    5.68 +
    5.69 +    <!-- tyrus runtime -->    
    5.70 +    <dependency>
    5.71 +        <groupId>org.glassfish.tyrus</groupId>
    5.72 +        <artifactId>tyrus-client</artifactId>
    5.73 +        <version>1.2.1</version>
    5.74 +        <scope>runtime</scope>
    5.75 +    </dependency>
    5.76 +    <dependency>
    5.77 +        <groupId>org.glassfish.tyrus</groupId>
    5.78 +        <artifactId>tyrus-container-grizzly</artifactId>
    5.79 +        <version>1.2.1</version>
    5.80 +        <scope>runtime</scope>
    5.81 +    </dependency>
    5.82 +    
    5.83 +    
    5.84 +    
    5.85 +    <!-- test only deps -->
    5.86 +    <dependency>
    5.87 +      <groupId>${project.groupId}</groupId>
    5.88 +      <artifactId>net.java.html.boot</artifactId>
    5.89 +      <version>${project.version}</version>
    5.90 +      <scope>test</scope>
    5.91 +      <type>jar</type>
    5.92 +    </dependency>
    5.93 +    <dependency>
    5.94 +      <groupId>${project.groupId}</groupId>
    5.95 +      <artifactId>net.java.html.json.tck</artifactId>
    5.96 +      <version>${project.version}</version>
    5.97 +      <scope>test</scope>
    5.98 +      <type>jar</type>
    5.99 +    </dependency>
   5.100 +    <dependency>
   5.101 +      <groupId>${project.groupId}</groupId>
   5.102 +      <artifactId>ko-fx</artifactId>
   5.103 +      <version>${project.version}</version>
   5.104 +      <scope>test</scope>
   5.105 +      <type>jar</type>
   5.106 +    </dependency>
   5.107 +    <dependency>
   5.108 +      <groupId>org.glassfish.grizzly</groupId>
   5.109 +      <artifactId>grizzly-http-server-core</artifactId>
   5.110 +      <version>2.3.3</version>
   5.111 +      <scope>test</scope>
   5.112 +      <type>jar</type>
   5.113 +    </dependency>
   5.114 +    <dependency>
   5.115 +      <groupId>org.glassfish.grizzly</groupId>
   5.116 +      <artifactId>grizzly-websockets-server</artifactId>
   5.117 +      <version>2.3.3</version>
   5.118 +      <scope>test</scope>
   5.119 +      <type>jar</type>
   5.120 +    </dependency>
   5.121 +    <dependency>
   5.122 +        <groupId>${project.groupId}</groupId>
   5.123 +        <artifactId>boot-fx</artifactId>
   5.124 +        <version>${project.version}</version>
   5.125 +        <scope>test</scope>
   5.126 +    </dependency>
   5.127 +    <dependency>
   5.128 +      <groupId>org.glassfish.grizzly</groupId>
   5.129 +      <artifactId>grizzly-http-server</artifactId>
   5.130 +      <version>2.3.3</version>
   5.131 +      <scope>test</scope>
   5.132 +    </dependency>
   5.133 +    <dependency>
   5.134 +        <groupId>org.glassfish.grizzly</groupId>
   5.135 +        <artifactId>grizzly-http-servlet</artifactId>
   5.136 +        <version>2.3.3</version>
   5.137 +        <scope>test</scope>
   5.138 +    </dependency>    
   5.139 +    <dependency>
   5.140 +        <groupId>javax.servlet</groupId>
   5.141 +        <artifactId>javax.servlet-api</artifactId>
   5.142 +        <scope>test</scope>
   5.143 +        <version>3.1.0</version>
   5.144 +    </dependency>
   5.145 +  </dependencies>
   5.146 +</project>
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/ko-ws-tyrus/src/main/java/org/apidesign/html/wstyrus/TyrusContext.java	Sun Aug 25 14:40:16 2013 +0200
     6.3 @@ -0,0 +1,159 @@
     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 org.apidesign.html.wstyrus;
    6.25 +
    6.26 +import java.io.IOException;
    6.27 +import java.net.URI;
    6.28 +import java.net.URISyntaxException;
    6.29 +import java.util.Iterator;
    6.30 +import javafx.application.Platform;
    6.31 +import javax.websocket.ClientEndpoint;
    6.32 +import javax.websocket.ContainerProvider;
    6.33 +import javax.websocket.DeploymentException;
    6.34 +import javax.websocket.OnClose;
    6.35 +import javax.websocket.OnError;
    6.36 +import javax.websocket.OnMessage;
    6.37 +import javax.websocket.OnOpen;
    6.38 +import javax.websocket.Session;
    6.39 +import javax.websocket.WebSocketContainer;
    6.40 +import org.apidesign.html.context.spi.Contexts;
    6.41 +import org.apidesign.html.json.spi.JSONCall;
    6.42 +import org.apidesign.html.json.spi.WSTransfer;
    6.43 +import org.apidesign.html.wstyrus.TyrusContext.Comm;
    6.44 +import org.json.JSONArray;
    6.45 +import org.json.JSONException;
    6.46 +import org.json.JSONObject;
    6.47 +import org.json.JSONTokener;
    6.48 +import org.openide.util.lookup.ServiceProvider;
    6.49 +
    6.50 +/**
    6.51 + *
    6.52 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    6.53 + */
    6.54 +@ServiceProvider(service = Contexts.Provider.class)
    6.55 +public final class TyrusContext implements Contexts.Provider, WSTransfer<Comm> {
    6.56 +    @Override
    6.57 +    public void fillContext(Contexts.Builder context, Class<?> requestor) {
    6.58 +        // default WebSocket transfer implementation is registered
    6.59 +        // in ko-fx module with 100, provide this one as a fallback only
    6.60 +        context.register(WSTransfer.class, this, 1000);
    6.61 +    }
    6.62 +
    6.63 +    @Override
    6.64 +    public Comm open(String url, JSONCall callback) {
    6.65 +        try {
    6.66 +            return new Comm(new URI(url), callback);
    6.67 +        } catch (URISyntaxException ex) {
    6.68 +            throw new IllegalStateException(ex);
    6.69 +        }
    6.70 +    }
    6.71 +
    6.72 +    @Override
    6.73 +    public void send(Comm socket, JSONCall data) {
    6.74 +        socket.session.getAsyncRemote().sendText(data.getMessage());
    6.75 +    }
    6.76 +
    6.77 +    @Override
    6.78 +    public void close(Comm socket) {
    6.79 +        try {
    6.80 +            socket.session.close();
    6.81 +        } catch (IOException ex) {
    6.82 +            socket.callback.notifyError(ex);
    6.83 +        }
    6.84 +    }
    6.85 +    
    6.86 +    @ClientEndpoint
    6.87 +    public static final class Comm {
    6.88 +        private final JSONCall callback;
    6.89 +        private Session session;
    6.90 +
    6.91 +        Comm(final URI url, JSONCall callback) {
    6.92 +            this.callback = callback;
    6.93 +            try {
    6.94 +                final WebSocketContainer c = ContainerProvider.getWebSocketContainer();
    6.95 +                c.connectToServer(Comm.this, url);
    6.96 +            } catch (DeploymentException | IOException ex) {
    6.97 +                wasAnError(ex);
    6.98 +            }
    6.99 +        }
   6.100 +
   6.101 +        @OnOpen
   6.102 +        public synchronized void open(Session s) {
   6.103 +            this.session = s;
   6.104 +            callback.notifySuccess(null);
   6.105 +        }
   6.106 +
   6.107 +        @OnClose
   6.108 +        public void close() {
   6.109 +            this.session = null;
   6.110 +            callback.notifyError(null);
   6.111 +        }
   6.112 +
   6.113 +        @OnMessage
   6.114 +        public void message(final String orig, Session s) {
   6.115 +            class R implements Runnable {
   6.116 +                Object json;
   6.117 +                public R() {
   6.118 +                    String data = orig.trim();
   6.119 +                    try {
   6.120 +                        JSONTokener tok = new JSONTokener(data);
   6.121 +                        Object obj = data.startsWith("[") ? new JSONArray(tok) : new JSONObject(tok);
   6.122 +                        json = convertToArray(obj);
   6.123 +                    } catch (JSONException ex) {
   6.124 +                        json = data;
   6.125 +                    }
   6.126 +                }
   6.127 +                @Override
   6.128 +                public void run() {
   6.129 +                    callback.notifySuccess(json);
   6.130 +                }
   6.131 +            }
   6.132 +            Platform.runLater(new R());
   6.133 +        }
   6.134 +
   6.135 +        @OnError
   6.136 +        public void wasAnError(Throwable t) {
   6.137 +            callback.notifyError(t);
   6.138 +        }
   6.139 +
   6.140 +        static Object convertToArray(Object o) throws JSONException {
   6.141 +            if (o instanceof JSONArray) {
   6.142 +                JSONArray ja = (JSONArray) o;
   6.143 +                Object[] arr = new Object[ja.length()];
   6.144 +                for (int i = 0; i < arr.length; i++) {
   6.145 +                    arr[i] = convertToArray(ja.get(i));
   6.146 +                }
   6.147 +                return arr;
   6.148 +            } else if (o instanceof JSONObject) {
   6.149 +                JSONObject obj = (JSONObject) o;
   6.150 +                Iterator it = obj.keys();
   6.151 +                while (it.hasNext()) {
   6.152 +                    String key = (String) it.next();
   6.153 +                    obj.put(key, convertToArray(obj.get(key)));
   6.154 +                }
   6.155 +                return obj;
   6.156 +            } else {
   6.157 +                return o;
   6.158 +            }
   6.159 +        }
   6.160 +        
   6.161 +    } // end of Comm
   6.162 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusDynamicHTTP.java	Sun Aug 25 14:40:16 2013 +0200
     7.3 @@ -0,0 +1,238 @@
     7.4 +/**
     7.5 + * HTML via Java(tm) Language Bindings
     7.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     7.7 + *
     7.8 + * This program is free software: you can redistribute it and/or modify
     7.9 + * it under the terms of the GNU General Public License as published by
    7.10 + * the Free Software Foundation, version 2 of the License.
    7.11 + *
    7.12 + * This program is distributed in the hope that it will be useful,
    7.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    7.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    7.15 + * GNU General Public License for more details. apidesign.org
    7.16 + * designates this particular file as subject to the
    7.17 + * "Classpath" exception as provided by apidesign.org
    7.18 + * in the License file that accompanied this code.
    7.19 + *
    7.20 + * You should have received a copy of the GNU General Public License
    7.21 + * along with this program. Look for COPYING file in the top folder.
    7.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    7.23 + */
    7.24 +package org.apidesign.html.wstyrus;
    7.25 +
    7.26 +import java.io.ByteArrayInputStream;
    7.27 +import java.io.ByteArrayOutputStream;
    7.28 +import java.io.IOException;
    7.29 +import java.io.InputStream;
    7.30 +import java.io.OutputStream;
    7.31 +import java.io.Reader;
    7.32 +import java.net.URI;
    7.33 +import java.net.URISyntaxException;
    7.34 +import java.util.ArrayList;
    7.35 +import java.util.List;
    7.36 +import java.util.logging.Level;
    7.37 +import java.util.logging.Logger;
    7.38 +import org.glassfish.grizzly.PortRange;
    7.39 +import org.glassfish.grizzly.http.server.HttpHandler;
    7.40 +import org.glassfish.grizzly.http.server.HttpServer;
    7.41 +import org.glassfish.grizzly.http.server.NetworkListener;
    7.42 +import org.glassfish.grizzly.http.server.Request;
    7.43 +import org.glassfish.grizzly.http.server.Response;
    7.44 +import org.glassfish.grizzly.http.server.ServerConfiguration;
    7.45 +import org.glassfish.grizzly.websockets.WebSocket;
    7.46 +import org.glassfish.grizzly.websockets.WebSocketAddOn;
    7.47 +import org.glassfish.grizzly.websockets.WebSocketApplication;
    7.48 +import org.glassfish.grizzly.websockets.WebSocketEngine;
    7.49 +
    7.50 +/**
    7.51 + *
    7.52 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    7.53 + */
    7.54 +final class TyrusDynamicHTTP extends HttpHandler {
    7.55 +    private static int resourcesCount;
    7.56 +    private static List<Resource> resources;
    7.57 +    private static ServerConfiguration conf;
    7.58 +    private static HttpServer server;
    7.59 +    
    7.60 +    private TyrusDynamicHTTP() {
    7.61 +    }
    7.62 +    
    7.63 +    static URI initServer() throws Exception {
    7.64 +        server = HttpServer.createSimpleServer(null, new PortRange(8080, 65535));
    7.65 +        final WebSocketAddOn addon = new WebSocketAddOn();
    7.66 +        for (NetworkListener listener : server.getListeners()) {
    7.67 +            listener.registerAddOn(addon);
    7.68 +        }        
    7.69 +        resources = new ArrayList<Resource>();
    7.70 +
    7.71 +        conf = server.getServerConfiguration();
    7.72 +        final TyrusDynamicHTTP dh = new TyrusDynamicHTTP();
    7.73 +
    7.74 +        conf.addHttpHandler(dh, "/");
    7.75 +        
    7.76 +        server.start();
    7.77 +
    7.78 +        return pageURL("http", server, "/test.html");
    7.79 +    }
    7.80 +    
    7.81 +    @Override
    7.82 +    public void service(Request request, Response response) throws Exception {
    7.83 +        if ("/test.html".equals(request.getRequestURI())) {
    7.84 +            response.setContentType("text/html");
    7.85 +            final InputStream is = TyrusDynamicHTTP.class.getResourceAsStream("test.html");
    7.86 +            copyStream(is, response.getOutputStream(), null);
    7.87 +            return;
    7.88 +        }
    7.89 +        if ("/dynamic".equals(request.getRequestURI())) {
    7.90 +            String mimeType = request.getParameter("mimeType");
    7.91 +            List<String> params = new ArrayList<String>();
    7.92 +            boolean webSocket = false;
    7.93 +            for (int i = 0;; i++) {
    7.94 +                String p = request.getParameter("param" + i);
    7.95 +                if (p == null) {
    7.96 +                    break;
    7.97 +                }
    7.98 +                if ("protocol:ws".equals(p)) {
    7.99 +                    webSocket = true;
   7.100 +                    continue;
   7.101 +                }
   7.102 +                params.add(p);
   7.103 +            }
   7.104 +            final String cnt = request.getParameter("content");
   7.105 +            String mangle = cnt.replace("%20", " ").replace("%0A", "\n");
   7.106 +            ByteArrayInputStream is = new ByteArrayInputStream(mangle.getBytes("UTF-8"));
   7.107 +            URI url;
   7.108 +            final Resource res = new Resource(is, mimeType, "/dynamic/res" + ++resourcesCount, params.toArray(new String[params.size()]));
   7.109 +            if (webSocket) {
   7.110 +                url = registerWebSocket(res);
   7.111 +            } else {
   7.112 +                url = registerResource(res);
   7.113 +            }
   7.114 +            response.getWriter().write(url.toString());
   7.115 +            response.getWriter().write("\n");
   7.116 +            return;
   7.117 +        }
   7.118 +
   7.119 +        for (Resource r : resources) {
   7.120 +            if (r.httpPath.equals(request.getRequestURI())) {
   7.121 +                response.setContentType(r.httpType);
   7.122 +                r.httpContent.reset();
   7.123 +                String[] params = null;
   7.124 +                if (r.parameters.length != 0) {
   7.125 +                    params = new String[r.parameters.length];
   7.126 +                    for (int i = 0; i < r.parameters.length; i++) {
   7.127 +                        params[i] = request.getParameter(r.parameters[i]);
   7.128 +                        if (params[i] == null) {
   7.129 +                            if ("http.method".equals(r.parameters[i])) {
   7.130 +                                params[i] = request.getMethod().toString();
   7.131 +                            } else if ("http.requestBody".equals(r.parameters[i])) {
   7.132 +                                Reader rdr = request.getReader();
   7.133 +                                StringBuilder sb = new StringBuilder();
   7.134 +                                for (;;) {
   7.135 +                                    int ch = rdr.read();
   7.136 +                                    if (ch == -1) {
   7.137 +                                        break;
   7.138 +                                    }
   7.139 +                                    sb.append((char) ch);
   7.140 +                                }
   7.141 +                                params[i] = sb.toString();
   7.142 +                            }
   7.143 +                        }
   7.144 +                        if (params[i] == null) {
   7.145 +                            params[i] = "null";
   7.146 +                        }
   7.147 +                    }
   7.148 +                }
   7.149 +
   7.150 +                copyStream(r.httpContent, response.getOutputStream(), null, params);
   7.151 +            }
   7.152 +        }
   7.153 +    }
   7.154 +    
   7.155 +    private URI registerWebSocket(Resource r) {
   7.156 +        WebSocketEngine.getEngine().register("", r.httpPath, new WS(r));
   7.157 +        return pageURL("ws", server, r.httpPath);
   7.158 +    }
   7.159 +
   7.160 +    private URI registerResource(Resource r) {
   7.161 +        if (!resources.contains(r)) {
   7.162 +            resources.add(r);
   7.163 +            conf.addHttpHandler(this, r.httpPath);
   7.164 +        }
   7.165 +        return pageURL("http", server, r.httpPath);
   7.166 +    }
   7.167 +    
   7.168 +    private static URI pageURL(String proto, HttpServer server, final String page) {
   7.169 +        NetworkListener listener = server.getListeners().iterator().next();
   7.170 +        int port = listener.getPort();
   7.171 +        try {
   7.172 +            return new URI(proto + "://localhost:" + port + page);
   7.173 +        } catch (URISyntaxException ex) {
   7.174 +            throw new IllegalStateException(ex);
   7.175 +        }
   7.176 +    }
   7.177 +    
   7.178 +    static final class Resource {
   7.179 +
   7.180 +        final InputStream httpContent;
   7.181 +        final String httpType;
   7.182 +        final String httpPath;
   7.183 +        final String[] parameters;
   7.184 +
   7.185 +        Resource(InputStream httpContent, String httpType, String httpPath,
   7.186 +            String[] parameters) {
   7.187 +            httpContent.mark(Integer.MAX_VALUE);
   7.188 +            this.httpContent = httpContent;
   7.189 +            this.httpType = httpType;
   7.190 +            this.httpPath = httpPath;
   7.191 +            this.parameters = parameters;
   7.192 +        }
   7.193 +    }
   7.194 +
   7.195 +    static void copyStream(InputStream is, OutputStream os, String baseURL, String... params) throws IOException {
   7.196 +        for (;;) {
   7.197 +            int ch = is.read();
   7.198 +            if (ch == -1) {
   7.199 +                break;
   7.200 +            }
   7.201 +            if (ch == '$' && params.length > 0) {
   7.202 +                int cnt = is.read() - '0';
   7.203 +                if (baseURL != null && cnt == 'U' - '0') {
   7.204 +                    os.write(baseURL.getBytes("UTF-8"));
   7.205 +                } else {
   7.206 +                    if (cnt >= 0 && cnt < params.length) {
   7.207 +                        os.write(params[cnt].getBytes("UTF-8"));
   7.208 +                    } else {
   7.209 +                        os.write('$');
   7.210 +                        os.write(cnt + '0');
   7.211 +                    }
   7.212 +                }
   7.213 +            } else {
   7.214 +                os.write(ch);
   7.215 +            }
   7.216 +        }
   7.217 +    }
   7.218 +    
   7.219 +    private static class WS extends WebSocketApplication {
   7.220 +        private final Resource r;
   7.221 +
   7.222 +        private WS(Resource r) {
   7.223 +            this.r = r;
   7.224 +        }
   7.225 +
   7.226 +        @Override
   7.227 +        public void onMessage(WebSocket socket, String text) {
   7.228 +            try {
   7.229 +                r.httpContent.reset();
   7.230 +                ByteArrayOutputStream out = new ByteArrayOutputStream();
   7.231 +                copyStream(r.httpContent, out, null, text);
   7.232 +                String s = new String(out.toByteArray(), "UTF-8");
   7.233 +                socket.send(s);
   7.234 +            } catch (IOException ex) {
   7.235 +                LOG.log(Level.WARNING, null, ex);
   7.236 +            }
   7.237 +        }
   7.238 +        private static final Logger LOG = Logger.getLogger(WS.class.getName());
   7.239 +        
   7.240 +    }
   7.241 +}
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusFX.java	Sun Aug 25 14:40:16 2013 +0200
     8.3 @@ -0,0 +1,92 @@
     8.4 +/**
     8.5 + * HTML via Java(tm) Language Bindings
     8.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     8.7 + *
     8.8 + * This program is free software: you can redistribute it and/or modify
     8.9 + * it under the terms of the GNU General Public License as published by
    8.10 + * the Free Software Foundation, version 2 of the License.
    8.11 + *
    8.12 + * This program is distributed in the hope that it will be useful,
    8.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    8.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    8.15 + * GNU General Public License for more details. apidesign.org
    8.16 + * designates this particular file as subject to the
    8.17 + * "Classpath" exception as provided by apidesign.org
    8.18 + * in the License file that accompanied this code.
    8.19 + *
    8.20 + * You should have received a copy of the GNU General Public License
    8.21 + * along with this program. Look for COPYING file in the top folder.
    8.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    8.23 + */
    8.24 +package org.apidesign.html.wstyrus;
    8.25 +
    8.26 +import java.lang.reflect.InvocationTargetException;
    8.27 +import java.lang.reflect.Method;
    8.28 +import javafx.application.Platform;
    8.29 +import org.testng.ITest;
    8.30 +import org.testng.annotations.Test;
    8.31 +
    8.32 +/**
    8.33 + *
    8.34 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    8.35 + */
    8.36 +public final class TyrusFX implements ITest, Runnable {
    8.37 +    private final Method m;
    8.38 +    private Object result;
    8.39 +    private Object inst;
    8.40 +    private int count;
    8.41 +
    8.42 +    TyrusFX(Method m) {
    8.43 +        this.m = m;
    8.44 +    }
    8.45 +
    8.46 +    @Override
    8.47 +    public String getTestName() {
    8.48 +        return m.getName();
    8.49 +    }
    8.50 +
    8.51 +    @Test
    8.52 +    public synchronized void executeTest() throws Exception {
    8.53 +        if (result == null) {
    8.54 +            Platform.runLater(this);
    8.55 +            wait();
    8.56 +        }
    8.57 +        if (result instanceof Exception) {
    8.58 +            throw (Exception)result;
    8.59 +        }
    8.60 +        if (result instanceof Error) {
    8.61 +            throw (Error)result;
    8.62 +        }
    8.63 +    }
    8.64 +
    8.65 +    @Override
    8.66 +    public synchronized void run() {
    8.67 +        boolean notify = true;
    8.68 +        try {
    8.69 +            if (inst == null) {
    8.70 +                inst = m.getDeclaringClass().newInstance();
    8.71 +            }
    8.72 +            result = m.invoke(inst);
    8.73 +            if (result == null) {
    8.74 +                result = this;
    8.75 +            }
    8.76 +        } catch (InvocationTargetException ex) {
    8.77 +            Throwable r = ex.getTargetException();
    8.78 +            if (r instanceof InterruptedException) {
    8.79 +                if (count++ < 10000) {
    8.80 +                    notify = false;
    8.81 +                    Platform.runLater(this);
    8.82 +                    return;
    8.83 +                }
    8.84 +            }
    8.85 +            result = r;
    8.86 +        } catch (Exception ex) {
    8.87 +            result = ex;
    8.88 +        } finally {
    8.89 +            if (notify) {
    8.90 +                notifyAll();
    8.91 +            }
    8.92 +        }
    8.93 +    }
    8.94 +    
    8.95 +}
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/ko-ws-tyrus/src/test/java/org/apidesign/html/wstyrus/TyrusKnockoutTest.java	Sun Aug 25 14:40:16 2013 +0200
     9.3 @@ -0,0 +1,185 @@
     9.4 +/**
     9.5 + * HTML via Java(tm) Language Bindings
     9.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     9.7 + *
     9.8 + * This program is free software: you can redistribute it and/or modify
     9.9 + * it under the terms of the GNU General Public License as published by
    9.10 + * the Free Software Foundation, version 2 of the License.
    9.11 + *
    9.12 + * This program is distributed in the hope that it will be useful,
    9.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    9.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    9.15 + * GNU General Public License for more details. apidesign.org
    9.16 + * designates this particular file as subject to the
    9.17 + * "Classpath" exception as provided by apidesign.org
    9.18 + * in the License file that accompanied this code.
    9.19 + *
    9.20 + * You should have received a copy of the GNU General Public License
    9.21 + * along with this program. Look for COPYING file in the top folder.
    9.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    9.23 + */
    9.24 +package org.apidesign.html.wstyrus;
    9.25 +
    9.26 +import java.io.BufferedReader;
    9.27 +import java.io.IOException;
    9.28 +import java.io.InputStreamReader;
    9.29 +import java.lang.annotation.Annotation;
    9.30 +import java.lang.reflect.Method;
    9.31 +import java.net.URI;
    9.32 +import java.net.URISyntaxException;
    9.33 +import java.net.URL;
    9.34 +import java.net.URLConnection;
    9.35 +import java.util.ArrayList;
    9.36 +import java.util.List;
    9.37 +import java.util.Map;
    9.38 +import java.util.concurrent.Executors;
    9.39 +import net.java.html.BrwsrCtx;
    9.40 +import net.java.html.boot.BrowserBuilder;
    9.41 +import net.java.html.js.JavaScriptBody;
    9.42 +import org.apidesign.html.context.spi.Contexts;
    9.43 +import org.apidesign.html.json.spi.Technology;
    9.44 +import org.apidesign.html.json.spi.Transfer;
    9.45 +import org.apidesign.html.json.spi.WSTransfer;
    9.46 +import org.apidesign.html.json.tck.KOTest;
    9.47 +import org.apidesign.html.json.tck.KnockoutTCK;
    9.48 +import org.apidesign.html.kofx.FXContext;
    9.49 +import org.json.JSONException;
    9.50 +import org.json.JSONObject;
    9.51 +import org.openide.util.lookup.ServiceProvider;
    9.52 +import org.testng.annotations.Factory;
    9.53 +import static org.testng.Assert.*;
    9.54 +
    9.55 +/**
    9.56 + *
    9.57 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    9.58 + */
    9.59 +@ServiceProvider(service = KnockoutTCK.class)
    9.60 +public final class TyrusKnockoutTest extends KnockoutTCK {
    9.61 +    private static Class<?> browserClass;
    9.62 +    
    9.63 +    public TyrusKnockoutTest() {
    9.64 +    }
    9.65 +    
    9.66 +    @Factory public static Object[] compatibilityTests() throws Exception {
    9.67 +        Class[] arr = testClasses();
    9.68 +        for (int i = 0; i < arr.length; i++) {
    9.69 +            assertEquals(
    9.70 +                arr[i].getClassLoader(),
    9.71 +                TyrusKnockoutTest.class.getClassLoader(),
    9.72 +                "All classes loaded by the same classloader"
    9.73 +            );
    9.74 +        }
    9.75 +        
    9.76 +        URI uri = TyrusDynamicHTTP.initServer();
    9.77 +    
    9.78 +        final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(TyrusKnockoutTest.class).
    9.79 +            loadPage(uri.toString()).
    9.80 +            invoke("initialized");
    9.81 +        
    9.82 +        Executors.newSingleThreadExecutor().submit(new Runnable() {
    9.83 +            @Override
    9.84 +            public void run() {
    9.85 +                bb.showAndWait();
    9.86 +            }
    9.87 +        });
    9.88 +        
    9.89 +        ClassLoader l = getClassLoader();
    9.90 +        List<Object> res = new ArrayList<Object>();
    9.91 +        for (int i = 0; i < arr.length; i++) {
    9.92 +            Class<?> c = Class.forName(arr[i].getName(), true, l);
    9.93 +            Class<? extends Annotation> koTest = 
    9.94 +                c.getClassLoader().loadClass(KOTest.class.getName()).
    9.95 +                asSubclass(Annotation.class);
    9.96 +            for (Method m : c.getMethods()) {
    9.97 +                if (m.getAnnotation(koTest) != null) {
    9.98 +                    res.add(new TyrusFX(m));
    9.99 +                }
   9.100 +            }
   9.101 +        }
   9.102 +        return res.toArray();
   9.103 +    }
   9.104 +
   9.105 +    static synchronized ClassLoader getClassLoader() throws InterruptedException {
   9.106 +        while (browserClass == null) {
   9.107 +            TyrusKnockoutTest.class.wait();
   9.108 +        }
   9.109 +        return browserClass.getClassLoader();
   9.110 +    }
   9.111 +    
   9.112 +    public static synchronized void initialized(Class<?> browserCls) throws Exception {
   9.113 +        browserClass = browserCls;
   9.114 +        TyrusKnockoutTest.class.notifyAll();
   9.115 +    }
   9.116 +    
   9.117 +    public static void initialized() throws Exception {
   9.118 +        Class<?> classpathClass = ClassLoader.getSystemClassLoader().loadClass(TyrusKnockoutTest.class.getName());
   9.119 +        Method m = classpathClass.getMethod("initialized", Class.class);
   9.120 +        m.invoke(null, TyrusKnockoutTest.class);
   9.121 +    }
   9.122 +    
   9.123 +    @Override
   9.124 +    public BrwsrCtx createContext() {
   9.125 +        FXContext fx = new FXContext();
   9.126 +        TyrusContext tc = new TyrusContext();
   9.127 +        Contexts.Builder cb = Contexts.newBuilder().
   9.128 +            register(Technology.class, fx, 10).
   9.129 +            register(Transfer.class, fx, 10).
   9.130 +            register(WSTransfer.class, tc, 10);
   9.131 +        return cb.build();
   9.132 +    }
   9.133 +
   9.134 +    @Override
   9.135 +    public Object createJSON(Map<String, Object> values) {
   9.136 +        JSONObject json = new JSONObject();
   9.137 +        for (Map.Entry<String, Object> entry : values.entrySet()) {
   9.138 +            try {
   9.139 +                json.put(entry.getKey(), entry.getValue());
   9.140 +            } catch (JSONException ex) {
   9.141 +                throw new IllegalStateException(ex);
   9.142 +            }
   9.143 +        }
   9.144 +        return json;
   9.145 +    }
   9.146 +
   9.147 +    @Override
   9.148 +    @JavaScriptBody(args = { "s", "args" }, body = ""
   9.149 +        + "var f = new Function(s); "
   9.150 +        + "return f.apply(null, args);"
   9.151 +    )
   9.152 +    public native Object executeScript(String script, Object[] arguments);
   9.153 +
   9.154 +    @JavaScriptBody(args = {  }, body = 
   9.155 +          "var h;"
   9.156 +        + "if (!!window && !!window.location && !!window.location.href)\n"
   9.157 +        + "  h = window.location.href;\n"
   9.158 +        + "else "
   9.159 +        + "  h = null;"
   9.160 +        + "return h;\n"
   9.161 +    )
   9.162 +    private static native String findBaseURL();
   9.163 +    
   9.164 +    @Override
   9.165 +    public URI prepareURL(String content, String mimeType, String[] parameters) {
   9.166 +        try {
   9.167 +            final URL baseURL = new URL(findBaseURL());
   9.168 +            StringBuilder sb = new StringBuilder();
   9.169 +            sb.append("/dynamic?mimeType=").append(mimeType);
   9.170 +            for (int i = 0; i < parameters.length; i++) {
   9.171 +                sb.append("&param" + i).append("=").append(parameters[i]);
   9.172 +            }
   9.173 +            String mangle = content.replace("\n", "%0a")
   9.174 +                .replace("\"", "\\\"").replace(" ", "%20");
   9.175 +            sb.append("&content=").append(mangle);
   9.176 +
   9.177 +            URL query = new URL(baseURL, sb.toString());
   9.178 +            URLConnection c = query.openConnection();
   9.179 +            BufferedReader br = new BufferedReader(new InputStreamReader(c.getInputStream()));
   9.180 +            URI connectTo = new URI(br.readLine());
   9.181 +            return connectTo;
   9.182 +        } catch (IOException ex) {
   9.183 +            throw new IllegalStateException(ex);
   9.184 +        } catch (URISyntaxException ex) {
   9.185 +            throw new IllegalStateException(ex);
   9.186 +        }
   9.187 +    }
   9.188 +}
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/ko-ws-tyrus/src/test/resources/org/apidesign/html/wstyrus/test.html	Sun Aug 25 14:40:16 2013 +0200
    10.3 @@ -0,0 +1,34 @@
    10.4 +<!--
    10.5 +
    10.6 +    HTML via Java(tm) Language Bindings
    10.7 +    Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    10.8 +
    10.9 +    This program is free software: you can redistribute it and/or modify
   10.10 +    it under the terms of the GNU General Public License as published by
   10.11 +    the Free Software Foundation, version 2 of the License.
   10.12 +
   10.13 +    This program is distributed in the hope that it will be useful,
   10.14 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
   10.15 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   10.16 +    GNU General Public License for more details. apidesign.org
   10.17 +    designates this particular file as subject to the
   10.18 +    "Classpath" exception as provided by apidesign.org
   10.19 +    in the License file that accompanied this code.
   10.20 +
   10.21 +    You should have received a copy of the GNU General Public License
   10.22 +    along with this program. Look for COPYING file in the top folder.
   10.23 +    If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
   10.24 +
   10.25 +-->
   10.26 +<!DOCTYPE html>
   10.27 +<html>
   10.28 +    <head>
   10.29 +        <title>Tyrus WebSockets Execution Harness</title>
   10.30 +        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
   10.31 +        <meta name="viewport" content="width=device-width">
   10.32 +    </head>
   10.33 +    <body>
   10.34 +        <h1>Tyrus WebSockets Execution Harness</h1>
   10.35 +    </body>
   10.36 +    <script></script>
   10.37 +</html>
    11.1 --- a/pom.xml	Sun Aug 25 12:35:32 2013 +0200
    11.2 +++ b/pom.xml	Sun Aug 25 14:40:16 2013 +0200
    11.3 @@ -27,6 +27,7 @@
    11.4      <module>boot</module>
    11.5      <module>boot-fx</module>
    11.6      <module>geo</module>
    11.7 +    <module>ko-ws-tyrus</module>
    11.8    </modules>
    11.9    <licenses>
   11.10        <license>