Merging the work on classloader method to main trunk
authorJaroslav Tulach <jtulach@netbeans.org>
Thu, 31 Jul 2014 14:37:06 +0200
changeset 7723285b4511afc
parent 770 a55e8e379b2a
parent 771 ee3614350fc8
child 773 d11158498374
child 789 64ddd3d0e8d9
Merging the work on classloader method to main trunk
ko-felix-test/pom.xml
ko-osgi-test/pom.xml
pom.xml
     1.1 --- a/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java	Thu Jul 31 07:25:13 2014 +0200
     1.2 +++ b/boot-fx/src/main/java/net/java/html/boot/fx/FXBrowsers.java	Thu Jul 31 14:37:06 2014 +0200
     1.3 @@ -121,9 +121,34 @@
     1.4      public static void load(
     1.5          WebView webView, final URL url, Runnable onPageLoad
     1.6      ) {
     1.7 +        load(webView, url, onPageLoad, null);
     1.8 +    }
     1.9 +    
    1.10 +    /** Enables the Java/JavaScript bridge (that supports {@link JavaScriptBody} and co.)
    1.11 +     * in the provided <code>webView</code>. This method returns 
    1.12 +     * immediately. Once the support is active, it calls back specified
    1.13 +     * method in <code>onPageLoad</code>'s run method. 
    1.14 +     * This is more convenient way to initialize the webview, 
    1.15 +     * but it requires one to make sure
    1.16 +     * all {@link JavaScriptBody} methods has been post-processed during
    1.17 +     * compilation and there will be no need to instantiate new classloader.
    1.18 +     * <p>
    1.19 +     * This method sets {@link WebView#getUserData()} and {@link #runInBrowser(javafx.scene.web.WebView, java.lang.Runnable)}
    1.20 +     * relies on the value. Please don't alter it.
    1.21 +     * 
    1.22 +     * @param webView the instance of Web View to tweak
    1.23 +     * @param url the URL of the HTML page to load into the view
    1.24 +     * @param onPageLoad callback to call when the page is ready
    1.25 +     * @param loader the loader to use when constructing initial {@link BrwsrCtx} or <code>null</code>
    1.26 +     * @since 0.9
    1.27 +     */
    1.28 +    public static void load(
    1.29 +        WebView webView, final URL url, Runnable onPageLoad, ClassLoader loader
    1.30 +    ) {
    1.31          BrowserBuilder.newBrowser(new Load(webView)).
    1.32                  loadPage(url.toExternalForm()).
    1.33                  loadFinished(onPageLoad).
    1.34 +                classloader(loader).
    1.35                  showAndWait();
    1.36      }
    1.37      
     2.1 --- a/boot/src/main/java/net/java/html/boot/BrowserBuilder.java	Thu Jul 31 07:25:13 2014 +0200
     2.2 +++ b/boot/src/main/java/net/java/html/boot/BrowserBuilder.java	Thu Jul 31 14:37:06 2014 +0200
     2.3 @@ -108,6 +108,7 @@
     2.4      private String methodName;
     2.5      private String[] methodArgs;
     2.6      private final Object[] context;
     2.7 +    private ClassLoader loader;
     2.8      
     2.9      private BrowserBuilder(Object[] context) {
    2.10          this.context = context;
    2.11 @@ -186,6 +187,22 @@
    2.12          return this;
    2.13      }
    2.14  
    2.15 +    /** Loader to use when searching for classes to initialize. 
    2.16 +     * If specified, this loader is going to be used to load {@link Fn.Presenter}
    2.17 +     * and {@link Contexts#fillInByProviders(java.lang.Class, org.apidesign.html.context.spi.Contexts.Builder) fill} {@link BrwsrCtx} in.
    2.18 +     * Specifying special classloader may be useful in modular systems, 
    2.19 +     * like OSGi, where one needs to load classes from many otherwise independent
    2.20 +     * modules.
    2.21 +     * 
    2.22 +     * @param l the loader to use (or <code>null</code>)
    2.23 +     * @return this builder
    2.24 +     * @since 0.9
    2.25 +     */
    2.26 +    public BrowserBuilder classloader(ClassLoader l) {
    2.27 +        this.loader = l;
    2.28 +        return this;
    2.29 +    }
    2.30 +
    2.31      /** Shows the browser, loads specified page in and executes the 
    2.32       * {@link #invoke(java.lang.String, java.lang.String[]) initialization method}.
    2.33       * The method returns when the browser is closed.
    2.34 @@ -277,6 +294,11 @@
    2.35              }
    2.36          }
    2.37  
    2.38 +        if (dfnr == null && loader != null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class, loader)) {
    2.39 +            dfnr = o;
    2.40 +            break;
    2.41 +        }
    2.42 +        
    2.43          if (dfnr == null) for (Fn.Presenter o : ServiceLoader.load(Fn.Presenter.class)) {
    2.44              dfnr = o;
    2.45              break;
    2.46 @@ -286,12 +308,17 @@
    2.47              throw new IllegalStateException("Can't find any Fn.Presenter");
    2.48          }
    2.49          
    2.50 -        final ClassLoader loader;
    2.51 -        if (FnUtils.isJavaScriptCapable(myCls.getClassLoader())) {
    2.52 -            loader = myCls.getClassLoader();
    2.53 +        final ClassLoader activeLoader;
    2.54 +        if (loader != null) {
    2.55 +            if (!FnUtils.isJavaScriptCapable(loader)) {
    2.56 +                throw new IllegalStateException("Loader cannot resolve @JavaScriptBody: " + loader);
    2.57 +            }
    2.58 +            activeLoader = loader;
    2.59 +        } else if (FnUtils.isJavaScriptCapable(myCls.getClassLoader())) {
    2.60 +            activeLoader = myCls.getClassLoader();
    2.61          } else {
    2.62              FImpl impl = new FImpl(myCls.getClassLoader());
    2.63 -            loader = FnUtils.newLoader(impl, dfnr, myCls.getClassLoader().getParent());
    2.64 +            activeLoader = FnUtils.newLoader(impl, dfnr, myCls.getClassLoader().getParent());
    2.65          }
    2.66          
    2.67          final Fn.Presenter dP = dfnr;
    2.68 @@ -303,8 +330,8 @@
    2.69                      final Fn.Presenter aP = Fn.activePresenter();
    2.70                      final Fn.Presenter currentP = aP != null ? aP : dP;
    2.71                      
    2.72 -                    Thread.currentThread().setContextClassLoader(loader);
    2.73 -                    final Class<?> newClazz = Class.forName(myCls.getName(), true, loader);
    2.74 +                    Thread.currentThread().setContextClassLoader(activeLoader);
    2.75 +                    final Class<?> newClazz = Class.forName(myCls.getName(), true, activeLoader);
    2.76                      if (browserClass != null) {
    2.77                          browserClass[0] = newClazz;
    2.78                      }
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/ko-felix-test/pom.xml	Thu Jul 31 14:37:06 2014 +0200
     3.3 @@ -0,0 +1,123 @@
     3.4 +<?xml version="1.0" encoding="UTF-8"?>
     3.5 +<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">
     3.6 +    <modelVersion>4.0.0</modelVersion>
     3.7 +    <parent>
     3.8 +        <groupId>org.netbeans.html</groupId>
     3.9 +        <artifactId>pom</artifactId>
    3.10 +        <version>1.0-SNAPSHOT</version>
    3.11 +    </parent>
    3.12 +    <name>KO Tests in Felix OSGi Container</name>
    3.13 +    <artifactId>ko-felix-test</artifactId>
    3.14 +    <packaging>bundle</packaging>
    3.15 +    <description>Runs the TCK for Knockout in Felix OSGi Container</description>
    3.16 +    <properties>
    3.17 +        <netbeans.compile.on.save>none</netbeans.compile.on.save>
    3.18 +    </properties>
    3.19 +    <build>
    3.20 +        <plugins>
    3.21 +            <plugin>
    3.22 +                <groupId>org.apache.felix</groupId>
    3.23 +                <artifactId>maven-bundle-plugin</artifactId>
    3.24 +            </plugin>
    3.25 +            <plugin>
    3.26 +                <groupId>org.netbeans.html</groupId>
    3.27 +                <artifactId>html4j-maven-plugin</artifactId>
    3.28 +            </plugin>
    3.29 +            <plugin>
    3.30 +                <artifactId>maven-failsafe-plugin</artifactId>
    3.31 +                <version>2.16</version>
    3.32 +                <configuration>
    3.33 +                    <additionalClasspathElements>
    3.34 +                        <additionalClasspathElement>${project.build.directory}/${project.build.finalName}.jar</additionalClasspathElement>
    3.35 +                    </additionalClasspathElements>
    3.36 +                </configuration>
    3.37 +                <executions>
    3.38 +                    <execution>
    3.39 +                        <goals>
    3.40 +                            <goal>integration-test</goal>
    3.41 +                            <goal>verify</goal>
    3.42 +                        </goals>
    3.43 +                    </execution>
    3.44 +                </executions>
    3.45 +            </plugin>
    3.46 +        </plugins>
    3.47 +    </build>
    3.48 +    <dependencies>
    3.49 +        <dependency>
    3.50 +            <groupId>com.oracle</groupId>
    3.51 +            <artifactId>javafx</artifactId>
    3.52 +            <version>2.2</version>
    3.53 +            <scope>system</scope>
    3.54 +            <systemPath>${jfxrt.jar}</systemPath>
    3.55 +        </dependency>
    3.56 +        <dependency>
    3.57 +            <groupId>de.twentyeleven.skysail</groupId>
    3.58 +            <artifactId>org.json-osgi</artifactId>
    3.59 +        </dependency>
    3.60 +        <dependency>
    3.61 +            <groupId>org.netbeans.html</groupId>
    3.62 +            <artifactId>net.java.html.json</artifactId>
    3.63 +            <version>${project.version}</version>
    3.64 +        </dependency>
    3.65 +        <dependency>
    3.66 +            <groupId>org.testng</groupId>
    3.67 +            <artifactId>testng</artifactId>
    3.68 +            <scope>test</scope>
    3.69 +        </dependency>
    3.70 +        <dependency>
    3.71 +            <groupId>${project.groupId}</groupId>
    3.72 +            <artifactId>net.java.html.json.tck</artifactId>
    3.73 +            <version>${project.version}</version>
    3.74 +        </dependency>
    3.75 +        <dependency>
    3.76 +            <groupId>org.netbeans.api</groupId>
    3.77 +            <artifactId>org-openide-util-lookup</artifactId>
    3.78 +            <scope>provided</scope>
    3.79 +        </dependency>
    3.80 +        <dependency>
    3.81 +            <groupId>org.netbeans.html</groupId>
    3.82 +            <artifactId>net.java.html.boot</artifactId>
    3.83 +            <version>${project.version}</version>
    3.84 +            <type>jar</type>
    3.85 +        </dependency>
    3.86 +        <dependency>
    3.87 +            <groupId>${project.groupId}</groupId>
    3.88 +            <artifactId>ko4j</artifactId>
    3.89 +            <version>${project.version}</version>
    3.90 +        </dependency>
    3.91 +        <dependency>
    3.92 +            <groupId>${project.groupId}</groupId>
    3.93 +            <artifactId>net.java.html.boot.fx</artifactId>
    3.94 +            <version>${project.version}</version>
    3.95 +            <scope>test</scope>
    3.96 +        </dependency>
    3.97 +        <dependency>
    3.98 +            <groupId>org.glassfish.grizzly</groupId>
    3.99 +            <artifactId>grizzly-http-server</artifactId>
   3.100 +            <version>${grizzly.version}</version>
   3.101 +            <scope>test</scope>
   3.102 +        </dependency>
   3.103 +        <dependency>
   3.104 +            <groupId>org.glassfish.grizzly</groupId>
   3.105 +            <artifactId>grizzly-websockets-server</artifactId>
   3.106 +            <version>${grizzly.version}</version>
   3.107 +            <scope>test</scope>
   3.108 +            <type>jar</type>
   3.109 +        </dependency>
   3.110 +        <dependency>
   3.111 +            <groupId>org.glassfish.grizzly</groupId>
   3.112 +            <artifactId>grizzly-http-servlet</artifactId>
   3.113 +            <version>${grizzly.version}</version>
   3.114 +            <scope>test</scope>
   3.115 +        </dependency>    
   3.116 +        <dependency>
   3.117 +            <groupId>javax.servlet</groupId>
   3.118 +            <artifactId>javax.servlet-api</artifactId>
   3.119 +            <scope>test</scope>
   3.120 +        </dependency>
   3.121 +        <dependency>
   3.122 +            <groupId>org.apache.felix</groupId>
   3.123 +            <artifactId>org.apache.felix.framework</artifactId>
   3.124 +        </dependency>
   3.125 +    </dependencies>
   3.126 +</project>
   3.127 \ No newline at end of file
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/ko-felix-test/src/main/java/org/netbeans/html/ko/felix/test/KnockoutFelixTCKImpl.java	Thu Jul 31 14:37:06 2014 +0200
     4.3 @@ -0,0 +1,279 @@
     4.4 +/**
     4.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     4.6 + *
     4.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     4.8 + *
     4.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    4.10 + * Other names may be trademarks of their respective owners.
    4.11 + *
    4.12 + * The contents of this file are subject to the terms of either the GNU
    4.13 + * General Public License Version 2 only ("GPL") or the Common
    4.14 + * Development and Distribution License("CDDL") (collectively, the
    4.15 + * "License"). You may not use this file except in compliance with the
    4.16 + * License. You can obtain a copy of the License at
    4.17 + * http://www.netbeans.org/cddl-gplv2.html
    4.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    4.19 + * specific language governing permissions and limitations under the
    4.20 + * License.  When distributing the software, include this License Header
    4.21 + * Notice in each file and include the License file at
    4.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    4.23 + * particular file as subject to the "Classpath" exception as provided
    4.24 + * by Oracle in the GPL Version 2 section of the License file that
    4.25 + * accompanied this code. If applicable, add the following below the
    4.26 + * License Header, with the fields enclosed by brackets [] replaced by
    4.27 + * your own identifying information:
    4.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    4.29 + *
    4.30 + * Contributor(s):
    4.31 + *
    4.32 + * The Original Software is NetBeans. The Initial Developer of the Original
    4.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    4.34 + *
    4.35 + * If you wish your version of this file to be governed by only the CDDL
    4.36 + * or only the GPL Version 2, indicate your decision by adding
    4.37 + * "[Contributor] elects to include this software in this distribution
    4.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    4.39 + * single choice of license, a recipient has the option to distribute
    4.40 + * your version of this file under either the CDDL, the GPL Version 2 or
    4.41 + * to extend the choice of license to its licensees as provided above.
    4.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    4.43 + * Version 2 license, then the option applies only if the new code is
    4.44 + * made subject to such option by the copyright holder.
    4.45 + */
    4.46 +package org.netbeans.html.ko.felix.test;
    4.47 +
    4.48 +import java.io.BufferedReader;
    4.49 +import java.io.IOException;
    4.50 +import java.io.InputStreamReader;
    4.51 +import java.lang.reflect.Constructor;
    4.52 +import java.lang.reflect.Method;
    4.53 +import java.net.URI;
    4.54 +import java.net.URISyntaxException;
    4.55 +import java.net.URL;
    4.56 +import java.net.URLConnection;
    4.57 +import java.util.ArrayList;
    4.58 +import java.util.Collections;
    4.59 +import java.util.Enumeration;
    4.60 +import java.util.List;
    4.61 +import java.util.Map;
    4.62 +import java.util.concurrent.Callable;
    4.63 +import java.util.concurrent.Executor;
    4.64 +import java.util.concurrent.Executors;
    4.65 +import net.java.html.BrwsrCtx;
    4.66 +import net.java.html.boot.BrowserBuilder;
    4.67 +import net.java.html.js.JavaScriptBody;
    4.68 +import org.apidesign.html.boot.spi.Fn;
    4.69 +import org.apidesign.html.context.spi.Contexts;
    4.70 +import org.apidesign.html.json.spi.Technology;
    4.71 +import org.apidesign.html.json.spi.Transfer;
    4.72 +import org.apidesign.html.json.tck.KnockoutTCK;
    4.73 +import org.json.JSONException;
    4.74 +import org.json.JSONObject;
    4.75 +import org.openide.util.lookup.ServiceProvider;
    4.76 +import org.osgi.framework.Bundle;
    4.77 +import org.osgi.framework.BundleContext;
    4.78 +import org.osgi.framework.FrameworkUtil;
    4.79 +
    4.80 +/**
    4.81 + *
    4.82 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    4.83 + */
    4.84 +@ServiceProvider(service = KnockoutTCK.class)
    4.85 +public class KnockoutFelixTCKImpl extends KnockoutTCK implements Callable<Class[]> {
    4.86 +    
    4.87 +    private static Fn.Presenter browserContext;
    4.88 +
    4.89 +    public static Class loadOSGiClass(String name, BundleContext ctx) throws Exception {
    4.90 +        for (Bundle b : ctx.getBundles()) {
    4.91 +            try {
    4.92 +                Class<?> osgiClass = b.loadClass(name);
    4.93 +                if (osgiClass != null && osgiClass.getClassLoader() != ClassLoader.getSystemClassLoader()) {
    4.94 +                    return osgiClass;
    4.95 +                }
    4.96 +            } catch (ClassNotFoundException cnfe) {
    4.97 +                // go on
    4.98 +            }
    4.99 +        }
   4.100 +        throw new IllegalStateException("Cannot load " + name + " from the OSGi container!");
   4.101 +    }
   4.102 +
   4.103 +    @Override
   4.104 +    public Class[] call() throws Exception {
   4.105 +        return testClasses();
   4.106 +    }
   4.107 +    
   4.108 +    public static void start(URI server) throws Exception {
   4.109 +        final BrowserBuilder bb = BrowserBuilder.newBrowser().loadClass(KnockoutFelixTCKImpl.class).
   4.110 +            loadPage(server.toString()).
   4.111 +            invoke("initialized");
   4.112 +
   4.113 +        Executors.newSingleThreadExecutor().submit(new Runnable() {
   4.114 +            @Override
   4.115 +            public void run() {
   4.116 +                try {
   4.117 +                    Bundle[] arr = FrameworkUtil.getBundle(BrowserBuilder.class).getBundleContext().getBundles();
   4.118 +                    final ClassLoader osgiClassLoader = new AllBundlesLoader(arr);
   4.119 +                    bb.classloader(osgiClassLoader);
   4.120 +                    bb.showAndWait();
   4.121 +                } catch (Throwable t) {
   4.122 +                    t.printStackTrace();
   4.123 +                }
   4.124 +            }
   4.125 +        });
   4.126 +    }
   4.127 +
   4.128 +    public static void initialized() throws Exception {
   4.129 +        Bundle bundle = FrameworkUtil.getBundle(KnockoutFelixTCKImpl.class);
   4.130 +        if (bundle == null) {
   4.131 +            throw new IllegalStateException(
   4.132 +                "Should be loaded from a bundle. But was: " + KnockoutFelixTCKImpl.class.getClassLoader()
   4.133 +            );
   4.134 +        }
   4.135 +        Class<?> classpathClass = ClassLoader.getSystemClassLoader().loadClass(
   4.136 +            "org.netbeans.html.ko.felix.test.KnockoutFelixIT"
   4.137 +        );
   4.138 +        Method m = classpathClass.getMethod("initialized", Class.class, Object.class);
   4.139 +        browserContext = Fn.activePresenter();
   4.140 +        m.invoke(null, KnockoutFelixTCKImpl.class, browserContext);
   4.141 +    }
   4.142 +    
   4.143 +    @Override
   4.144 +    public BrwsrCtx createContext() {
   4.145 +        try {
   4.146 +            Class<?> fxCls = loadOSGiClass(
   4.147 +                "org.netbeans.html.ko4j.FXContext",
   4.148 +                FrameworkUtil.getBundle(KnockoutFelixTCKImpl.class).getBundleContext()
   4.149 +            );
   4.150 +            final Constructor<?> cnstr = fxCls.getConstructor(Fn.Presenter.class);
   4.151 +            cnstr.setAccessible(true);
   4.152 +            Object fx = cnstr.newInstance(browserContext);
   4.153 +            Contexts.Builder cb = Contexts.newBuilder().
   4.154 +                register(Technology.class, (Technology)fx, 10).
   4.155 +                register(Transfer.class, (Transfer)fx, 10).
   4.156 +                register(Executor.class, (Executor)browserContext, 10);
   4.157 +//        if (fx.areWebSocketsSupported()) {
   4.158 +//            cb.register(WSTransfer.class, fx, 10);
   4.159 +//        }
   4.160 +            return cb.build();
   4.161 +        } catch (Exception ex) {
   4.162 +            throw new IllegalStateException(ex);
   4.163 +        }
   4.164 +    }
   4.165 +
   4.166 +    @Override
   4.167 +    public Object createJSON(Map<String, Object> values) {
   4.168 +        JSONObject json = new JSONObject();
   4.169 +        for (Map.Entry<String, Object> entry : values.entrySet()) {
   4.170 +            try {
   4.171 +                json.put(entry.getKey(), entry.getValue());
   4.172 +            } catch (JSONException ex) {
   4.173 +                throw new IllegalStateException(ex);
   4.174 +            }
   4.175 +        }
   4.176 +        return json;
   4.177 +    }
   4.178 +
   4.179 +    @Override
   4.180 +    @JavaScriptBody(args = { "s", "args" }, body = ""
   4.181 +        + "var f = new Function(s); "
   4.182 +        + "return f.apply(null, args);"
   4.183 +    )
   4.184 +    public native Object executeScript(String script, Object[] arguments);
   4.185 +
   4.186 +    @JavaScriptBody(args = {  }, body = 
   4.187 +          "var h;"
   4.188 +        + "if (!!window && !!window.location && !!window.location.href)\n"
   4.189 +        + "  h = window.location.href;\n"
   4.190 +        + "else "
   4.191 +        + "  h = null;"
   4.192 +        + "return h;\n"
   4.193 +    )
   4.194 +    private static native String findBaseURL();
   4.195 +    
   4.196 +    @Override
   4.197 +    public URI prepareURL(String content, String mimeType, String[] parameters) {
   4.198 +        try {
   4.199 +            final URL baseURL = new URL(findBaseURL());
   4.200 +            StringBuilder sb = new StringBuilder();
   4.201 +            sb.append("/dynamic?mimeType=").append(mimeType);
   4.202 +            for (int i = 0; i < parameters.length; i++) {
   4.203 +                sb.append("&param" + i).append("=").append(parameters[i]);
   4.204 +            }
   4.205 +            String mangle = content.replace("\n", "%0a")
   4.206 +                .replace("\"", "\\\"").replace(" ", "%20");
   4.207 +            sb.append("&content=").append(mangle);
   4.208 +
   4.209 +            URL query = new URL(baseURL, sb.toString());
   4.210 +            URLConnection c = query.openConnection();
   4.211 +            BufferedReader br = new BufferedReader(new InputStreamReader(c.getInputStream()));
   4.212 +            URI connectTo = new URI(br.readLine());
   4.213 +            return connectTo;
   4.214 +        } catch (IOException ex) {
   4.215 +            throw new IllegalStateException(ex);
   4.216 +        } catch (URISyntaxException ex) {
   4.217 +            throw new IllegalStateException(ex);
   4.218 +        }
   4.219 +    }
   4.220 +
   4.221 +    @Override
   4.222 +    public boolean canFailWebSocketTest() {
   4.223 +        return true;
   4.224 +    }
   4.225 +
   4.226 +    private static final class AllBundlesLoader extends ClassLoader {
   4.227 +        private final Bundle[] arr;
   4.228 +
   4.229 +        public AllBundlesLoader(Bundle[] arr) {
   4.230 +            super(ClassLoader.getSystemClassLoader().getParent());
   4.231 +            this.arr = arr;
   4.232 +        }
   4.233 +
   4.234 +        @Override
   4.235 +        public Class<?> loadClass(String name) throws ClassNotFoundException {
   4.236 +            return loadClass(name, false);
   4.237 +        }
   4.238 +
   4.239 +        @Override
   4.240 +        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
   4.241 +            ClassNotFoundException err = null;
   4.242 +            for (Bundle b : arr) {
   4.243 +                try {
   4.244 +                    Class<?> cls = b.loadClass(name);
   4.245 +                    if (FrameworkUtil.getBundle(cls) == b) {
   4.246 +                        return cls;
   4.247 +                    }
   4.248 +                } catch (ClassNotFoundException ex) {
   4.249 +                    err = ex;
   4.250 +                }
   4.251 +            }
   4.252 +            throw err;
   4.253 +        }
   4.254 +
   4.255 +        @Override
   4.256 +        protected URL findResource(String name) {
   4.257 +            for (Bundle b : arr) {
   4.258 +                URL r = b.getResource(name);
   4.259 +                if (r != null) {
   4.260 +                    return r;
   4.261 +                }
   4.262 +            }
   4.263 +            return null;
   4.264 +        }
   4.265 +
   4.266 +        @Override
   4.267 +        protected Enumeration<URL> findResources(String name) throws IOException {
   4.268 +            List<URL> ret = new ArrayList<URL>();
   4.269 +            for (Bundle b : arr) {
   4.270 +                Enumeration<URL> en = b.getResources(name);
   4.271 +                if (en != null) while (en.hasMoreElements()) {
   4.272 +                    URL u = en.nextElement();
   4.273 +                    ret.add(u);
   4.274 +                }
   4.275 +            }
   4.276 +            return Collections.enumeration(ret);
   4.277 +        }
   4.278 +        
   4.279 +        
   4.280 +        
   4.281 +    }
   4.282 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/DynamicHTTP.java	Thu Jul 31 14:37:06 2014 +0200
     5.3 @@ -0,0 +1,259 @@
     5.4 +/**
     5.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     5.6 + *
     5.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     5.8 + *
     5.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    5.10 + * Other names may be trademarks of their respective owners.
    5.11 + *
    5.12 + * The contents of this file are subject to the terms of either the GNU
    5.13 + * General Public License Version 2 only ("GPL") or the Common
    5.14 + * Development and Distribution License("CDDL") (collectively, the
    5.15 + * "License"). You may not use this file except in compliance with the
    5.16 + * License. You can obtain a copy of the License at
    5.17 + * http://www.netbeans.org/cddl-gplv2.html
    5.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    5.19 + * specific language governing permissions and limitations under the
    5.20 + * License.  When distributing the software, include this License Header
    5.21 + * Notice in each file and include the License file at
    5.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    5.23 + * particular file as subject to the "Classpath" exception as provided
    5.24 + * by Oracle in the GPL Version 2 section of the License file that
    5.25 + * accompanied this code. If applicable, add the following below the
    5.26 + * License Header, with the fields enclosed by brackets [] replaced by
    5.27 + * your own identifying information:
    5.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    5.29 + *
    5.30 + * Contributor(s):
    5.31 + *
    5.32 + * The Original Software is NetBeans. The Initial Developer of the Original
    5.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    5.34 + *
    5.35 + * If you wish your version of this file to be governed by only the CDDL
    5.36 + * or only the GPL Version 2, indicate your decision by adding
    5.37 + * "[Contributor] elects to include this software in this distribution
    5.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    5.39 + * single choice of license, a recipient has the option to distribute
    5.40 + * your version of this file under either the CDDL, the GPL Version 2 or
    5.41 + * to extend the choice of license to its licensees as provided above.
    5.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    5.43 + * Version 2 license, then the option applies only if the new code is
    5.44 + * made subject to such option by the copyright holder.
    5.45 + */
    5.46 +package org.netbeans.html.ko.felix.test;
    5.47 +
    5.48 +import java.io.ByteArrayInputStream;
    5.49 +import java.io.ByteArrayOutputStream;
    5.50 +import java.io.IOException;
    5.51 +import java.io.InputStream;
    5.52 +import java.io.OutputStream;
    5.53 +import java.io.Reader;
    5.54 +import java.net.URI;
    5.55 +import java.net.URISyntaxException;
    5.56 +import java.util.ArrayList;
    5.57 +import java.util.List;
    5.58 +import java.util.logging.Level;
    5.59 +import java.util.logging.Logger;
    5.60 +import org.glassfish.grizzly.PortRange;
    5.61 +import org.glassfish.grizzly.http.server.HttpHandler;
    5.62 +import org.glassfish.grizzly.http.server.HttpServer;
    5.63 +import org.glassfish.grizzly.http.server.NetworkListener;
    5.64 +import org.glassfish.grizzly.http.server.Request;
    5.65 +import org.glassfish.grizzly.http.server.Response;
    5.66 +import org.glassfish.grizzly.http.server.ServerConfiguration;
    5.67 +import org.glassfish.grizzly.websockets.WebSocket;
    5.68 +import org.glassfish.grizzly.websockets.WebSocketAddOn;
    5.69 +import org.glassfish.grizzly.websockets.WebSocketApplication;
    5.70 +import org.glassfish.grizzly.websockets.WebSocketEngine;
    5.71 +
    5.72 +/**
    5.73 + *
    5.74 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    5.75 + */
    5.76 +final class DynamicHTTP extends HttpHandler {
    5.77 +    private static final Logger LOG = Logger.getLogger(DynamicHTTP.class.getName());
    5.78 +    private static int resourcesCount;
    5.79 +    private static List<Resource> resources;
    5.80 +    private static ServerConfiguration conf;
    5.81 +    private static HttpServer server;
    5.82 +    
    5.83 +    private DynamicHTTP() {
    5.84 +    }
    5.85 +    
    5.86 +    static URI initServer() throws Exception {
    5.87 +        server = HttpServer.createSimpleServer(null, new PortRange(8080, 65535));
    5.88 +        final WebSocketAddOn addon = new WebSocketAddOn();
    5.89 +        for (NetworkListener listener : server.getListeners()) {
    5.90 +            listener.registerAddOn(addon);
    5.91 +        }        
    5.92 +        resources = new ArrayList<Resource>();
    5.93 +
    5.94 +        conf = server.getServerConfiguration();
    5.95 +        final DynamicHTTP dh = new DynamicHTTP();
    5.96 +
    5.97 +        conf.addHttpHandler(dh, "/");
    5.98 +        
    5.99 +        server.start();
   5.100 +
   5.101 +        return pageURL("http", server, "/test.html");
   5.102 +    }
   5.103 +    
   5.104 +    @Override
   5.105 +    public void service(Request request, Response response) throws Exception {
   5.106 +        if ("/test.html".equals(request.getRequestURI())) {
   5.107 +            response.setContentType("text/html");
   5.108 +            final InputStream is = DynamicHTTP.class.getResourceAsStream("test.html");
   5.109 +            copyStream(is, response.getOutputStream(), null);
   5.110 +            return;
   5.111 +        }
   5.112 +        if ("/dynamic".equals(request.getRequestURI())) {
   5.113 +            String mimeType = request.getParameter("mimeType");
   5.114 +            List<String> params = new ArrayList<String>();
   5.115 +            boolean webSocket = false;
   5.116 +            for (int i = 0;; i++) {
   5.117 +                String p = request.getParameter("param" + i);
   5.118 +                if (p == null) {
   5.119 +                    break;
   5.120 +                }
   5.121 +                if ("protocol:ws".equals(p)) {
   5.122 +                    webSocket = true;
   5.123 +                    continue;
   5.124 +                }
   5.125 +                params.add(p);
   5.126 +            }
   5.127 +            final String cnt = request.getParameter("content");
   5.128 +            String mangle = cnt.replace("%20", " ").replace("%0A", "\n");
   5.129 +            ByteArrayInputStream is = new ByteArrayInputStream(mangle.getBytes("UTF-8"));
   5.130 +            URI url;
   5.131 +            final Resource res = new Resource(is, mimeType, "/dynamic/res" + ++resourcesCount, params.toArray(new String[params.size()]));
   5.132 +            if (webSocket) {
   5.133 +                url = registerWebSocket(res);
   5.134 +            } else {
   5.135 +                url = registerResource(res);
   5.136 +            }
   5.137 +            response.getWriter().write(url.toString());
   5.138 +            response.getWriter().write("\n");
   5.139 +            return;
   5.140 +        }
   5.141 +
   5.142 +        for (Resource r : resources) {
   5.143 +            if (r.httpPath.equals(request.getRequestURI())) {
   5.144 +                response.setContentType(r.httpType);
   5.145 +                r.httpContent.reset();
   5.146 +                String[] params = null;
   5.147 +                if (r.parameters.length != 0) {
   5.148 +                    params = new String[r.parameters.length];
   5.149 +                    for (int i = 0; i < r.parameters.length; i++) {
   5.150 +                        params[i] = request.getParameter(r.parameters[i]);
   5.151 +                        if (params[i] == null) {
   5.152 +                            if ("http.method".equals(r.parameters[i])) {
   5.153 +                                params[i] = request.getMethod().toString();
   5.154 +                            } else if ("http.requestBody".equals(r.parameters[i])) {
   5.155 +                                Reader rdr = request.getReader();
   5.156 +                                StringBuilder sb = new StringBuilder();
   5.157 +                                for (;;) {
   5.158 +                                    int ch = rdr.read();
   5.159 +                                    if (ch == -1) {
   5.160 +                                        break;
   5.161 +                                    }
   5.162 +                                    sb.append((char) ch);
   5.163 +                                }
   5.164 +                                params[i] = sb.toString();
   5.165 +                            }
   5.166 +                        }
   5.167 +                        if (params[i] == null) {
   5.168 +                            params[i] = "null";
   5.169 +                        }
   5.170 +                    }
   5.171 +                }
   5.172 +
   5.173 +                copyStream(r.httpContent, response.getOutputStream(), null, params);
   5.174 +            }
   5.175 +        }
   5.176 +    }
   5.177 +    
   5.178 +    private URI registerWebSocket(Resource r) {
   5.179 +        WebSocketEngine.getEngine().register("", r.httpPath, new WS(r));
   5.180 +        return pageURL("ws", server, r.httpPath);
   5.181 +    }
   5.182 +
   5.183 +    private URI registerResource(Resource r) {
   5.184 +        if (!resources.contains(r)) {
   5.185 +            resources.add(r);
   5.186 +            conf.addHttpHandler(this, r.httpPath);
   5.187 +        }
   5.188 +        return pageURL("http", server, r.httpPath);
   5.189 +    }
   5.190 +    
   5.191 +    private static URI pageURL(String proto, HttpServer server, final String page) {
   5.192 +        NetworkListener listener = server.getListeners().iterator().next();
   5.193 +        int port = listener.getPort();
   5.194 +        try {
   5.195 +            return new URI(proto + "://localhost:" + port + page);
   5.196 +        } catch (URISyntaxException ex) {
   5.197 +            throw new IllegalStateException(ex);
   5.198 +        }
   5.199 +    }
   5.200 +    
   5.201 +    static final class Resource {
   5.202 +
   5.203 +        final InputStream httpContent;
   5.204 +        final String httpType;
   5.205 +        final String httpPath;
   5.206 +        final String[] parameters;
   5.207 +
   5.208 +        Resource(InputStream httpContent, String httpType, String httpPath,
   5.209 +            String[] parameters) {
   5.210 +            httpContent.mark(Integer.MAX_VALUE);
   5.211 +            this.httpContent = httpContent;
   5.212 +            this.httpType = httpType;
   5.213 +            this.httpPath = httpPath;
   5.214 +            this.parameters = parameters;
   5.215 +        }
   5.216 +    }
   5.217 +
   5.218 +    static void copyStream(InputStream is, OutputStream os, String baseURL, String... params) throws IOException {
   5.219 +        for (;;) {
   5.220 +            int ch = is.read();
   5.221 +            if (ch == -1) {
   5.222 +                break;
   5.223 +            }
   5.224 +            if (ch == '$' && params.length > 0) {
   5.225 +                int cnt = is.read() - '0';
   5.226 +                if (baseURL != null && cnt == 'U' - '0') {
   5.227 +                    os.write(baseURL.getBytes("UTF-8"));
   5.228 +                } else {
   5.229 +                    if (cnt >= 0 && cnt < params.length) {
   5.230 +                        os.write(params[cnt].getBytes("UTF-8"));
   5.231 +                    } else {
   5.232 +                        os.write('$');
   5.233 +                        os.write(cnt + '0');
   5.234 +                    }
   5.235 +                }
   5.236 +            } else {
   5.237 +                os.write(ch);
   5.238 +            }
   5.239 +        }
   5.240 +    }
   5.241 +    
   5.242 +    private static class WS extends WebSocketApplication {
   5.243 +        private final Resource r;
   5.244 +
   5.245 +        private WS(Resource r) {
   5.246 +            this.r = r;
   5.247 +        }
   5.248 +
   5.249 +        @Override
   5.250 +        public void onMessage(WebSocket socket, String text) {
   5.251 +            try {
   5.252 +                r.httpContent.reset();
   5.253 +                ByteArrayOutputStream out = new ByteArrayOutputStream();
   5.254 +                copyStream(r.httpContent, out, null, text);
   5.255 +                String s = new String(out.toByteArray(), "UTF-8");
   5.256 +                socket.send(s);
   5.257 +            } catch (IOException ex) {
   5.258 +                LOG.log(Level.WARNING, "Error processing message " + text, ex);
   5.259 +            }
   5.260 +        }
   5.261 +    }
   5.262 +}
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/KOFx.java	Thu Jul 31 14:37:06 2014 +0200
     6.3 @@ -0,0 +1,128 @@
     6.4 +/**
     6.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     6.6 + *
     6.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     6.8 + *
     6.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    6.10 + * Other names may be trademarks of their respective owners.
    6.11 + *
    6.12 + * The contents of this file are subject to the terms of either the GNU
    6.13 + * General Public License Version 2 only ("GPL") or the Common
    6.14 + * Development and Distribution License("CDDL") (collectively, the
    6.15 + * "License"). You may not use this file except in compliance with the
    6.16 + * License. You can obtain a copy of the License at
    6.17 + * http://www.netbeans.org/cddl-gplv2.html
    6.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    6.19 + * specific language governing permissions and limitations under the
    6.20 + * License.  When distributing the software, include this License Header
    6.21 + * Notice in each file and include the License file at
    6.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    6.23 + * particular file as subject to the "Classpath" exception as provided
    6.24 + * by Oracle in the GPL Version 2 section of the License file that
    6.25 + * accompanied this code. If applicable, add the following below the
    6.26 + * License Header, with the fields enclosed by brackets [] replaced by
    6.27 + * your own identifying information:
    6.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    6.29 + *
    6.30 + * Contributor(s):
    6.31 + *
    6.32 + * The Original Software is NetBeans. The Initial Developer of the Original
    6.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    6.34 + *
    6.35 + * If you wish your version of this file to be governed by only the CDDL
    6.36 + * or only the GPL Version 2, indicate your decision by adding
    6.37 + * "[Contributor] elects to include this software in this distribution
    6.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    6.39 + * single choice of license, a recipient has the option to distribute
    6.40 + * your version of this file under either the CDDL, the GPL Version 2 or
    6.41 + * to extend the choice of license to its licensees as provided above.
    6.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    6.43 + * Version 2 license, then the option applies only if the new code is
    6.44 + * made subject to such option by the copyright holder.
    6.45 + */
    6.46 +package org.netbeans.html.ko.felix.test;
    6.47 +
    6.48 +import java.io.Closeable;
    6.49 +import java.io.IOException;
    6.50 +import java.lang.reflect.InvocationTargetException;
    6.51 +import java.lang.reflect.Method;
    6.52 +import java.util.logging.Level;
    6.53 +import java.util.logging.Logger;
    6.54 +import javafx.application.Platform;
    6.55 +import org.apidesign.html.boot.spi.Fn;
    6.56 +import org.testng.ITest;
    6.57 +import org.testng.annotations.Test;
    6.58 +
    6.59 +/**
    6.60 + *
    6.61 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    6.62 + */
    6.63 +public final class KOFx implements ITest, Runnable {
    6.64 +    private final Object p;
    6.65 +    private final Method m;
    6.66 +    private Object result;
    6.67 +    private Object inst;
    6.68 +    private int count;
    6.69 +
    6.70 +    KOFx(Object p, Method m) {
    6.71 +        this.p = p;
    6.72 +        this.m = m;
    6.73 +    }
    6.74 +
    6.75 +    @Override
    6.76 +    public String getTestName() {
    6.77 +        return m.getName();
    6.78 +    }
    6.79 +
    6.80 +    @Test
    6.81 +    public synchronized void executeTest() throws Exception {
    6.82 +        if (result == null) {
    6.83 +            Platform.runLater(this);
    6.84 +            wait();
    6.85 +        }
    6.86 +        if (result instanceof Exception) {
    6.87 +            throw (Exception)result;
    6.88 +        }
    6.89 +        if (result instanceof Error) {
    6.90 +            throw (Error)result;
    6.91 +        }
    6.92 +    }
    6.93 +
    6.94 +    @Override
    6.95 +    public synchronized void run() {
    6.96 +        boolean notify = true;
    6.97 +        Closeable a = null;
    6.98 +        try {
    6.99 +            a = KnockoutFelixIT.activateInOSGi(p);
   6.100 +            if (inst == null) {
   6.101 +                inst = m.getDeclaringClass().newInstance();
   6.102 +            }
   6.103 +            result = m.invoke(inst);
   6.104 +            if (result == null) {
   6.105 +                result = this;
   6.106 +            }
   6.107 +        } catch (InvocationTargetException ex) {
   6.108 +            Throwable r = ex.getTargetException();
   6.109 +            if (r instanceof InterruptedException) {
   6.110 +                if (count++ < 10000) {
   6.111 +                    notify = false;
   6.112 +                    Platform.runLater(this);
   6.113 +                    return;
   6.114 +                }
   6.115 +            }
   6.116 +            result = r;
   6.117 +        } catch (Exception ex) {
   6.118 +            result = ex;
   6.119 +        } finally {
   6.120 +            if (notify) {
   6.121 +                notifyAll();
   6.122 +            }
   6.123 +            try {
   6.124 +                if (a != null) a.close();
   6.125 +            } catch (IOException ex) {
   6.126 +                throw new IllegalStateException(ex);
   6.127 +            }
   6.128 +        }
   6.129 +    }
   6.130 +    
   6.131 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/ko-felix-test/src/test/java/org/netbeans/html/ko/felix/test/KnockoutFelixIT.java	Thu Jul 31 14:37:06 2014 +0200
     7.3 @@ -0,0 +1,248 @@
     7.4 +/**
     7.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     7.6 + *
     7.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     7.8 + *
     7.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    7.10 + * Other names may be trademarks of their respective owners.
    7.11 + *
    7.12 + * The contents of this file are subject to the terms of either the GNU
    7.13 + * General Public License Version 2 only ("GPL") or the Common
    7.14 + * Development and Distribution License("CDDL") (collectively, the
    7.15 + * "License"). You may not use this file except in compliance with the
    7.16 + * License. You can obtain a copy of the License at
    7.17 + * http://www.netbeans.org/cddl-gplv2.html
    7.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    7.19 + * specific language governing permissions and limitations under the
    7.20 + * License.  When distributing the software, include this License Header
    7.21 + * Notice in each file and include the License file at
    7.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    7.23 + * particular file as subject to the "Classpath" exception as provided
    7.24 + * by Oracle in the GPL Version 2 section of the License file that
    7.25 + * accompanied this code. If applicable, add the following below the
    7.26 + * License Header, with the fields enclosed by brackets [] replaced by
    7.27 + * your own identifying information:
    7.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    7.29 + *
    7.30 + * Contributor(s):
    7.31 + *
    7.32 + * The Original Software is NetBeans. The Initial Developer of the Original
    7.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    7.34 + *
    7.35 + * If you wish your version of this file to be governed by only the CDDL
    7.36 + * or only the GPL Version 2, indicate your decision by adding
    7.37 + * "[Contributor] elects to include this software in this distribution
    7.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    7.39 + * single choice of license, a recipient has the option to distribute
    7.40 + * your version of this file under either the CDDL, the GPL Version 2 or
    7.41 + * to extend the choice of license to its licensees as provided above.
    7.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    7.43 + * Version 2 license, then the option applies only if the new code is
    7.44 + * made subject to such option by the copyright holder.
    7.45 + */
    7.46 +package org.netbeans.html.ko.felix.test;
    7.47 +
    7.48 +import org.netbeans.html.ko.felix.test.KnockoutFelixTCKImpl;
    7.49 +import java.io.Closeable;
    7.50 +import java.io.File;
    7.51 +import java.io.IOException;
    7.52 +import java.lang.annotation.Annotation;
    7.53 +import java.lang.reflect.Method;
    7.54 +import java.net.URI;
    7.55 +import java.util.ArrayList;
    7.56 +import java.util.HashMap;
    7.57 +import java.util.List;
    7.58 +import java.util.Map;
    7.59 +import java.util.ServiceLoader;
    7.60 +import java.util.concurrent.Callable;
    7.61 +import java.util.jar.JarFile;
    7.62 +import java.util.logging.Level;
    7.63 +import java.util.logging.Logger;
    7.64 +import org.apidesign.html.boot.spi.Fn;
    7.65 +import org.apidesign.html.json.tck.KOTest;
    7.66 +import org.apidesign.html.json.tck.KnockoutTCK;
    7.67 +import org.osgi.framework.Bundle;
    7.68 +import org.osgi.framework.BundleException;
    7.69 +import org.osgi.framework.Constants;
    7.70 +import org.osgi.framework.launch.Framework;
    7.71 +import org.osgi.framework.launch.FrameworkFactory;
    7.72 +import static org.testng.Assert.assertNotNull;
    7.73 +import static org.testng.Assert.fail;
    7.74 +import org.testng.annotations.AfterClass;
    7.75 +import org.testng.annotations.Factory;
    7.76 +
    7.77 +/**
    7.78 + *
    7.79 + * @author Jaroslav Tulach <jtulach@netbeans.org>
    7.80 + */
    7.81 +public class KnockoutFelixIT {
    7.82 +    private static final Logger LOG = Logger.getLogger(KnockoutFelixIT.class.getName());
    7.83 +    private static Framework framework;
    7.84 +    private static File dir;
    7.85 +    static Framework framework() throws Exception {
    7.86 +        if (framework != null) {
    7.87 +            return framework;
    7.88 +        }
    7.89 +        for (FrameworkFactory ff : ServiceLoader.load(FrameworkFactory.class)) {
    7.90 +            
    7.91 +            String basedir = System.getProperty("basedir");
    7.92 +            assertNotNull("basedir preperty provided", basedir);
    7.93 +            File target = new File(basedir, "target");
    7.94 +            dir = new File(target, "osgi");
    7.95 +            dir.mkdirs();
    7.96 +            
    7.97 +            Map<String,String> config = new HashMap<String, String>();
    7.98 +            config.put(Constants.FRAMEWORK_STORAGE, dir.getPath());
    7.99 +            config.put(Constants.FRAMEWORK_STORAGE_CLEAN, "true");
   7.100 +            config.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, "sun.misc,"
   7.101 +                + "javafx.application,"
   7.102 +                + "javafx.beans,"
   7.103 +                + "javafx.beans.property,"
   7.104 +                + "javafx.beans.value,"
   7.105 +                + "javafx.collections,"
   7.106 +                + "javafx.concurrent,"
   7.107 +                + "javafx.event,"
   7.108 +                + "javafx.geometry,"
   7.109 +                + "javafx.scene,"
   7.110 +                + "javafx.scene.control,"
   7.111 +                + "javafx.scene.image,"
   7.112 +                + "javafx.scene.layout,"
   7.113 +                + "javafx.scene.text,"
   7.114 +                + "javafx.scene.web,"
   7.115 +                + "javafx.stage,"
   7.116 +                + "javafx.util,"
   7.117 +                + "netscape.javascript"
   7.118 +            );
   7.119 +            framework = ff.newFramework(config);
   7.120 +            framework.init();
   7.121 +            loadClassPathBundles(framework);
   7.122 +            framework.start();
   7.123 +            for (Bundle b : framework.getBundleContext().getBundles()) {
   7.124 +                try {
   7.125 +                    if (b.getSymbolicName().contains("felix.framework")) {
   7.126 +                        continue;
   7.127 +                    }
   7.128 +                    if (b.getSymbolicName().contains("grizzly.websockets-server")) {
   7.129 +                        continue;
   7.130 +                    }
   7.131 +                    b.start();
   7.132 +                    LOG.log(Level.INFO, "Started {0}", b.getSymbolicName());
   7.133 +                } catch (BundleException ex) {
   7.134 +                    LOG.log(Level.WARNING, "Cannot start bundle " + b.getSymbolicName(), ex);
   7.135 +                }
   7.136 +            }
   7.137 +            return framework;
   7.138 +        }
   7.139 +        fail("No OSGi framework in the path");
   7.140 +        return null;
   7.141 +    }
   7.142 +    
   7.143 +    @AfterClass public static void cleanUp() throws Exception {
   7.144 +        if (framework != null) framework.stop();
   7.145 +        clearUpDir(dir);
   7.146 +    }
   7.147 +    private static void clearUpDir(File dir) {
   7.148 +        if (dir.isDirectory()) {
   7.149 +            for (File f : dir.listFiles()) {
   7.150 +                clearUpDir(f);
   7.151 +            }
   7.152 +        }
   7.153 +        dir.delete();
   7.154 +    }
   7.155 +    
   7.156 +    
   7.157 +
   7.158 +    private static void loadClassPathBundles(Framework f) throws IOException, BundleException {
   7.159 +        for (String jar : System.getProperty("java.class.path").split(File.pathSeparator)) {
   7.160 +            File file = new File(jar);
   7.161 +            if (!file.isFile()) {
   7.162 +                LOG.info("Not loading " + file);
   7.163 +                continue;
   7.164 +            }
   7.165 +            JarFile jf = new JarFile(file);
   7.166 +            final String name = jf.getManifest().getMainAttributes().getValue("Bundle-SymbolicName");
   7.167 +            jf.close();
   7.168 +            if (name != null) {
   7.169 +                if (name.contains("org.eclipse.osgi")) {
   7.170 +                    throw new IllegalStateException("Found " + name + " !");
   7.171 +                }
   7.172 +                if (name.contains("felix.framework")) {
   7.173 +                    continue;
   7.174 +                }
   7.175 +                if (name.contains("testng")) {
   7.176 +                    continue;
   7.177 +                }
   7.178 +                final String path = "reference:" + file.toURI().toString();
   7.179 +                try {
   7.180 +                    Bundle b = f.getBundleContext().installBundle(path);
   7.181 +                } catch (BundleException ex) {
   7.182 +                    LOG.log(Level.WARNING, "Cannot install " + file, ex);
   7.183 +                }
   7.184 +            }
   7.185 +        }
   7.186 +    }
   7.187 +    
   7.188 +    private static Class<?> loadOSGiClass(Class<?> c) throws Exception {
   7.189 +        return KnockoutFelixTCKImpl.loadOSGiClass(c.getName(), KnockoutFelixIT.framework().getBundleContext());
   7.190 +    }
   7.191 +    
   7.192 +    private static Class<?> browserClass;
   7.193 +    private static Object browserContext;
   7.194 +    
   7.195 +    @Factory public static Object[] compatibilityTests() throws Exception {
   7.196 +        Class<?> tck = loadOSGiClass(KnockoutTCK.class);
   7.197 +        Class<?> peer = loadOSGiClass(KnockoutFelixTCKImpl.class);
   7.198 +        // initialize the TCK
   7.199 +        Callable<Class[]> inst = (Callable<Class[]>) peer.newInstance();
   7.200 +        
   7.201 +        Class[] arr = inst.call();
   7.202 +        for (int i = 0; i < arr.length; i++) {
   7.203 +            if (arr[i].getClassLoader() == ClassLoader.getSystemClassLoader()) {
   7.204 +                fail("Should be an OSGi class: " + arr[i]);
   7.205 +            }
   7.206 +        }
   7.207 +        
   7.208 +        URI uri = DynamicHTTP.initServer();
   7.209 +
   7.210 +        Method start = peer.getMethod("start", URI.class);
   7.211 +        start.invoke(null, uri);
   7.212 +        
   7.213 +        ClassLoader l = getClassLoader();
   7.214 +        List<Object> res = new ArrayList<Object>();
   7.215 +        for (int i = 0; i < arr.length; i++) {
   7.216 +            seekKOTests(arr[i], res);
   7.217 +        }
   7.218 +        return res.toArray();
   7.219 +    }
   7.220 +
   7.221 +    private static void seekKOTests(Class<?> c, List<Object> res) throws SecurityException, ClassNotFoundException {
   7.222 +        Class<? extends Annotation> koTest =
   7.223 +            c.getClassLoader().loadClass(KOTest.class.getName()).
   7.224 +            asSubclass(Annotation.class);
   7.225 +        for (Method m : c.getMethods()) {
   7.226 +            if (m.getAnnotation(koTest) != null) {
   7.227 +                res.add(new KOFx(browserContext, m));
   7.228 +            }
   7.229 +        }
   7.230 +    }
   7.231 +
   7.232 +    static synchronized ClassLoader getClassLoader() throws InterruptedException {
   7.233 +        while (browserClass == null) {
   7.234 +            KnockoutFelixIT.class.wait();
   7.235 +        }
   7.236 +        return browserClass.getClassLoader();
   7.237 +    }
   7.238 +    
   7.239 +    public static synchronized void initialized(Class<?> browserCls, Object presenter) throws Exception {
   7.240 +        browserClass = browserCls;
   7.241 +        browserContext = presenter;
   7.242 +        KnockoutFelixIT.class.notifyAll();
   7.243 +    }
   7.244 +
   7.245 +    static Closeable activateInOSGi(Object presenter) throws Exception {
   7.246 +        Class<?> presenterClass = loadOSGiClass(Fn.Presenter.class);
   7.247 +        Class<?> fnClass = loadOSGiClass(Fn.class);
   7.248 +        Method m = fnClass.getMethod("activate", presenterClass);
   7.249 +        return (Closeable) m.invoke(null, presenter);
   7.250 +    }
   7.251 +}
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/ko-felix-test/src/test/resources/org/netbeans/html/ko/felix/test/test.html	Thu Jul 31 14:37:06 2014 +0200
     8.3 @@ -0,0 +1,56 @@
     8.4 +<!--
     8.5 +
     8.6 +    DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     8.7 +
     8.8 +    Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     8.9 +
    8.10 +    Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    8.11 +    Other names may be trademarks of their respective owners.
    8.12 +
    8.13 +    The contents of this file are subject to the terms of either the GNU
    8.14 +    General Public License Version 2 only ("GPL") or the Common
    8.15 +    Development and Distribution License("CDDL") (collectively, the
    8.16 +    "License"). You may not use this file except in compliance with the
    8.17 +    License. You can obtain a copy of the License at
    8.18 +    http://www.netbeans.org/cddl-gplv2.html
    8.19 +    or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    8.20 +    specific language governing permissions and limitations under the
    8.21 +    License.  When distributing the software, include this License Header
    8.22 +    Notice in each file and include the License file at
    8.23 +    nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    8.24 +    particular file as subject to the "Classpath" exception as provided
    8.25 +    by Oracle in the GPL Version 2 section of the License file that
    8.26 +    accompanied this code. If applicable, add the following below the
    8.27 +    License Header, with the fields enclosed by brackets [] replaced by
    8.28 +    your own identifying information:
    8.29 +    "Portions Copyrighted [year] [name of copyright owner]"
    8.30 +
    8.31 +    Contributor(s):
    8.32 +
    8.33 +    The Original Software is NetBeans. The Initial Developer of the Original
    8.34 +    Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    8.35 +
    8.36 +    If you wish your version of this file to be governed by only the CDDL
    8.37 +    or only the GPL Version 2, indicate your decision by adding
    8.38 +    "[Contributor] elects to include this software in this distribution
    8.39 +    under the [CDDL or GPL Version 2] license." If you do not indicate a
    8.40 +    single choice of license, a recipient has the option to distribute
    8.41 +    your version of this file under either the CDDL, the GPL Version 2 or
    8.42 +    to extend the choice of license to its licensees as provided above.
    8.43 +    However, if you add GPL Version 2 code and therefore, elected the GPL
    8.44 +    Version 2 license, then the option applies only if the new code is
    8.45 +    made subject to such option by the copyright holder.
    8.46 +
    8.47 +-->
    8.48 +<!DOCTYPE html>
    8.49 +<html>
    8.50 +    <head>
    8.51 +        <title>Knockout.fx Execution Harness</title>
    8.52 +        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    8.53 +        <meta name="viewport" content="width=device-width">
    8.54 +    </head>
    8.55 +    <body>
    8.56 +        <h1>Knockout.fx in Felix Execution Harness</h1>
    8.57 +    </body>
    8.58 +    <script></script>
    8.59 +</html>
     9.1 --- a/ko-osgi-test/pom.xml	Thu Jul 31 07:25:13 2014 +0200
     9.2 +++ b/ko-osgi-test/pom.xml	Thu Jul 31 14:37:06 2014 +0200
     9.3 @@ -6,10 +6,10 @@
     9.4          <artifactId>pom</artifactId>
     9.5          <version>1.0-SNAPSHOT</version>
     9.6      </parent>
     9.7 -    <name>KO Tests in an OSGi Container</name>
     9.8 +    <name>KO Tests in Equinox OSGi Container</name>
     9.9      <artifactId>ko-osgi-test</artifactId>
    9.10      <packaging>bundle</packaging>
    9.11 -    <description>Runs the TCK for Knockout in an OSGi Container</description>
    9.12 +    <description>Runs the TCK for Knockout in Equinox OSGi Container</description>
    9.13      <properties>
    9.14          <netbeans.compile.on.save>none</netbeans.compile.on.save>
    9.15      </properties>
    10.1 --- a/ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java	Thu Jul 31 07:25:13 2014 +0200
    10.2 +++ b/ko-osgi-test/src/main/java/org/netbeans/html/ko/osgi/test/KnockoutEquinoxTCKImpl.java	Thu Jul 31 14:37:06 2014 +0200
    10.3 @@ -108,7 +108,7 @@
    10.4              public void run() {
    10.5                  try {
    10.6                      final ClassLoader osgiClassLoader = BrowserBuilder.class.getClassLoader();
    10.7 -                    Thread.currentThread().setContextClassLoader(osgiClassLoader);
    10.8 +                    bb.classloader(osgiClassLoader);
    10.9                      bb.showAndWait();
   10.10                  } catch (Throwable t) {
   10.11                      t.printStackTrace();
    11.1 --- a/ko-osgi-test/src/test/resources/org/netbeans/html/ko/osgi/test/test.html	Thu Jul 31 07:25:13 2014 +0200
    11.2 +++ b/ko-osgi-test/src/test/resources/org/netbeans/html/ko/osgi/test/test.html	Thu Jul 31 14:37:06 2014 +0200
    11.3 @@ -50,7 +50,7 @@
    11.4          <meta name="viewport" content="width=device-width">
    11.5      </head>
    11.6      <body>
    11.7 -        <h1>Knockout.fx Execution Harness</h1>
    11.8 +        <h1>Knockout.fx in Equinox Execution Harness</h1>
    11.9      </body>
   11.10      <script></script>
   11.11  </html>
    12.1 --- a/pom.xml	Thu Jul 31 07:25:13 2014 +0200
    12.2 +++ b/pom.xml	Thu Jul 31 14:37:06 2014 +0200
    12.3 @@ -30,6 +30,7 @@
    12.4      <module>geo</module>
    12.5      <module>ko-ws-tyrus</module>
    12.6      <module>html4j-maven-plugin</module>
    12.7 +    <module>ko-felix-test</module>
    12.8      <module>ko-osgi-test</module>
    12.9      <module>equinox-agentclass-hook</module>
   12.10      <module>boot-script</module>
   12.11 @@ -313,6 +314,16 @@
   12.12              <artifactId>org-netbeans-lib-nbjavac</artifactId>
   12.13              <version>${netbeans.version}</version>
   12.14          </dependency>
   12.15 +        <dependency>
   12.16 +            <groupId>org.apache.felix</groupId>
   12.17 +            <artifactId>org.apache.felix.framework</artifactId>
   12.18 +            <version>4.2.1</version>
   12.19 +        </dependency>
   12.20 +      <dependency>
   12.21 +            <groupId>javax.servlet</groupId>
   12.22 +            <artifactId>javax.servlet-api</artifactId>
   12.23 +            <version>3.1.0</version>
   12.24 +        </dependency>
   12.25          <dependency> 
   12.26              <groupId>org.netbeans.modules</groupId>
   12.27              <artifactId>org-netbeans-modules-web-browser-api</artifactId>
    13.1 --- a/src/main/javadoc/overview.html	Thu Jul 31 07:25:13 2014 +0200
    13.2 +++ b/src/main/javadoc/overview.html	Thu Jul 31 14:37:06 2014 +0200
    13.3 @@ -75,7 +75,13 @@
    13.4           yet the application code can be written in Java.
    13.5          </p>
    13.6          
    13.7 -        <h3>What's New in Version 0.8.3?</h3>
    13.8 +        <h3>What's New in Version 0.9?</h3>
    13.9 +        
   13.10 +        <p>
   13.11 +            System can run in {@link net.java.html.boot.BrowserBuilder#classloader(java.lang.ClassLoader) Felix OSGi container} (originally only Equinox).
   13.12 +        </p>
   13.13 +        
   13.14 +        <h3>What's New in 0.8.x Versions?</h3>
   13.15          
   13.16          <p>
   13.17              Setters or array properties on classes generated by {@link net.java.html.json.Model}
   13.18 @@ -88,8 +94,6 @@
   13.19              {@link net.java.html.json.Model knockout bindings}.
   13.20          </p>
   13.21          
   13.22 -        <h3>What's New in Version 0.8.2?</h3>
   13.23 -        
   13.24          <p>
   13.25              Few bugfixes for better portability. 
   13.26              New API for {@link net.java.html.boot.script.Scripts headless execution}
   13.27 @@ -102,8 +106,6 @@
   13.28              {@link net.java.html.js Java/JavaScript interactions}.
   13.29          </p>
   13.30          
   13.31 -        <h3>What's New in Version 0.8.1?</h3>
   13.32 -        
   13.33          <p>
   13.34              {@link net.java.html.boot.fx.FXBrowsers} has been extended
   13.35              with new helper methods to make it easier to use HTML+Java
   13.36 @@ -121,8 +123,6 @@
   13.37              {@link java.lang.Class#getProtectionDomain}.
   13.38          </p>
   13.39          
   13.40 -        <h3>What's New in Version 0.8?</h3>
   13.41 -        
   13.42          <p>
   13.43              The first argument of method annotated by
   13.44              {@link net.java.html.json.OnReceive} annotation has to
   13.45 @@ -408,6 +408,7 @@
   13.46          online:
   13.47          <ul>
   13.48              <li>Current <a target="_blank" href="http://bits.netbeans.org/html+java/dev/">development</a> version
   13.49 +            <li>Version <a target="_blank" href="http://bits.netbeans.org/html+java/0.8.3">0.8.3</a>
   13.50              <li>Version <a target="_blank" href="http://bits.netbeans.org/html+java/0.8.2">0.8.2</a>
   13.51              <li>Version <a target="_blank" href="http://bits.netbeans.org/html+java/0.8.1">0.8.1</a>
   13.52              <li>Version <a target="_blank" href="http://bits.netbeans.org/html+java/0.8">0.8</a>