# HG changeset patch # User Jaroslav Tulach # Date 1406810160 -7200 # Node ID ee3614350fc81cf8842c4fd591f6c430c6e69bfd # Parent 561536cc73fd28ee9039cd7c07f84aa07e8a673d Adding classloader method to allow the system to run in Felix diff -r 561536cc73fd -r ee3614350fc8 boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java --- a/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java Fri Jul 25 14:15:20 2014 +0200 +++ b/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java Thu Jul 31 14:36:00 2014 +0200 @@ -121,9 +121,34 @@ public static void load( WebView webView, final URL url, Runnable onPageLoad ) { + load(webView, url, onPageLoad, null); + } + + /** Enables the Java/JavaScript bridge (that supports {@link JavaScriptBody} and co.) + * in the provided webView. This method returns + * immediately. Once the support is active, it calls back specified + * method in onPageLoad's run method. + * This is more convenient way to initialize the webview, + * but it requires one to make sure + * all {@link JavaScriptBody} methods has been post-processed during + * compilation and there will be no need to instantiate new classloader. + *

+ * This method sets {@link WebView#getUserData()} and {@link #runInBrowser(javafx.scene.web.WebView, java.lang.Runnable)} + * relies on the value. Please don't alter it. + * + * @param webView the instance of Web View to tweak + * @param url the URL of the HTML page to load into the view + * @param onPageLoad callback to call when the page is ready + * @param loader the loader to use when constructing initial {@link BrwsrCtx} or null + * @since 0.9 + */ + public static void load( + WebView webView, final URL url, Runnable onPageLoad, ClassLoader loader + ) { BrowserBuilder.newBrowser(new Load(webView)). loadPage(url.toExternalForm()). loadFinished(onPageLoad). + classloader(loader). showAndWait(); } diff -r 561536cc73fd -r ee3614350fc8 boot/src/main/java/net/java/html/boot/BrowserBuilder.java --- a/boot/src/main/java/net/java/html/boot/BrowserBuilder.java Fri Jul 25 14:15:20 2014 +0200 +++ b/boot/src/main/java/net/java/html/boot/BrowserBuilder.java Thu Jul 31 14:36:00 2014 +0200 @@ -108,6 +108,7 @@ private String methodName; private String[] methodArgs; private final Object[] context; + private ClassLoader loader; private BrowserBuilder(Object[] context) { this.context = context; @@ -186,6 +187,22 @@ return this; } + /** Loader to use when searching for classes to initialize. + * If specified, this loader is going to be used to load {@link Fn.Presenter} + * and {@link Contexts#fillInByProviders(java.lang.Class, org.apidesign.html.context.spi.Contexts.Builder) fill} {@link BrwsrCtx} in. + * Specifying special classloader may be useful in modular systems, + * like OSGi, where one needs to load classes from many otherwise independent + * modules. + * + * @param l the loader to use (or null) + * @return this builder + * @since 0.9 + */ + public BrowserBuilder classloader(ClassLoader l) { + this.loader = l; + return this; + } + /** Shows the browser, loads specified page in and executes the * {@link #invoke(java.lang.String, java.lang.String[]) initialization method}. * The method returns when the browser is closed. @@ -277,6 +294,11 @@ } } + if (dfnr == null && loader != null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class, loader)) { + dfnr = o; + break; + } + if (dfnr == null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class)) { dfnr = o; break; @@ -286,12 +308,17 @@ throw new IllegalStateException("Can't find any Fn.Presenter"); } - final ClassLoader loader; - if (FnUtils.isJavaScriptCapable(myCls.getClassLoader())) { - loader = myCls.getClassLoader(); + final ClassLoader activeLoader; + if (loader != null) { + if (!FnUtils.isJavaScriptCapable(loader)) { + throw new IllegalStateException("Loader cannot resolve @JavaScriptBody: " + loader); + } + activeLoader = loader; + } else if (FnUtils.isJavaScriptCapable(myCls.getClassLoader())) { + activeLoader = myCls.getClassLoader(); } else { FImpl impl = new FImpl(myCls.getClassLoader()); - loader = FnUtils.newLoader(impl, dfnr, myCls.getClassLoader().getParent()); + activeLoader = FnUtils.newLoader(impl, dfnr, myCls.getClassLoader().getParent()); } final Fn.Presenter dP = dfnr; @@ -303,8 +330,8 @@ final Fn.Presenter aP = Fn.activePresenter(); final Fn.Presenter currentP = aP != null ? aP : dP; - Thread.currentThread().setContextClassLoader(loader); - final Class newClazz = Class.forName(myCls.getName(), true, loader); + Thread.currentThread().setContextClassLoader(activeLoader); + final Class newClazz = Class.forName(myCls.getName(), true, activeLoader); if (browserClass != null) { browserClass[0] = newClazz; } diff -r 561536cc73fd -r ee3614350fc8 ko-felix-test/pom.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko-felix-test/pom.xml Thu Jul 31 14:36:00 2014 +0200 @@ -0,0 +1,123 @@ + + + 4.0.0 + + org.netbeans.html + pom + 0.8.3 + + KO Tests in Felix OSGi Container + ko-felix-test + bundle + Runs the TCK for Knockout in Felix OSGi Container + + none + + + + + org.apache.felix + maven-bundle-plugin + + + org.netbeans.html + html4j-maven-plugin + + + maven-failsafe-plugin + 2.16 + + + ${project.build.directory}/${project.build.finalName}.jar + + + + + + integration-test + verify + + + + + + + + + com.oracle + javafx + 2.2 + system + ${jfxrt.jar} + + + de.twentyeleven.skysail + org.json-osgi + + + org.netbeans.html + net.java.html.json + ${project.version} + + + org.testng + testng + test + + + ${project.groupId} + net.java.html.json.tck + ${project.version} + + + org.netbeans.api + org-openide-util-lookup + provided + + + org.netbeans.html + net.java.html.boot + ${project.version} + jar + + + ${project.groupId} + ko4j + ${project.version} + + + ${project.groupId} + net.java.html.boot.fx + ${project.version} + test + + + org.glassfish.grizzly + grizzly-http-server + ${grizzly.version} + test + + + org.glassfish.grizzly + grizzly-websockets-server + ${grizzly.version} + test + jar + + + org.glassfish.grizzly + grizzly-http-servlet + ${grizzly.version} + test + + + javax.servlet + javax.servlet-api + test + + + org.apache.felix + org.apache.felix.framework + + + \ No newline at end of file diff -r 561536cc73fd -r ee3614350fc8 ko-felix-test/src/main/java/org/netbeans/html/ko/felix/test/KnockoutFelixTCKImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko-felix-test/src/main/java/org/netbeans/html/ko/felix/test/KnockoutFelixTCKImpl.java Thu Jul 31 14:36:00 2014 +0200 @@ -0,0 +1,279 @@ +/** + * 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.ko.felix.test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Constructor; +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.Collections; +import java.util.Enumeration; +import java.util.List; +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.apidesign.html.boot.spi.Fn; +import org.apidesign.html.context.spi.Contexts; +import org.apidesign.html.json.spi.Technology; +import org.apidesign.html.json.spi.Transfer; +import org.apidesign.html.json.tck.KnockoutTCK; +import org.json.JSONException; +import org.json.JSONObject; +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 KnockoutFelixTCKImpl extends KnockoutTCK implements Callable { + + 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(KnockoutFelixTCKImpl.class). + loadPage(server.toString()). + invoke("initialized"); + + Executors.newSingleThreadExecutor().submit(new Runnable() { + @Override + public void run() { + try { + Bundle[] arr = FrameworkUtil.getBundle(BrowserBuilder.class).getBundleContext().getBundles(); + final ClassLoader osgiClassLoader = new AllBundlesLoader(arr); + bb.classloader(osgiClassLoader); + bb.showAndWait(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + }); + } + + public static void initialized() throws Exception { + Bundle bundle = FrameworkUtil.getBundle(KnockoutFelixTCKImpl.class); + if (bundle == null) { + throw new IllegalStateException( + "Should be loaded from a bundle. But was: " + KnockoutFelixTCKImpl.class.getClassLoader() + ); + } + Class classpathClass = ClassLoader.getSystemClassLoader().loadClass( + "org.netbeans.html.ko.felix.test.KnockoutFelixIT" + ); + Method m = classpathClass.getMethod("initialized", Class.class, Object.class); + browserContext = Fn.activePresenter(); + m.invoke(null, KnockoutFelixTCKImpl.class, browserContext); + } + + @Override + public BrwsrCtx createContext() { + try { + Class fxCls = loadOSGiClass( + "org.netbeans.html.ko4j.FXContext", + FrameworkUtil.getBundle(KnockoutFelixTCKImpl.class).getBundleContext() + ); + final Constructor cnstr = fxCls.getConstructor(Fn.Presenter.class); + cnstr.setAccessible(true); + Object fx = cnstr.newInstance(browserContext); + Contexts.Builder cb = Contexts.newBuilder(). + register(Technology.class, (Technology)fx, 10). + register(Transfer.class, (Transfer)fx, 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); + } + } + + @Override + public Object createJSON(Map values) { + JSONObject json = new JSONObject(); + for (Map.Entry entry : values.entrySet()) { + try { + json.put(entry.getKey(), entry.getValue()); + } catch (JSONException ex) { + throw new IllegalStateException(ex); + } + } + return json; + } + + @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; + } + + private static final class AllBundlesLoader extends ClassLoader { + private final Bundle[] arr; + + public AllBundlesLoader(Bundle[] arr) { + super(ClassLoader.getSystemClassLoader().getParent()); + this.arr = arr; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return loadClass(name, false); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + ClassNotFoundException err = null; + for (Bundle b : arr) { + try { + Class cls = b.loadClass(name); + if (FrameworkUtil.getBundle(cls) == b) { + return cls; + } + } catch (ClassNotFoundException ex) { + err = ex; + } + } + throw err; + } + + @Override + protected URL findResource(String name) { + for (Bundle b : arr) { + URL r = b.getResource(name); + if (r != null) { + return r; + } + } + return null; + } + + @Override + protected Enumeration findResources(String name) throws IOException { + List ret = new ArrayList(); + for (Bundle b : arr) { + Enumeration en = b.getResources(name); + if (en != null) while (en.hasMoreElements()) { + URL u = en.nextElement(); + ret.add(u); + } + } + return Collections.enumeration(ret); + } + + + + } +} diff -r 561536cc73fd -r ee3614350fc8 ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/DynamicHTTP.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/DynamicHTTP.java Thu Jul 31 14:36:00 2014 +0200 @@ -0,0 +1,259 @@ +/** + * 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.ko.felix.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 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(); + + 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 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(); + } + } + 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); + } + } + } +} diff -r 561536cc73fd -r ee3614350fc8 ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/KOFx.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/KOFx.java Thu Jul 31 14:36:00 2014 +0200 @@ -0,0 +1,128 @@ +/** + * 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.ko.felix.test; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.logging.Level; +import java.util.logging.Logger; +import javafx.application.Platform; +import org.apidesign.html.boot.spi.Fn; +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 = KnockoutFelixIT.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; + 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); + } + } + } + +} diff -r 561536cc73fd -r ee3614350fc8 ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/KnockoutFelixIT.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/KnockoutFelixIT.java Thu Jul 31 14:36:00 2014 +0200 @@ -0,0 +1,248 @@ +/** + * 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.ko.felix.test; + +import org.netbeans.html.ko.felix.test.KnockoutFelixTCKImpl; +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.apidesign.html.boot.spi.Fn; +import org.apidesign.html.json.tck.KOTest; +import org.apidesign.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 KnockoutFelixIT { + private static final Logger LOG = Logger.getLogger(KnockoutFelixIT.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 config = new HashMap(); + 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" + ); + framework = ff.newFramework(config); + framework.init(); + loadClassPathBundles(framework); + framework.start(); + for (Bundle b : framework.getBundleContext().getBundles()) { + try { + if (b.getSymbolicName().contains("felix.framework")) { + continue; + } + if (b.getSymbolicName().contains("grizzly.websockets-server")) { + continue; + } + b.start(); + LOG.log(Level.INFO, "Started {0}", b.getSymbolicName()); + } catch (BundleException ex) { + LOG.log(Level.WARNING, "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")) { + throw new IllegalStateException("Found " + name + " !"); + } + if (name.contains("felix.framework")) { + 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 KnockoutFelixTCKImpl.loadOSGiClass(c.getName(), KnockoutFelixIT.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(KnockoutFelixTCKImpl.class); + // initialize the TCK + Callable inst = (Callable) 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 res = new ArrayList(); + for (int i = 0; i < arr.length; i++) { + seekKOTests(arr[i], res); + } + return res.toArray(); + } + + private static void seekKOTests(Class c, List res) throws SecurityException, ClassNotFoundException { + Class 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) { + KnockoutFelixIT.class.wait(); + } + return browserClass.getClassLoader(); + } + + public static synchronized void initialized(Class browserCls, Object presenter) throws Exception { + browserClass = browserCls; + browserContext = presenter; + KnockoutFelixIT.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); + } +} diff -r 561536cc73fd -r ee3614350fc8 ko-felix-test/src/test/resources/org/netbeans/html/ko/felix/test/test.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ko-felix-test/src/test/resources/org/netbeans/html/ko/felix/test/test.html Thu Jul 31 14:36:00 2014 +0200 @@ -0,0 +1,56 @@ + + + + + Knockout.fx Execution Harness + + + + +

Knockout.fx in Felix Execution Harness

+ + + diff -r 561536cc73fd -r ee3614350fc8 ko-osgi-test/pom.xml --- a/ko-osgi-test/pom.xml Fri Jul 25 14:15:20 2014 +0200 +++ b/ko-osgi-test/pom.xml Thu Jul 31 14:36:00 2014 +0200 @@ -6,10 +6,10 @@ pom 0.8.3 - KO Tests in an OSGi Container + KO Tests in Equinox OSGi Container ko-osgi-test bundle - Runs the TCK for Knockout in an OSGi Container + Runs the TCK for Knockout in Equinox OSGi Container none diff -r 561536cc73fd -r ee3614350fc8 ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java --- a/ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java Fri Jul 25 14:15:20 2014 +0200 +++ b/ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java Thu Jul 31 14:36:00 2014 +0200 @@ -108,7 +108,7 @@ public void run() { try { final ClassLoader osgiClassLoader = BrowserBuilder.class.getClassLoader(); - Thread.currentThread().setContextClassLoader(osgiClassLoader); + bb.classloader(osgiClassLoader); bb.showAndWait(); } catch (Throwable t) { t.printStackTrace(); diff -r 561536cc73fd -r ee3614350fc8 ko-osgi-test/src/test/resources/org/netbeans/html/ko/osgi/test/test.html --- a/ko-osgi-test/src/test/resources/org/netbeans/html/ko/osgi/test/test.html Fri Jul 25 14:15:20 2014 +0200 +++ b/ko-osgi-test/src/test/resources/org/netbeans/html/ko/osgi/test/test.html Thu Jul 31 14:36:00 2014 +0200 @@ -50,7 +50,7 @@ -

Knockout.fx Execution Harness

+

Knockout.fx in Equinox Execution Harness

diff -r 561536cc73fd -r ee3614350fc8 pom.xml --- a/pom.xml Fri Jul 25 14:15:20 2014 +0200 +++ b/pom.xml Thu Jul 31 14:36:00 2014 +0200 @@ -30,6 +30,7 @@ geo ko-ws-tyrus html4j-maven-plugin + ko-felix-test ko-osgi-test equinox-agentclass-hook boot-script @@ -313,6 +314,16 @@ org-netbeans-lib-nbjavac ${netbeans.version} + + org.apache.felix + org.apache.felix.framework + 4.2.1 + + + javax.servlet + javax.servlet-api + 3.1.0 + org.netbeans.modules org-netbeans-modules-web-browser-api diff -r 561536cc73fd -r ee3614350fc8 src/main/javadoc/overview.html --- a/src/main/javadoc/overview.html Fri Jul 25 14:15:20 2014 +0200 +++ b/src/main/javadoc/overview.html Thu Jul 31 14:36:00 2014 +0200 @@ -75,7 +75,13 @@ yet the application code can be written in Java.

-

What's New in Version 0.8.3?

+

What's New in Version 0.9?

+ +

+ System can run in {@link net.java.html.boot.BrowserBuilder#classloader(java.lang.ClassLoader) Felix OSGi container} (originally only Equinox). +

+ +

What's New in 0.8.x Versions?

Setters or array properties on classes generated by {@link net.java.html.json.Model} @@ -88,8 +94,6 @@ {@link net.java.html.json.Model knockout bindings}.

-

What's New in Version 0.8.2?

-

Few bugfixes for better portability. New API for {@link net.java.html.boot.script.Scripts headless execution} @@ -102,8 +106,6 @@ {@link net.java.html.js Java/JavaScript interactions}.

-

What's New in Version 0.8.1?

-

{@link net.java.html.boot.fx.FXBrowsers} has been extended with new helper methods to make it easier to use HTML+Java @@ -121,8 +123,6 @@ {@link java.lang.Class#getProtectionDomain}.

-

What's New in Version 0.8?

-

The first argument of method annotated by {@link net.java.html.json.OnReceive} annotation has to @@ -408,6 +408,7 @@ online: