# HG changeset patch # User Jaroslav Tulach # Date 1456719931 -3600 # Node ID b547f8f663f5a43cd635b46634ef71bcce45edba # Parent 6f1a8b251b7d1402a70422f10af556e058be77d6 #257849: xhr4j module uses java.net package to handle @OnReceive requests to workaround CORS limitations diff -r 6f1a8b251b7d -r b547f8f663f5 context/src/main/java/org/netbeans/html/context/spi/Contexts.java --- a/context/src/main/java/org/netbeans/html/context/spi/Contexts.java Mon Sep 21 21:19:13 2015 +0200 +++ b/context/src/main/java/org/netbeans/html/context/spi/Contexts.java Mon Feb 29 05:25:31 2016 +0100 @@ -42,6 +42,7 @@ */ package org.netbeans.html.context.spi; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -159,6 +160,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) + @Documented public @interface Id { /** Identifier(s) for the implementation. * diff -r 6f1a8b251b7d -r b547f8f663f5 json/src/main/java/org/netbeans/html/json/impl/RcvrJSON.java --- a/json/src/main/java/org/netbeans/html/json/impl/RcvrJSON.java Mon Sep 21 21:19:13 2015 +0200 +++ b/json/src/main/java/org/netbeans/html/json/impl/RcvrJSON.java Mon Feb 29 05:25:31 2016 +0100 @@ -42,8 +42,7 @@ */ package org.netbeans.html.json.impl; -import java.util.ArrayList; -import net.java.html.BrwsrCtx; +import java.util.concurrent.Callable; /** Super type for those who wish to receive JSON messages. * @@ -94,11 +93,20 @@ }; } - public static MsgEvnt createMessage(final Object value) { + public static MsgEvnt createMessage(final Object value) { return new MsgEvnt() { + private Object val = value; + @Override public Object[] getValues() { - return value instanceof Object[] ? (Object[])value : new Object[] { value }; + if (val instanceof Callable) { + try { + val = ((Callable)val).call(); + } catch (Exception ex) { + throw new IllegalStateException("Cannot compute " + val, ex); + } + } + return val instanceof Object[] ? (Object[])val : new Object[] { val }; } @Override diff -r 6f1a8b251b7d -r b547f8f663f5 pom.xml --- a/pom.xml Mon Sep 21 21:19:13 2015 +0200 +++ b/pom.xml Mon Feb 29 05:25:31 2016 +0100 @@ -36,6 +36,7 @@ equinox-agentclass-hook boot-script boot-agent-test + xhr4j diff -r 6f1a8b251b7d -r b547f8f663f5 xhr4j/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xhr4j/pom.xml Mon Feb 29 05:25:31 2016 +0100 @@ -0,0 +1,122 @@ + + + 4.0.0 + + org.netbeans.html + pom + 1.2.3 + + org.netbeans.html + xhr4j + 1.2.3 + bundle + XHR via Java + http://maven.apache.org + + + + org.apache.felix + maven-bundle-plugin + + + org.netbeans.html + html4j-maven-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + false + + + + + + UTF-8 + org.netbeans.html.xhr4j + + + + + org.netbeans.api + org-openide-util-lookup + jar + provided + + + + + ${project.groupId} + net.java.html + ${project.version} + jar + + + ${project.groupId} + net.java.html.json + ${project.version} + jar + + + + ${project.groupId} + net.java.html.boot + ${project.version} + provided + jar + + + ${project.groupId} + net.java.html.json.tck + ${project.version} + test + jar + + + ${project.groupId} + ko4j + ${project.version} + test + jar + + + org.glassfish.grizzly + grizzly-http-server-core + ${grizzly.version} + test + jar + + + org.glassfish.grizzly + grizzly-websockets-server + ${grizzly.version} + test + jar + + + ${project.groupId} + net.java.html.boot.fx + ${project.version} + test + + + org.glassfish.grizzly + grizzly-http-server + ${grizzly.version} + test + + + org.glassfish.grizzly + grizzly-http-servlet + ${grizzly.version} + test + + + javax.servlet + javax.servlet-api + test + + + Implementation module with support for XHR via Java. +Use it to workaround CORS limitations. + diff -r 6f1a8b251b7d -r b547f8f663f5 xhr4j/src/main/java/org/netbeans/html/xhr4j/LoadJSON.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xhr4j/src/main/java/org/netbeans/html/xhr4j/LoadJSON.java Mon Feb 29 05:25:31 2016 +0100 @@ -0,0 +1,273 @@ +/** + * 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-2014 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.xhr4j; + +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.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.concurrent.Callable; +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.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); + thread.setName("xhr4j daemon"); + 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); + String response = readStream(is); + if (call.isJSONP()) { + response = '(' + response; + } + json = new Result(response, arrayOrString[0], arrayOrString[1]); + } catch (IOException ex) { + error = ex; + } finally { + if (error != null) { + call.notifyError(error); + } else { + call.notifySuccess(json); + } + } + } + + private static final class Result implements Callable { + private final String response; + private final boolean array; + private final boolean plain; + + Result(String response, boolean array, boolean plain) { + this.response = response; + this.array = array; + this.plain = plain; + } + + @Override + public Object call() throws Exception { + if (plain) { + return response; + } else { + if (array) { + Object r = parse(response); + Object[] arr = r instanceof Object[] ? (Object[])r : new Object[] { r }; + for (int i = 0; i < arr.length; i++) { + arr[i] = new JSObjToStr(response, arr[i]); + } + return arr; + } else { + return new JSObjToStr(response, parse(response)); + } + } + } + } + private static final class JSObjToStr { + final String str; + final Object obj; + + public JSObjToStr(Object str, Object obj) { + this.str = str == null ? "" : str.toString(); + this.obj = obj; + } + + @Override + public String toString() { + return str; + } + } + + static String readStream(InputStream is) throws IOException, UnsupportedEncodingException { + Reader r = new InputStreamReader(is, "UTF-8"); + StringBuilder sb = new StringBuilder(); + char[] arr = new char[4096]; + for (;;) { + int len = r.read(arr); + if (len == -1) { + break; + } + sb.append(arr, 0, len); + } + return sb.toString(); + } + + 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; + } + } + } + + @JavaScriptBody(args = {"object", "property"}, + body + = "if (property === null) return object;\n" + + "if (object === null) return null;\n" + + "var p = object[property]; return p ? p : null;" + ) + private static Object getProperty(Object object, String property) { + return null; + } + + @JavaScriptBody(args = {"s"}, body = "return eval('(' + s + ')');") + static Object parse(String s) { + throw new IllegalStateException("No parser context for " + s); + } + + static void extractJSON(Object js, String[] props, Object[] values) { + if (js instanceof JSObjToStr) { + js = ((JSObjToStr)js).obj; + } + for (int i = 0; i < props.length; i++) { + values[i] = getProperty(js, props[i]); + } + } + +} diff -r 6f1a8b251b7d -r b547f8f663f5 xhr4j/src/main/java/org/netbeans/html/xhr4j/XmlHttpResourceContext.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xhr4j/src/main/java/org/netbeans/html/xhr4j/XmlHttpResourceContext.java Mon Feb 29 05:25:31 2016 +0100 @@ -0,0 +1,88 @@ +/** + * 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-2014 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.xhr4j; + +import java.io.IOException; +import java.io.InputStream; +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.openide.util.lookup.ServiceProvider; + +/** Implementation module with support for XHR via Java. + * Handles {@link OnReceive} requests by using Java to connect to given + * URL and then parsing it via JavaScript. Use this module if you have + * problems with CORS - as the Java connection isn't restricted by CORS + * rules. + * + * Registers {@link Transfer} technology at position 50. + * The {@link Contexts.Id} of the technology is xhr4j. + * + * @author Jaroslav Tulach + * @since 1.3 + */ +@Contexts.Id("xhr4j") +@ServiceProvider(service = Contexts.Provider.class) +public final class XmlHttpResourceContext +implements Contexts.Provider, Transfer { + @Override + public void fillContext(Contexts.Builder context, Class requestor) { + context.register(Transfer.class, this, 50); + } + + @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(LoadJSON.readStream(is)); + } + + @Override + public void loadJSON(JSONCall call) { + LoadJSON.loadJSON(call); + } +} diff -r 6f1a8b251b7d -r b547f8f663f5 xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonDynamicHTTP.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonDynamicHTTP.java Mon Feb 29 05:25:31 2016 +0100 @@ -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-2014 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.xhr4j; + +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 JsonDynamicHTTP extends HttpHandler { + private static int resourcesCount; + private static List resources; + private static ServerConfiguration conf; + private static HttpServer server; + + private JsonDynamicHTTP() { + } + + 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(); + + conf = server.getServerConfiguration(); + final JsonDynamicHTTP dh = new JsonDynamicHTTP(); + + 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 = JsonDynamicHTTP.class.getResourceAsStream("test.html"); + copyStream(is, response.getOutputStream(), null); + return; + } + if ("/dynamic".equals(request.getRequestURI())) { + String mimeType = request.getParameter("mimeType"); + List params = new ArrayList(); + 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()); + + } +} diff -r 6f1a8b251b7d -r b547f8f663f5 xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonFX.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonFX.java Mon Feb 29 05:25:31 2016 +0100 @@ -0,0 +1,126 @@ +/** + * 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-2014 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.xhr4j; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javafx.application.Platform; +import org.netbeans.html.boot.impl.FnContext; +import org.netbeans.html.boot.spi.Fn; +import org.testng.ITest; +import org.testng.annotations.Test; + +/** + * + * @author Jaroslav Tulach + */ +public final class JsonFX implements ITest, Runnable { + private final Fn.Presenter p; + private final Method m; + private Object result; + private Object inst; + private int count; + + JsonFX(Fn.Presenter 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; + try { + FnContext.currentPresenter(p); + if (inst == null) { + inst = m.getDeclaringClass().newInstance(); + } + System.err.println("invoke " + m + " with " + count); + result = m.invoke(inst); + if (result == null) { + result = this; + } + } catch (InvocationTargetException ex) { + Throwable r = ex.getTargetException(); + if (r instanceof InterruptedException) { + if (count++ < 100) { + 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(); + } + FnContext.currentPresenter(null); + } + } + +} diff -r 6f1a8b251b7d -r b547f8f663f5 xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonKnockoutTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xhr4j/src/test/java/org/netbeans/html/xhr4j/JsonKnockoutTest.java Mon Feb 29 05:25:31 2016 +0100 @@ -0,0 +1,217 @@ +/** + * 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-2014 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.xhr4j; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +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.tck.KOTest; +import org.netbeans.html.json.tck.KnockoutTCK; +import org.netbeans.html.ko4j.KO4J; +import org.openide.util.lookup.ServiceProvider; +import org.testng.Assert; +import static org.testng.Assert.*; +import org.testng.annotations.Factory; + +/** + * + * @author Jaroslav Tulach + */ +@ServiceProvider(service = KnockoutTCK.class) +public final class JsonKnockoutTest extends KnockoutTCK { + private static Class browserClass; + private static Fn.Presenter browserContext; + + public JsonKnockoutTest() { + } + + @Factory public static Object[] compatibilityTests() throws Exception { + Class[] arr = testClasses(); + for (int i = 0; i < arr.length; i++) { + assertEquals(arr[i].getClassLoader(), + JsonKnockoutTest.class.getClassLoader(), + "All classes loaded by the same classloader" + ); + } + + URI uri = JsonDynamicHTTP.initServer(); + + final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(JsonKnockoutTest.class). + loadPage(uri.toString()). + invoke("initialized"); + + Executors.newSingleThreadExecutor().submit(new Runnable() { + @Override + public void run() { + bb.showAndWait(); + } + }); + + ClassLoader l = getClassLoader(); + List res = new ArrayList(); + for (int i = 0; i < arr.length; i++) { + Class c = Class.forName(arr[i].getName(), true, l); + Class koTest = + c.getClassLoader().loadClass(KOTest.class.getName()). + asSubclass(Annotation.class); + for (Method m : c.getMethods()) { + if (m.getAnnotation(koTest) != null) { + res.add(new JsonFX(browserContext, m)); + } + } + } + return res.toArray(); + } + + static synchronized ClassLoader getClassLoader() throws InterruptedException { + while (browserClass == null) { + JsonKnockoutTest.class.wait(); + } + return browserClass.getClassLoader(); + } + + public static synchronized void initialized(Class browserCls) throws Exception { + browserClass = browserCls; + browserContext = Fn.activePresenter(); + JsonKnockoutTest.class.notifyAll(); + } + + public static void initialized() throws Exception { + Assert.assertSame(JsonKnockoutTest.class.getClassLoader(), + ClassLoader.getSystemClassLoader(), + "No special classloaders" + ); + JsonKnockoutTest.initialized(JsonKnockoutTest.class); + } + + @Override + public boolean canFailWebSocketTest() { + return true; + } + + @Override + public BrwsrCtx createContext() { + KO4J ko = new KO4J(browserContext); + XmlHttpResourceContext tc = new XmlHttpResourceContext(); + Contexts.Builder cb = Contexts.newBuilder(). + register(Technology.class, ko.knockout(), 10). + register(Executor.class, (Executor)browserContext, 10). + register(Fn.Presenter.class, (Fn.Presenter)browserContext, 10); + tc.fillContext(cb, browserClass); + return cb.build(); + } + + @Override + public Object createJSON(Map values) { + Object json = createJSON(); + for (Map.Entry entry : values.entrySet()) { + setProperty(json, entry.getKey(), entry.getValue()); + } + return json; + } + + @JavaScriptBody(args = {}, body = "return new Object();") + private static native Object createJSON(); + + @JavaScriptBody(args = {"json", "key", "value"}, body = "json[key] = value;") + private static native void setProperty(Object json, String key, Object value); + + @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); + } + } +} diff -r 6f1a8b251b7d -r b547f8f663f5 xhr4j/src/test/resources/org/netbeans/html/xhr4j/test.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xhr4j/src/test/resources/org/netbeans/html/xhr4j/test.html Mon Feb 29 05:25:31 2016 +0100 @@ -0,0 +1,56 @@ + + + + + XHR via Java Harness + + + + +

XHR via Java Harness

+ + +