http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/ko-osgi-test/pom.xml ---------------------------------------------------------------------- diff --git a/ko-osgi-test/pom.xml b/ko-osgi-test/pom.xml new file mode 100644 index 0000000..2af6772 --- /dev/null +++ b/ko-osgi-test/pom.xml @@ -0,0 +1,181 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + + Copyright 2013-2016 Oracle and/or its affiliates. All rights reserved. + + Oracle and Java are registered trademarks of Oracle and/or its affiliates. + Other names may be trademarks of their respective owners. + + The contents of this file are subject to the terms of either the GNU + General Public License Version 2 only ("GPL") or the Common + Development and Distribution License("CDDL") (collectively, the + "License"). You may not use this file except in compliance with the + License. You can obtain a copy of the License at + http://www.netbeans.org/cddl-gplv2.html + or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + specific language governing permissions and limitations under the + License. When distributing the software, include this License Header + Notice in each file and include the License file at + nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + particular file as subject to the "Classpath" exception as provided + by Oracle in the GPL Version 2 section of the License file that + accompanied this code. If applicable, add the following below the + License Header, with the fields enclosed by brackets [] replaced by + your own identifying information: + "Portions Copyrighted [year] [name of copyright owner]" + + Contributor(s): + + The Original Software is NetBeans. The Initial Developer of the Original + Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + + If you wish your version of this file to be governed by only the CDDL + or only the GPL Version 2, indicate your decision by adding + "[Contributor] elects to include this software in this distribution + under the [CDDL or GPL Version 2] license." If you do not indicate a + single choice of license, a recipient has the option to distribute + your version of this file under either the CDDL, the GPL Version 2 or + to extend the choice of license to its licensees as provided above. + However, if you add GPL Version 2 code and therefore, elected the GPL + Version 2 license, then the option applies only if the new code is + made subject to such option by the copyright holder. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.netbeans.html</groupId> + <artifactId>pom</artifactId> + <version>2.0-SNAPSHOT</version> + </parent> + <name>KO Tests in Equinox OSGi Container</name> + <artifactId>ko-osgi-test</artifactId> + <packaging>bundle</packaging> + <description>Runs the TCK for Knockout in Equinox OSGi Container</description> + <properties> + <netbeans.compile.on.save>none</netbeans.compile.on.save> + </properties> + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.netbeans.html</groupId> + <artifactId>html4j-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <artifactId>maven-failsafe-plugin</artifactId> + <configuration> + <additionalClasspathElements> + <additionalClasspathElement>${project.build.directory}/${project.build.finalName}.jar</additionalClasspathElement> + </additionalClasspathElements> + </configuration> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + <dependencies> + <dependency> + <groupId>com.oracle</groupId> + <artifactId>javafx</artifactId> + <version>2.2</version> + <scope>system</scope> + <systemPath>${jfxrt.jar}</systemPath> + </dependency> + <dependency> + <groupId>de.twentyeleven.skysail</groupId> + <artifactId>org.json-osgi</artifactId> + </dependency> + <dependency> + <groupId>org.netbeans.html</groupId> + <artifactId>net.java.html.json</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.testng</groupId> + <artifactId>testng</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>net.java.html.json.tck</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.netbeans.api</groupId> + <artifactId>org-openide-util-lookup</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.netbeans.html</groupId> + <artifactId>net.java.html.boot</artifactId> + <version>${project.version}</version> + <type>jar</type> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>ko4j</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>net.java.html.boot.fx</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.glassfish.grizzly</groupId> + <artifactId>grizzly-http-server</artifactId> + <version>${grizzly.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.glassfish.grizzly</groupId> + <artifactId>grizzly-websockets-server</artifactId> + <version>${grizzly.version}</version> + <scope>test</scope> + <type>jar</type> + </dependency> + <dependency> + <groupId>org.glassfish.grizzly</groupId> + <artifactId>grizzly-http-servlet</artifactId> + <version>${grizzly.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <scope>test</scope> + <version>3.1.0</version> + </dependency> + <dependency> + <groupId>org.eclipse</groupId> + <artifactId>org.eclipse.osgi</artifactId> + <version>3.8.0.v20120529-1548</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>equinox-agentclass-hook</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java ---------------------------------------------------------------------- diff --git a/ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java b/ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java new file mode 100644 index 0000000..1ff2564 --- /dev/null +++ b/ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java @@ -0,0 +1,222 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.ko.osgi.test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import net.java.html.BrwsrCtx; +import net.java.html.boot.BrowserBuilder; +import net.java.html.js.JavaScriptBody; +import org.netbeans.html.boot.spi.Fn; +import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.json.spi.Technology; +import org.netbeans.html.json.spi.Transfer; +import org.netbeans.html.json.tck.KnockoutTCK; +import org.openide.util.lookup.ServiceProvider; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; + +/** + * + * @author Jaroslav Tulach + */ +@ServiceProvider(service = KnockoutTCK.class) +public class KnockoutEquinoxTCKImpl extends KnockoutTCK implements Callable<Class[]> { + + private static Fn.Presenter browserContext; + + public static Class loadOSGiClass(String name, BundleContext ctx) throws Exception { + for (Bundle b : ctx.getBundles()) { + try { + Class<?> osgiClass = b.loadClass(name); + if (osgiClass != null && osgiClass.getClassLoader() != ClassLoader.getSystemClassLoader()) { + return osgiClass; + } + } catch (ClassNotFoundException cnfe) { + // go on + } + } + throw new IllegalStateException("Cannot load " + name + " from the OSGi container!"); + } + + @Override + public Class[] call() throws Exception { + return testClasses(); + } + + public static void start(URI server) throws Exception { + final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(KnockoutEquinoxTCKImpl.class). + loadPage(server.toString()). + invoke("initialized"); + + Executors.newSingleThreadExecutor().submit(new Runnable() { + @Override + public void run() { + try { + final ClassLoader osgiClassLoader = BrowserBuilder.class.getClassLoader(); + bb.classloader(osgiClassLoader); + bb.showAndWait(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + }); + } + + public static void initialized() throws Exception { + Bundle bundle = FrameworkUtil.getBundle(KnockoutEquinoxTCKImpl.class); + if (bundle == null) { + throw new IllegalStateException( + "Should be loaded from a bundle. But was: " + KnockoutEquinoxTCKImpl.class.getClassLoader() + ); + } + Class<?> classpathClass = ClassLoader.getSystemClassLoader().loadClass( + "org.netbeans.html.ko.osgi.test.KnockoutEquinoxIT" + ); + Method m = classpathClass.getMethod("initialized", Class.class, Object.class); + browserContext = Fn.activePresenter(); + m.invoke(null, KnockoutEquinoxTCKImpl.class, browserContext); + } + + @Override + public BrwsrCtx createContext() { + try { + Contexts.Builder cb = Contexts.newBuilder(). + register(Technology.class, (Technology)osgiInstance("KOTech"), 10). + register(Transfer.class, (Transfer)osgiInstance("KOTransfer"), 10). + register(Executor.class, (Executor)browserContext, 10); +// if (fx.areWebSocketsSupported()) { +// cb.register(WSTransfer.class, fx, 10); +// } + return cb.build(); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + private Object osgiInstance(String simpleName) throws IllegalAccessException, SecurityException, IllegalArgumentException, Exception, NoSuchMethodException, InstantiationException, InvocationTargetException { + Class<?> fxCls = loadOSGiClass( + "org.netbeans.html.ko4j." + simpleName, + FrameworkUtil.getBundle(KnockoutEquinoxTCKImpl.class).getBundleContext() + ); + final Constructor<?> cnstr = fxCls.getDeclaredConstructor(); + cnstr.setAccessible(true); + Object fx = cnstr.newInstance(); + return fx; + } + + @Override + public Object createJSON(Map<String, Object> values) { + Object json = createObj(); + for (Map.Entry<String, Object> entry : values.entrySet()) { + putObj(json, entry.getKey(), entry.getValue()); + } + return json; + } + + @JavaScriptBody(args = {}, body = "return {};") + private static native Object createObj(); + + @JavaScriptBody(args = {"obj", "prop", "val"}, body = "obj[prop] = val;") + private static native void putObj(Object obj, String prop, Object val); + + @Override + @JavaScriptBody(args = { "s", "args" }, body = "" + + "var f = new Function(s); " + + "return f.apply(null, args);" + ) + public native Object executeScript(String script, Object[] arguments); + + @JavaScriptBody(args = { }, body = + "var h;" + + "if (!!window && !!window.location && !!window.location.href)\n" + + " h = window.location.href;\n" + + "else " + + " h = null;" + + "return h;\n" + ) + private static native String findBaseURL(); + + @Override + public URI prepareURL(String content, String mimeType, String[] parameters) { + try { + final URL baseURL = new URL(findBaseURL()); + StringBuilder sb = new StringBuilder(); + sb.append("/dynamic?mimeType=").append(mimeType); + for (int i = 0; i < parameters.length; i++) { + sb.append("¶m" + i).append("=").append(parameters[i]); + } + String mangle = content.replace("\n", "%0a") + .replace("\"", "\\\"").replace(" ", "%20"); + sb.append("&content=").append(mangle); + + URL query = new URL(baseURL, sb.toString()); + URLConnection c = query.openConnection(); + BufferedReader br = new BufferedReader(new InputStreamReader(c.getInputStream())); + URI connectTo = new URI(br.readLine()); + return connectTo; + } catch (IOException ex) { + throw new IllegalStateException(ex); + } catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public boolean canFailWebSocketTest() { + return true; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/DynamicHTTP.java ---------------------------------------------------------------------- diff --git a/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/DynamicHTTP.java b/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/DynamicHTTP.java new file mode 100644 index 0000000..3c70570 --- /dev/null +++ b/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/DynamicHTTP.java @@ -0,0 +1,261 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.ko.osgi.test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.glassfish.grizzly.PortRange; +import org.glassfish.grizzly.http.server.HttpHandler; +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; +import org.glassfish.grizzly.http.server.ServerConfiguration; +import org.glassfish.grizzly.websockets.WebSocket; +import org.glassfish.grizzly.websockets.WebSocketAddOn; +import org.glassfish.grizzly.websockets.WebSocketApplication; +import org.glassfish.grizzly.websockets.WebSocketEngine; + +/** + * + * @author Jaroslav Tulach + */ +final class DynamicHTTP extends HttpHandler { + private static final Logger LOG = Logger.getLogger(DynamicHTTP.class.getName()); + private static int resourcesCount; + private static List<Resource> resources; + private static ServerConfiguration conf; + private static HttpServer server; + + private DynamicHTTP() { + } + + static URI initServer() throws Exception { + server = HttpServer.createSimpleServer(null, new PortRange(8080, 65535)); + final WebSocketAddOn addon = new WebSocketAddOn(); + for (NetworkListener listener : server.getListeners()) { + listener.registerAddOn(addon); + } + resources = new ArrayList<Resource>(); + + conf = server.getServerConfiguration(); + final DynamicHTTP dh = new DynamicHTTP(); + + conf.addHttpHandler(dh, "/"); + + server.start(); + + return pageURL("http", server, "/test.html"); + } + + @Override + public void service(Request request, Response response) throws Exception { + if ("/test.html".equals(request.getRequestURI())) { + response.setContentType("text/html"); + final InputStream is = DynamicHTTP.class.getResourceAsStream("test.html"); + copyStream(is, response.getOutputStream(), null); + return; + } + if ("/dynamic".equals(request.getRequestURI())) { + String mimeType = request.getParameter("mimeType"); + List<String> params = new ArrayList<String>(); + boolean webSocket = false; + for (int i = 0;; i++) { + String p = request.getParameter("param" + i); + if (p == null) { + break; + } + if ("protocol:ws".equals(p)) { + webSocket = true; + continue; + } + params.add(p); + } + final String cnt = request.getParameter("content"); + String mangle = cnt.replace("%20", " ").replace("%0A", "\n"); + ByteArrayInputStream is = new ByteArrayInputStream(mangle.getBytes("UTF-8")); + URI url; + final Resource res = new Resource(is, mimeType, "/dynamic/res" + ++resourcesCount, params.toArray(new String[params.size()])); + if (webSocket) { + url = registerWebSocket(res); + } else { + url = registerResource(res); + } + response.getWriter().write(url.toString()); + response.getWriter().write("\n"); + return; + } + + for (Resource r : resources) { + if (r.httpPath.equals(request.getRequestURI())) { + response.setContentType(r.httpType); + r.httpContent.reset(); + String[] params = null; + if (r.parameters.length != 0) { + params = new String[r.parameters.length]; + for (int i = 0; i < r.parameters.length; i++) { + params[i] = request.getParameter(r.parameters[i]); + if (params[i] == null) { + if ("http.method".equals(r.parameters[i])) { + params[i] = request.getMethod().toString(); + } else if ("http.requestBody".equals(r.parameters[i])) { + Reader rdr = request.getReader(); + StringBuilder sb = new StringBuilder(); + for (;;) { + int ch = rdr.read(); + if (ch == -1) { + break; + } + sb.append((char) ch); + } + params[i] = sb.toString(); + } else if (r.parameters[i].startsWith("http.header.")) { + params[i] = request.getHeader(r.parameters[i].substring(12)); + } + } + if (params[i] == null) { + params[i] = "null"; + } + } + } + + copyStream(r.httpContent, response.getOutputStream(), null, params); + } + } + } + + private URI registerWebSocket(Resource r) { + WebSocketEngine.getEngine().register("", r.httpPath, new WS(r)); + return pageURL("ws", server, r.httpPath); + } + + private URI registerResource(Resource r) { + if (!resources.contains(r)) { + resources.add(r); + conf.addHttpHandler(this, r.httpPath); + } + return pageURL("http", server, r.httpPath); + } + + private static URI pageURL(String proto, HttpServer server, final String page) { + NetworkListener listener = server.getListeners().iterator().next(); + int port = listener.getPort(); + try { + return new URI(proto + "://localhost:" + port + page); + } catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + + static final class Resource { + + final InputStream httpContent; + final String httpType; + final String httpPath; + final String[] parameters; + + Resource(InputStream httpContent, String httpType, String httpPath, + String[] parameters) { + httpContent.mark(Integer.MAX_VALUE); + this.httpContent = httpContent; + this.httpType = httpType; + this.httpPath = httpPath; + this.parameters = parameters; + } + } + + static void copyStream(InputStream is, OutputStream os, String baseURL, String... params) throws IOException { + for (;;) { + int ch = is.read(); + if (ch == -1) { + break; + } + if (ch == '$' && params.length > 0) { + int cnt = is.read() - '0'; + if (baseURL != null && cnt == 'U' - '0') { + os.write(baseURL.getBytes("UTF-8")); + } else { + if (cnt >= 0 && cnt < params.length) { + os.write(params[cnt].getBytes("UTF-8")); + } else { + os.write('$'); + os.write(cnt + '0'); + } + } + } else { + os.write(ch); + } + } + } + + private static class WS extends WebSocketApplication { + private final Resource r; + + private WS(Resource r) { + this.r = r; + } + + @Override + public void onMessage(WebSocket socket, String text) { + try { + r.httpContent.reset(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + copyStream(r.httpContent, out, null, text); + String s = new String(out.toByteArray(), "UTF-8"); + socket.send(s); + } catch (IOException ex) { + LOG.log(Level.WARNING, "Error processing message " + text, ex); + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/KOFx.java ---------------------------------------------------------------------- diff --git a/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/KOFx.java b/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/KOFx.java new file mode 100644 index 0000000..686a930 --- /dev/null +++ b/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/KOFx.java @@ -0,0 +1,130 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.ko.osgi.test; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javafx.application.Platform; +import org.testng.ITest; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public final class KOFx implements ITest, Runnable { + private final Object p; + private final Method m; + private Object result; + private Object inst; + private int count; + + KOFx(Object p, Method m) { + this.p = p; + this.m = m; + } + + @Override + public String getTestName() { + return m.getName(); + } + + @Test + public synchronized void executeTest() throws Exception { + if (result == null) { + Platform.runLater(this); + wait(); + } + if (result instanceof Exception) { + throw (Exception)result; + } + if (result instanceof Error) { + throw (Error)result; + } + } + + @Override + public synchronized void run() { + boolean notify = true; + Closeable a = null; + try { + a = KnockoutEquinoxIT.activateInOSGi(p); + if (inst == null) { + inst = m.getDeclaringClass().newInstance(); + } + result = m.invoke(inst); + if (result == null) { + result = this; + } + } catch (InvocationTargetException ex) { + Throwable r = ex.getTargetException(); + if (r instanceof InterruptedException) { + if (count++ < 10000) { + notify = false; + try { + Thread.sleep(100); + } catch (Exception ex1) { + // ignore and continue + } + Platform.runLater(this); + return; + } + } + result = r; + } catch (Exception ex) { + result = ex; + } finally { + if (notify) { + notifyAll(); + } + try { + if (a != null) a.close(); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxIT.java ---------------------------------------------------------------------- diff --git a/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxIT.java b/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxIT.java new file mode 100644 index 0000000..6c57f82 --- /dev/null +++ b/ko-osgi-test/src/test/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxIT.java @@ -0,0 +1,245 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.ko.osgi.test; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.Callable; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.html.boot.spi.Fn; +import org.netbeans.html.json.tck.KOTest; +import org.netbeans.html.json.tck.KnockoutTCK; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.framework.launch.Framework; +import org.osgi.framework.launch.FrameworkFactory; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.fail; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +public class KnockoutEquinoxIT { + private static final Logger LOG = Logger.getLogger(KnockoutEquinoxIT.class.getName()); + private static Framework framework; + private static File dir; + static Framework framework() throws Exception { + if (framework != null) { + return framework; + } + for (FrameworkFactory ff : ServiceLoader.load(FrameworkFactory.class)) { + + String basedir = System.getProperty("basedir"); + assertNotNull("basedir preperty provided", basedir); + File target = new File(basedir, "target"); + dir = new File(target, "osgi"); + dir.mkdirs(); + + Map<String,String> config = new HashMap<String, String>(); + config.put(Constants.FRAMEWORK_STORAGE, dir.getPath()); + config.put(Constants.FRAMEWORK_STORAGE_CLEAN, "true"); + config.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, "sun.misc," + + "javafx.application," + + "javafx.beans," + + "javafx.beans.property," + + "javafx.beans.value," + + "javafx.collections," + + "javafx.concurrent," + + "javafx.event," + + "javafx.geometry," + + "javafx.scene," + + "javafx.scene.control," + + "javafx.scene.image," + + "javafx.scene.layout," + + "javafx.scene.text," + + "javafx.scene.web," + + "javafx.stage," + + "javafx.util," + + "netscape.javascript" + ); + config.put("osgi.hook.configurators.include", "org.netbeans.html.equinox.agentclass.AgentHook"); + framework = ff.newFramework(config); + framework.init(); + loadClassPathBundles(framework); + framework.start(); + for (Bundle b : framework.getBundleContext().getBundles()) { + try { + if (b.getSymbolicName().contains("equinox-agentclass-hook")) { + continue; + } + if (b.getSymbolicName().contains("glassfish.grizzly")) { + continue; + } + b.start(); + LOG.log(Level.INFO, "Started {0}", b.getSymbolicName()); + } catch (BundleException ex) { + throw new IllegalStateException("Cannot start bundle " + b.getSymbolicName(), ex); + } + } + return framework; + } + fail("No OSGi framework in the path"); + return null; + } + + @AfterClass public static void cleanUp() throws Exception { + if (framework != null) framework.stop(); + clearUpDir(dir); + } + private static void clearUpDir(File dir) { + if (dir.isDirectory()) { + for (File f : dir.listFiles()) { + clearUpDir(f); + } + } + dir.delete(); + } + + + + private static void loadClassPathBundles(Framework f) throws IOException, BundleException { + for (String jar : System.getProperty("java.class.path").split(File.pathSeparator)) { + File file = new File(jar); + if (!file.isFile()) { + LOG.info("Not loading " + file); + continue; + } + JarFile jf = new JarFile(file); + final String name = jf.getManifest().getMainAttributes().getValue("Bundle-SymbolicName"); + jf.close(); + if (name != null) { + if (name.contains("org.eclipse.osgi")) { + continue; + } + if (name.contains("testng")) { + continue; + } + final String path = "reference:" + file.toURI().toString(); + try { + Bundle b = f.getBundleContext().installBundle(path); + } catch (BundleException ex) { + LOG.log(Level.WARNING, "Cannot install " + file, ex); + } + } + } + } + + private static Class<?> loadOSGiClass(Class<?> c) throws Exception { + return KnockoutEquinoxTCKImpl.loadOSGiClass(c.getName(), KnockoutEquinoxIT.framework().getBundleContext()); + } + + private static Class<?> browserClass; + private static Object browserContext; + + @Factory public static Object[] compatibilityTests() throws Exception { + Class<?> tck = loadOSGiClass(KnockoutTCK.class); + Class<?> peer = loadOSGiClass(KnockoutEquinoxTCKImpl.class); + // initialize the TCK + Callable<Class[]> inst = (Callable<Class[]>) peer.newInstance(); + + Class[] arr = inst.call(); + for (int i = 0; i < arr.length; i++) { + if (arr[i].getClassLoader() == ClassLoader.getSystemClassLoader()) { + fail("Should be an OSGi class: " + arr[i]); + } + } + + URI uri = DynamicHTTP.initServer(); + + Method start = peer.getMethod("start", URI.class); + start.invoke(null, uri); + + ClassLoader l = getClassLoader(); + List<Object> res = new ArrayList<Object>(); + for (int i = 0; i < arr.length; i++) { + seekKOTests(arr[i], res); + } + return res.toArray(); + } + + private static void seekKOTests(Class<?> c, List<Object> res) throws SecurityException, ClassNotFoundException { + Class<? extends Annotation> koTest = + c.getClassLoader().loadClass(KOTest.class.getName()). + asSubclass(Annotation.class); + for (Method m : c.getMethods()) { + if (m.getAnnotation(koTest) != null) { + res.add(new KOFx(browserContext, m)); + } + } + } + + static synchronized ClassLoader getClassLoader() throws InterruptedException { + while (browserClass == null) { + KnockoutEquinoxIT.class.wait(); + } + return browserClass.getClassLoader(); + } + + public static synchronized void initialized(Class<?> browserCls, Object presenter) throws Exception { + browserClass = browserCls; + browserContext = presenter; + KnockoutEquinoxIT.class.notifyAll(); + } + + static Closeable activateInOSGi(Object presenter) throws Exception { + Class<?> presenterClass = loadOSGiClass(Fn.Presenter.class); + Class<?> fnClass = loadOSGiClass(Fn.class); + Method m = fnClass.getMethod("activate", presenterClass); + return (Closeable) m.invoke(null, presenter); + } +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/ko-osgi-test/src/test/resources/org/netbeans/html/ko/osgi/test/test.html ---------------------------------------------------------------------- diff --git a/ko-osgi-test/src/test/resources/org/netbeans/html/ko/osgi/test/test.html b/ko-osgi-test/src/test/resources/org/netbeans/html/ko/osgi/test/test.html new file mode 100644 index 0000000..226c9f5 --- /dev/null +++ b/ko-osgi-test/src/test/resources/org/netbeans/html/ko/osgi/test/test.html @@ -0,0 +1,56 @@ +<!-- + + DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + + Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + + Oracle and Java are registered trademarks of Oracle and/or its affiliates. + Other names may be trademarks of their respective owners. + + The contents of this file are subject to the terms of either the GNU + General Public License Version 2 only ("GPL") or the Common + Development and Distribution License("CDDL") (collectively, the + "License"). You may not use this file except in compliance with the + License. You can obtain a copy of the License at + http://www.netbeans.org/cddl-gplv2.html + or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + specific language governing permissions and limitations under the + License. When distributing the software, include this License Header + Notice in each file and include the License file at + nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + particular file as subject to the "Classpath" exception as provided + by Oracle in the GPL Version 2 section of the License file that + accompanied this code. If applicable, add the following below the + License Header, with the fields enclosed by brackets [] replaced by + your own identifying information: + "Portions Copyrighted [year] [name of copyright owner]" + + Contributor(s): + + The Original Software is NetBeans. The Initial Developer of the Original + Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + + If you wish your version of this file to be governed by only the CDDL + or only the GPL Version 2, indicate your decision by adding + "[Contributor] elects to include this software in this distribution + under the [CDDL or GPL Version 2] license." If you do not indicate a + single choice of license, a recipient has the option to distribute + your version of this file under either the CDDL, the GPL Version 2 or + to extend the choice of license to its licensees as provided above. + However, if you add GPL Version 2 code and therefore, elected the GPL + Version 2 license, then the option applies only if the new code is + made subject to such option by the copyright holder. + +--> +<!DOCTYPE html> +<html> + <head> + <title>Knockout.fx Execution Harness</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <meta name="viewport" content="width=device-width"> + </head> + <body> + <h1>Knockout.fx in Equinox Execution Harness</h1> + </body> + <script></script> +</html> http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/ko-ws-tyrus/pom.xml ---------------------------------------------------------------------- diff --git a/ko-ws-tyrus/pom.xml b/ko-ws-tyrus/pom.xml new file mode 100644 index 0000000..9415d83 --- /dev/null +++ b/ko-ws-tyrus/pom.xml @@ -0,0 +1,194 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + + Copyright 2013-2016 Oracle and/or its affiliates. All rights reserved. + + Oracle and Java are registered trademarks of Oracle and/or its affiliates. + Other names may be trademarks of their respective owners. + + The contents of this file are subject to the terms of either the GNU + General Public License Version 2 only ("GPL") or the Common + Development and Distribution License("CDDL") (collectively, the + "License"). You may not use this file except in compliance with the + License. You can obtain a copy of the License at + http://www.netbeans.org/cddl-gplv2.html + or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + specific language governing permissions and limitations under the + License. When distributing the software, include this License Header + Notice in each file and include the License file at + nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + particular file as subject to the "Classpath" exception as provided + by Oracle in the GPL Version 2 section of the License file that + accompanied this code. If applicable, add the following below the + License Header, with the fields enclosed by brackets [] replaced by + your own identifying information: + "Portions Copyrighted [year] [name of copyright owner]" + + Contributor(s): + + The Original Software is NetBeans. The Initial Developer of the Original + Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + + If you wish your version of this file to be governed by only the CDDL + or only the GPL Version 2, indicate your decision by adding + "[Contributor] elects to include this software in this distribution + under the [CDDL or GPL Version 2] license." If you do not indicate a + single choice of license, a recipient has the option to distribute + your version of this file under either the CDDL, the GPL Version 2 or + to extend the choice of license to its licensees as provided above. + However, if you add GPL Version 2 code and therefore, elected the GPL + Version 2 license, then the option applies only if the new code is + made subject to such option by the copyright holder. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.netbeans.html</groupId> + <artifactId>pom</artifactId> + <version>2.0-SNAPSHOT</version> + </parent> + <groupId>org.netbeans.html</groupId> + <artifactId>ko-ws-tyrus</artifactId> + <version>2.0-SNAPSHOT</version> + <packaging>bundle</packaging> + <name>Tyrus Based WebSockets</name> + <url>http://maven.apache.org</url> + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.netbeans.html</groupId> + <artifactId>html4j-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <configuration> + <skip>false</skip> + </configuration> + </plugin> + </plugins> + </build> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <bundleSymbolicName>org.netbeans.html.ko-ws-tyrus</bundleSymbolicName> + </properties> + <dependencies> + <!-- compile only deps --> + <dependency> + <groupId>org.netbeans.api</groupId> + <artifactId>org-openide-util-lookup</artifactId> + <type>jar</type> + <scope>provided</scope> + </dependency> + + <!-- compile + runtime --> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>net.java.html</artifactId> + <version>${project.version}</version> + <type>jar</type> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>net.java.html.json</artifactId> + <version>${project.version}</version> + <type>jar</type> + </dependency> + <dependency> + <groupId>de.twentyeleven.skysail</groupId> + <artifactId>org.json-osgi</artifactId> + </dependency> + <dependency> + <artifactId>javax.websocket-api</artifactId> + <groupId>javax.websocket</groupId> + <type>jar</type> + <version>1.0</version> + </dependency> + + <!-- tyrus runtime --> + <dependency> + <groupId>org.glassfish.tyrus</groupId> + <artifactId>tyrus-client</artifactId> + <version>1.3.1</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.glassfish.tyrus</groupId> + <artifactId>tyrus-container-grizzly-client</artifactId> + <version>1.3.1</version> + <scope>runtime</scope> + </dependency> + + + + <!-- test only deps --> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>net.java.html.boot</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + <type>jar</type> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>net.java.html.json.tck</artifactId> + <version>${project.version}</version> + <scope>test</scope> + <type>jar</type> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>ko4j</artifactId> + <version>${project.version}</version> + <scope>test</scope> + <type>jar</type> + </dependency> + <dependency> + <groupId>org.glassfish.grizzly</groupId> + <artifactId>grizzly-http-server-core</artifactId> + <version>${grizzly.version}</version> + <scope>test</scope> + <type>jar</type> + </dependency> + <dependency> + <groupId>org.glassfish.grizzly</groupId> + <artifactId>grizzly-websockets-server</artifactId> + <version>${grizzly.version}</version> + <scope>test</scope> + <type>jar</type> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>net.java.html.boot.fx</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.glassfish.grizzly</groupId> + <artifactId>grizzly-http-server</artifactId> + <version>${grizzly.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.glassfish.grizzly</groupId> + <artifactId>grizzly-http-servlet</artifactId> + <version>${grizzly.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + <scope>test</scope> + <version>3.1.0</version> + </dependency> + </dependencies> + <description>An implementation module that provides support for WebSocket protocol on JDK7. +No need to use it when running on JDK8 with FX WebView supporting WebSocket directly.</description> +</project> http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java ---------------------------------------------------------------------- diff --git a/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java b/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java new file mode 100644 index 0000000..c91f6aa --- /dev/null +++ b/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java @@ -0,0 +1,301 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.wstyrus; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PushbackInputStream; +import java.io.Reader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.logging.Logger; +import net.java.html.js.JavaScriptBody; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.netbeans.html.json.spi.JSONCall; + +/** This is an implementation package - just + * include its JAR on classpath and use official {@link Context} API + * to access the functionality. + * + * @author Jaroslav Tulach + */ +final class LoadJSON implements Runnable { + private static final Logger LOG = Logger.getLogger(LoadJSON.class.getName()); + private static final Executor REQ = Executors.newCachedThreadPool(new ThreadFactory() { + @Override + public Thread newThread(Runnable runnable) { + Thread thread = Executors.defaultThreadFactory().newThread(runnable); + thread.setDaemon(true); + return thread; + } + }); + + private final JSONCall call; + private final URL base; + + + private LoadJSON(JSONCall call) { + this.call = call; + this.base = null; + } + + public static void loadJSON(JSONCall call) { + assert !"WebSocket".equals(call.getMethod()); + REQ.execute(new LoadJSON((call))); + } + + @Override + public void run() { + final String url; + Throwable error = null; + Object json = null; + + if (call.isJSONP()) { + url = call.composeURL("dummy"); + } else { + url = call.composeURL(null); + } + try { + final URL u = new URL(base, url.replace(" ", "%20")); + URLConnection conn = u.openConnection(); + if (call.isDoOutput()) { + conn.setDoOutput(true); + } + String h = call.getHeaders(); + if (h != null) { + int pos = 0; + while (pos < h.length()) { + int tagEnd = h.indexOf(':', pos); + if (tagEnd == -1) { + break; + } + int r = h.indexOf('\r', tagEnd); + int n = h.indexOf('\n', tagEnd); + if (r == -1) { + r = h.length(); + } + if (n == -1) { + n = h.length(); + } + String key = h.substring(pos, tagEnd).trim(); + String val = h.substring(tagEnd + 1, Math.min(r, n)).trim(); + conn.setRequestProperty(key, val);; + pos = Math.max(r, n); + } + } + if (call.getMethod() != null && conn instanceof HttpURLConnection) { + ((HttpURLConnection) conn).setRequestMethod(call.getMethod()); + } + if (call.isDoOutput()) { + final OutputStream os = conn.getOutputStream(); + call.writeData(os); + os.flush(); + } + final PushbackInputStream is = new PushbackInputStream( + conn.getInputStream(), 1 + ); + boolean[] arrayOrString = { false, false }; + detectJSONType(call.isJSONP(), is, arrayOrString); + try { + if (arrayOrString[1]) { + throw new JSONException(""); + } + JSONTokener tok = createTokener(is); + Object obj; + obj = arrayOrString[0] ? new JSONArray(tok) : new JSONObject(tok); + json = convertToArray(obj); + } catch (JSONException ex) { + Reader r = new InputStreamReader(is, "UTF-8"); + StringBuilder sb = new StringBuilder(); + for (;;) { + int ch = r.read(); + if (ch == -1) { + break; + } + sb.append((char)ch); + } + json = sb.toString(); + } + } catch (IOException ex) { + error = ex; + } finally { + if (error != null) { + call.notifyError(error); + } else { + call.notifySuccess(json); + } + } + } + + private static void detectJSONType(boolean skipAnything, final PushbackInputStream is, boolean[] arrayOrString) throws IOException { + for (;;) { + int ch = is.read(); + if (ch == -1) { + arrayOrString[1] = true; + break; + } + if (Character.isWhitespace(ch)) { + continue; + } + + if (ch == '[') { + is.unread(ch); + arrayOrString[0] = true; + break; + } + if (ch == '{') { + is.unread(ch); + break; + } + if (!skipAnything) { + is.unread(ch); + arrayOrString[1] = true; + break; + } + } + } + + private static JSONTokener createTokener(InputStream is) throws IOException { + Reader r = new InputStreamReader(is, "UTF-8"); + try { + return new JSONTokener(r); + } catch (LinkageError ex) { + // phones may carry outdated version of JSONTokener + StringBuilder sb = new StringBuilder(); + for (;;) { + int ch = r.read(); + if (ch == -1) { + break; + } + sb.append((char)ch); + } + return new JSONTokener(sb.toString()); + } + } + + static Object convertToArray(Object o) throws JSONException { + if (o instanceof JSONArray) { + JSONArray ja = (JSONArray)o; + Object[] arr = new Object[ja.length()]; + for (int i = 0; i < arr.length; i++) { + arr[i] = convertToArray(ja.get(i)); + } + return arr; + } else if (o instanceof JSONObject) { + JSONObject obj = (JSONObject)o; + Iterator it = obj.keys(); + List<Object> collect = new ArrayList<Object>(); + while (it.hasNext()) { + String key = (String)it.next(); + final Object val = obj.get(key); + final Object newVal = convertToArray(val); + if (val != newVal) { + collect.add(key); + collect.add(newVal); + } + } + int size = collect.size(); + for (int i = 0; i < size; i += 2) { + obj.put((String) collect.get(i), collect.get(i + 1)); + } + return obj; + } else if (o == JSONObject.NULL) { + return null; + } else { + return o; + } + } + + public static void extractJSON(Object jsonObject, String[] props, Object[] values) { + if (jsonObject instanceof JSONObject) { + JSONObject obj = (JSONObject)jsonObject; + for (int i = 0; i < props.length; i++) { + Object val = obj.opt(props[i]); + if (val == JSONObject.NULL) { + val = null; + } + values[i] = val; + } + return; + } + for (int i = 0; i < props.length; i++) { + values[i] = getProperty(jsonObject, props[i]); + } + } + + @JavaScriptBody(args = {"object", "property"}, body = + "var ret;\n" + + "if (property === null) ret = object;\n" + + "else if (object === null) ret = null;\n" + + "else ret = object[property];\n" + + "return ret ? (typeof ko === 'undefined' ? ret : ko.utils.unwrapObservable(ret)) : null;" + ) + private static Object getProperty(Object object, String property) { + return null; + } + + public static Object parse(InputStream is) throws IOException { + try { + PushbackInputStream push = new PushbackInputStream(is, 1); + boolean[] arrayOrString = { false, false }; + detectJSONType(false, push, arrayOrString); + JSONTokener t = createTokener(push); + Object obj = arrayOrString[0] ? new JSONArray(t) : new JSONObject(t); + return convertToArray(obj); + } catch (JSONException ex) { + throw new IOException(ex); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/TyrusContext.java ---------------------------------------------------------------------- diff --git a/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/TyrusContext.java b/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/TyrusContext.java new file mode 100644 index 0000000..498da94 --- /dev/null +++ b/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/TyrusContext.java @@ -0,0 +1,209 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.wstyrus; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Iterator; +import javax.websocket.ClientEndpoint; +import javax.websocket.ContainerProvider; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; +import net.java.html.json.OnReceive; +import org.netbeans.html.context.spi.Contexts; +import org.netbeans.html.json.spi.JSONCall; +import org.netbeans.html.json.spi.Transfer; +import org.netbeans.html.json.spi.WSTransfer; +import org.netbeans.html.wstyrus.TyrusContext.Comm; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.openide.util.lookup.ServiceProvider; + +/** This is an implementation module that provides support for + * WebSocket protocol for {@link OnReceive} communication end point for + * JDK7. + * <p> + * Don't deal with this module directly, rather use the + * {@link OnReceive @OnReceive(url="ws://...", ...)} API to establish your + * WebSocket connection. + * <p> + * There is no need to include this module in your application if you are + * running on JDK8. JDK8 WebView provides its own implementation of the + * WebSocket API based on WebSocket object inside a browser. This is included + * in the <code>org.netbeans.html:ko4j:1.0</code> module. + * + * @author Jaroslav Tulach + */ [email protected]("tyrus") +@ServiceProvider(service = Contexts.Provider.class) +public final class TyrusContext +implements Contexts.Provider, WSTransfer<Comm>, Transfer { + @Override + public void fillContext(Contexts.Builder context, Class<?> requestor) { + // default WebSocket transfer implementation is registered + // in ko-fx module with 100, provide this one as a fallback only + context.register(WSTransfer.class, this, 1000); + context.register(Transfer.class, this, 1000); + } + + @Override + public Comm open(String url, JSONCall callback) { + try { + return new Comm(new URI(url), callback); + } catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + + @Override + public void send(Comm socket, JSONCall data) { + socket.session.getAsyncRemote().sendText(data.getMessage()); + } + + @Override + public void close(Comm socket) { + try { + final Session s = socket.session; + if (s != null) { + s.close(); + } + } catch (IOException ex) { + socket.callback.notifyError(ex); + } + } + + @Override + public void extract(Object obj, String[] props, Object[] values) { + LoadJSON.extractJSON(obj, props, values); + } + + @Override + public Object toJSON(InputStream is) throws IOException { + return LoadJSON.parse(is); + } + + @Override + public void loadJSON(JSONCall call) { + LoadJSON.loadJSON(call); + } + + /** Implementation class in an implementation. Represents a {@link ClientEndpoint} of the + * WebSocket channel. You are unlikely to get on hold of it. + */ + @ClientEndpoint + public static final class Comm { + private final JSONCall callback; + private Session session; + + Comm(final URI url, JSONCall callback) { + this.callback = callback; + try { + final WebSocketContainer c = ContainerProvider.getWebSocketContainer(); + c.connectToServer(Comm.this, url); + } catch (Exception ex) { + wasAnError(ex); + } + } + + @OnOpen + public synchronized void open(Session s) { + this.session = s; + callback.notifySuccess(null); + } + + @OnClose + public void close() { + this.session = null; + callback.notifyError(null); + } + + @OnMessage + public void message(final String orig, Session s) { + Object json; + String data = orig.trim(); + try { + JSONTokener tok = new JSONTokener(data); + Object obj = data.startsWith("[") ? new JSONArray(tok) : new JSONObject(tok); + json = convertToArray(obj); + } catch (JSONException ex) { + json = data; + } + callback.notifySuccess(json); + } + + @OnError + public void wasAnError(Throwable t) { + callback.notifyError(t); + } + + static Object convertToArray(Object o) throws JSONException { + if (o instanceof JSONArray) { + JSONArray ja = (JSONArray) o; + Object[] arr = new Object[ja.length()]; + for (int i = 0; i < arr.length; i++) { + arr[i] = convertToArray(ja.get(i)); + } + return arr; + } else if (o instanceof JSONObject) { + JSONObject obj = (JSONObject) o; + Iterator it = obj.keys(); + while (it.hasNext()) { + String key = (String) it.next(); + obj.put(key, convertToArray(obj.get(key))); + } + return obj; + } else { + return o; + } + } + + } // end of Comm +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusDynamicHTTP.java ---------------------------------------------------------------------- diff --git a/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusDynamicHTTP.java b/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusDynamicHTTP.java new file mode 100644 index 0000000..96b41cd --- /dev/null +++ b/ko-ws-tyrus/src/test/java/org/netbeans/html/wstyrus/TyrusDynamicHTTP.java @@ -0,0 +1,262 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.wstyrus; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.glassfish.grizzly.PortRange; +import org.glassfish.grizzly.http.server.HttpHandler; +import org.glassfish.grizzly.http.server.HttpServer; +import org.glassfish.grizzly.http.server.NetworkListener; +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.grizzly.http.server.Response; +import org.glassfish.grizzly.http.server.ServerConfiguration; +import org.glassfish.grizzly.websockets.WebSocket; +import org.glassfish.grizzly.websockets.WebSocketAddOn; +import org.glassfish.grizzly.websockets.WebSocketApplication; +import org.glassfish.grizzly.websockets.WebSocketEngine; + +/** + * + * @author Jaroslav Tulach + */ +final class TyrusDynamicHTTP extends HttpHandler { + private static int resourcesCount; + private static List<Resource> resources; + private static ServerConfiguration conf; + private static HttpServer server; + + private TyrusDynamicHTTP() { + } + + static URI initServer() throws Exception { + server = HttpServer.createSimpleServer(null, new PortRange(8080, 65535)); + final WebSocketAddOn addon = new WebSocketAddOn(); + for (NetworkListener listener : server.getListeners()) { + listener.registerAddOn(addon); + } + resources = new ArrayList<Resource>(); + + conf = server.getServerConfiguration(); + final TyrusDynamicHTTP dh = new TyrusDynamicHTTP(); + + conf.addHttpHandler(dh, "/"); + + server.start(); + + return pageURL("http", server, "/test.html"); + } + + @Override + public void service(Request request, Response response) throws Exception { + if ("/test.html".equals(request.getRequestURI())) { + response.setContentType("text/html"); + final InputStream is = TyrusDynamicHTTP.class.getResourceAsStream("test.html"); + copyStream(is, response.getOutputStream(), null); + return; + } + if ("/dynamic".equals(request.getRequestURI())) { + String mimeType = request.getParameter("mimeType"); + List<String> params = new ArrayList<String>(); + boolean webSocket = false; + for (int i = 0;; i++) { + String p = request.getParameter("param" + i); + if (p == null) { + break; + } + if ("protocol:ws".equals(p)) { + webSocket = true; + continue; + } + params.add(p); + } + final String cnt = request.getParameter("content"); + String mangle = cnt.replace("%20", " ").replace("%0A", "\n"); + ByteArrayInputStream is = new ByteArrayInputStream(mangle.getBytes("UTF-8")); + URI url; + final Resource res = new Resource(is, mimeType, "/dynamic/res" + ++resourcesCount, params.toArray(new String[params.size()])); + if (webSocket) { + url = registerWebSocket(res); + } else { + url = registerResource(res); + } + response.getWriter().write(url.toString()); + response.getWriter().write("\n"); + return; + } + + for (Resource r : resources) { + if (r.httpPath.equals(request.getRequestURI())) { + response.setContentType(r.httpType); + r.httpContent.reset(); + String[] params = null; + if (r.parameters.length != 0) { + params = new String[r.parameters.length]; + for (int i = 0; i < r.parameters.length; i++) { + params[i] = request.getParameter(r.parameters[i]); + if (params[i] == null) { + if ("http.method".equals(r.parameters[i])) { + params[i] = request.getMethod().toString(); + } else if ("http.requestBody".equals(r.parameters[i])) { + Reader rdr = request.getReader(); + StringBuilder sb = new StringBuilder(); + for (;;) { + int ch = rdr.read(); + if (ch == -1) { + break; + } + sb.append((char) ch); + } + params[i] = sb.toString(); + } else if (r.parameters[i].startsWith("http.header.")) { + params[i] = request.getHeader(r.parameters[i].substring(12)); + } + } + if (params[i] == null) { + params[i] = "null"; + } + } + } + + copyStream(r.httpContent, response.getOutputStream(), null, params); + } + } + } + + private URI registerWebSocket(Resource r) { + WebSocketEngine.getEngine().register("", r.httpPath, new WS(r)); + return pageURL("ws", server, r.httpPath); + } + + private URI registerResource(Resource r) { + if (!resources.contains(r)) { + resources.add(r); + conf.addHttpHandler(this, r.httpPath); + } + return pageURL("http", server, r.httpPath); + } + + private static URI pageURL(String proto, HttpServer server, final String page) { + NetworkListener listener = server.getListeners().iterator().next(); + int port = listener.getPort(); + try { + return new URI(proto + "://localhost:" + port + page); + } catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + + static final class Resource { + + final InputStream httpContent; + final String httpType; + final String httpPath; + final String[] parameters; + + Resource(InputStream httpContent, String httpType, String httpPath, + String[] parameters) { + httpContent.mark(Integer.MAX_VALUE); + this.httpContent = httpContent; + this.httpType = httpType; + this.httpPath = httpPath; + this.parameters = parameters; + } + } + + static void copyStream(InputStream is, OutputStream os, String baseURL, String... params) throws IOException { + for (;;) { + int ch = is.read(); + if (ch == -1) { + break; + } + if (ch == '$' && params.length > 0) { + int cnt = is.read() - '0'; + if (baseURL != null && cnt == 'U' - '0') { + os.write(baseURL.getBytes("UTF-8")); + } else { + if (cnt >= 0 && cnt < params.length) { + os.write(params[cnt].getBytes("UTF-8")); + } else { + os.write('$'); + os.write(cnt + '0'); + } + } + } else { + os.write(ch); + } + } + } + + private static class WS extends WebSocketApplication { + private final Resource r; + + private WS(Resource r) { + this.r = r; + } + + @Override + public void onMessage(WebSocket socket, String text) { + try { + r.httpContent.reset(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + copyStream(r.httpContent, out, null, text); + String s = new String(out.toByteArray(), "UTF-8"); + socket.send(s); + } catch (IOException ex) { + LOG.log(Level.WARNING, null, ex); + } + } + private static final Logger LOG = Logger.getLogger(WS.class.getName()); + + } +}
