Use getDeclaredMethod when looking for the framework method that gives us the urlhandlers and fix a bug when running in different classloaders (FELIX-3840).

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1441630 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/URLHandlersContentHandlerProxy.java b/framework/src/main/java/org/apache/felix/framework/URLHandlersContentHandlerProxy.java
index 64610c4..354fab8 100644
--- a/framework/src/main/java/org/apache/felix/framework/URLHandlersContentHandlerProxy.java
+++ b/framework/src/main/java/org/apache/felix/framework/URLHandlersContentHandlerProxy.java
@@ -125,7 +125,7 @@
             else
             {
                 service = (ContentHandler) m_action.invoke(
-                    m_action.getMethod(framework.getClass(), "getContentHandlerService", STRING_TYPES),
+                    m_action.getDeclaredMethod(framework.getClass(), "getContentHandlerService", STRING_TYPES),
                     framework, new Object[]{m_mimeType});
             }
 
diff --git a/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java b/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java
index debd1fb..de4fea1 100644
--- a/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java
+++ b/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java
@@ -1,4 +1,4 @@
-/* 
+/*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -69,21 +69,21 @@
     private static final Method OPEN_CONNECTION_PROXY;
     private static final Method SAME_FILE;
     private static final Method TO_EXTERNAL_FORM;
-    
+
     static {
         SecureAction action = new SecureAction();
         try
         {
-            EQUALS = URLStreamHandler.class.getDeclaredMethod("equals", 
+            EQUALS = URLStreamHandler.class.getDeclaredMethod("equals",
                 new Class[]{URL.class, URL.class});
             action.setAccesssible(EQUALS);
-            GET_DEFAULT_PORT = URLStreamHandler.class.getDeclaredMethod("getDefaultPort", 
+            GET_DEFAULT_PORT = URLStreamHandler.class.getDeclaredMethod("getDefaultPort",
                 (Class[]) null);
             action.setAccesssible(GET_DEFAULT_PORT);
             GET_HOST_ADDRESS = URLStreamHandler.class.getDeclaredMethod(
                     "getHostAddress", new Class[]{URL.class});
             action.setAccesssible(GET_HOST_ADDRESS);
-            HASH_CODE = URLStreamHandler.class.getDeclaredMethod( 
+            HASH_CODE = URLStreamHandler.class.getDeclaredMethod(
                     "hashCode", new Class[]{URL.class});
             action.setAccesssible(HASH_CODE);
             HOSTS_EQUAL = URLStreamHandler.class.getDeclaredMethod(
@@ -95,7 +95,7 @@
             SAME_FILE = URLStreamHandler.class.getDeclaredMethod(
                     "sameFile", new Class[]{URL.class, URL.class});
             action.setAccesssible(SAME_FILE);
-            TO_EXTERNAL_FORM = URLStreamHandler.class.getDeclaredMethod( 
+            TO_EXTERNAL_FORM = URLStreamHandler.class.getDeclaredMethod(
                    "toExternalForm", new Class[]{URL.class});
             action.setAccesssible(TO_EXTERNAL_FORM);
         }
@@ -115,7 +115,7 @@
         }
         catch (Throwable ex)
         {
-           open_connection_proxy = null; 
+           open_connection_proxy = null;
            url_proxy_class = null;
         }
         OPEN_CONNECTION_PROXY = open_connection_proxy;
@@ -128,7 +128,7 @@
     private final URL m_builtInURL;
     private final String m_protocol;
 
-    public URLHandlersStreamHandlerProxy(String protocol, 
+    public URLHandlersStreamHandlerProxy(String protocol,
         SecureAction action, URLStreamHandler builtIn, URL builtInURL)
     {
         m_protocol = protocol;
@@ -137,7 +137,7 @@
         m_builtIn = builtIn;
         m_builtInURL = builtInURL;
     }
-    
+
     private URLHandlersStreamHandlerProxy(Object service, SecureAction action)
     {
         m_protocol = null;
@@ -162,11 +162,11 @@
         {
             return ((URLStreamHandlerService) svc).equals(url1, url2);
         }
-        try 
+        try
         {
             return ((Boolean) EQUALS.invoke(svc, new Object[]{url1, url2})).booleanValue();
-        } 
-        catch (Exception ex)  
+        }
+        catch (Exception ex)
         {
             throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage());
         }
@@ -183,11 +183,11 @@
         {
             return ((URLStreamHandlerService) svc).getDefaultPort();
         }
-        try 
+        try
         {
             return ((Integer) GET_DEFAULT_PORT.invoke(svc, null)).intValue();
-        } 
-        catch (Exception ex)  
+        }
+        catch (Exception ex)
         {
             throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage());
         }
@@ -205,11 +205,11 @@
         {
             return ((URLStreamHandlerService) svc).getHostAddress(url);
         }
-        try 
+        try
         {
             return (InetAddress) GET_HOST_ADDRESS.invoke(svc, new Object[]{url});
-        } 
-        catch (Exception ex)  
+        }
+        catch (Exception ex)
         {
             throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage());
         }
@@ -227,11 +227,11 @@
         {
             return ((URLStreamHandlerService) svc).hashCode(url);
         }
-        try 
+        try
         {
             return ((Integer) HASH_CODE.invoke(svc, new Object[]{url})).intValue();
-        } 
-        catch (Exception ex)  
+        }
+        catch (Exception ex)
         {
             throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage());
         }
@@ -249,11 +249,11 @@
         {
             return ((URLStreamHandlerService) svc).hostsEqual(url1, url2);
         }
-        try 
+        try
         {
             return ((Boolean) HOSTS_EQUAL.invoke(svc, new Object[]{url1, url2})).booleanValue();
-        } 
-        catch (Exception ex)  
+        }
+        catch (Exception ex)
         {
             throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage());
         }
@@ -270,7 +270,7 @@
         {
             return ((URLStreamHandlerService) svc).openConnection(url);
         }
-        try 
+        try
         {
             if ("http".equals(url.getProtocol()) &&
                 "felix.extensions".equals(url.getHost()) &&
@@ -280,15 +280,15 @@
                 {
                     Object handler =  m_action.getDeclaredField(
                         ExtensionManager.class, "m_extensionManager", null);
-    
+
                     if (handler != null)
                     {
                         return (URLConnection) m_action.invoke(
-                            m_action.getMethod(handler.getClass(), 
-                            "openConnection", new Class[]{URL.class}), handler, 
+                            m_action.getMethod(handler.getClass(),
+                            "openConnection", new Class[]{URL.class}), handler,
                             new Object[]{url});
                     }
-                    
+
                     throw new IOException("Extensions not supported or ambiguous context.");
                 }
                 catch (IOException ex)
@@ -301,12 +301,12 @@
                 }
             }
             return (URLConnection) OPEN_CONNECTION.invoke(svc, new Object[]{url});
-        } 
+        }
         catch (IOException ex)
         {
             throw ex;
         }
-        catch (Exception ex)  
+        catch (Exception ex)
         {
             throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage());
         }
@@ -322,22 +322,22 @@
         if (svc instanceof URLStreamHandlerService)
         {
             Method method;
-            try 
+            try
             {
                 method = svc.getClass().getMethod("openConnection", URL_PROXY_CLASS);
-            } 
-            catch (NoSuchMethodException e) 
+            }
+            catch (NoSuchMethodException e)
             {
                 RuntimeException rte = new UnsupportedOperationException(e.getMessage());
                 rte.initCause(e);
                 throw rte;
             }
-            try 
+            try
             {
                 m_action.setAccesssible(method);
                 return (URLConnection) method.invoke(svc, new Object[]{url, proxy});
             }
-            catch (Exception e) 
+            catch (Exception e)
             {
                 if (e instanceof IOException)
                 {
@@ -346,11 +346,11 @@
                 throw new IOException(e.getMessage());
             }
         }
-        try 
+        try
         {
             return (URLConnection) OPEN_CONNECTION_PROXY.invoke(svc, new Object[]{url, proxy});
-        } 
-        catch (Exception ex)  
+        }
+        catch (Exception ex)
         {
             if (ex instanceof IOException)
             {
@@ -360,7 +360,7 @@
         }
     }
 
-    // We use this thread local to detect whether we have a reentrant entry to the parseURL 
+    // We use this thread local to detect whether we have a reentrant entry to the parseURL
     // method. This can happen do to some difference between gnu/classpath and sun jvms
     // For more see inside the method.
     private static final ThreadLocal m_loopCheck = new ThreadLocal();
@@ -378,22 +378,22 @@
         }
         else
         {
-            try 
+            try
             {
                 URL test = null;
-                // In order to cater for built-in urls being over-writable we need to use a 
+                // In order to cater for built-in urls being over-writable we need to use a
                 // somewhat strange hack. We use a hidden feature inside the jdk which passes
                 // the handler of the url given as a context to a new URL to that URL as its
                 // handler. This way, we can create a new URL which will use the given built-in
                 // handler to parse the url. Subsequently, we can use the information from that
-                // URL to call set with the correct values. 
+                // URL to call set with the correct values.
                 if (m_builtInURL != null)
                 {
                     // However, if we are on gnu/classpath we have to pass the handler directly
                     // because the hidden feature is not there. Funnily, the workaround to pass
                     // pass the handler directly doesn't work on sun as their handler detects
                     // that it is not the same as the one inside the url and throws an exception
-                    // Luckily it doesn't do that on gnu/classpath. We detect that we need to 
+                    // Luckily it doesn't do that on gnu/classpath. We detect that we need to
                     // pass the handler directly by using the m_loopCheck thread local to detect
                     // that we parseURL has been called inside a call to parseURL.
                     if (m_loopCheck.get() != null)
@@ -418,15 +418,15 @@
                 else
                 {
                     // We don't have a url with a built-in handler for this but still want to create
-                    // the url with the buil-in handler as we could find one now. This might not 
-                    // work for all handlers on sun but it is better then doing nothing. 
+                    // the url with the buil-in handler as we could find one now. This might not
+                    // work for all handlers on sun but it is better then doing nothing.
                     test = m_action.createURL(url, spec, (URLStreamHandler) svc);
                 }
 
-                super.setURL(url, test.getProtocol(), test.getHost(), test.getPort(),test.getAuthority(), 
+                super.setURL(url, test.getProtocol(), test.getHost(), test.getPort(),test.getAuthority(),
                     test.getUserInfo(), test.getPath(), test.getQuery(), test.getRef());
-            } 
-            catch (Exception ex)  
+            }
+            catch (Exception ex)
             {
                 throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage());
             }
@@ -445,12 +445,12 @@
         {
             return ((URLStreamHandlerService) svc).sameFile(url1, url2);
         }
-        try 
+        try
         {
-            return ((Boolean) SAME_FILE.invoke( 
+            return ((Boolean) SAME_FILE.invoke(
                 svc, new Object[]{url1, url2})).booleanValue();
-        } 
-        catch (Exception ex)  
+        }
+        catch (Exception ex)
         {
             throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage());
         }
@@ -473,7 +473,7 @@
     {
         return toExternalForm(url, getStreamHandlerService());
     }
-    
+
     private String toExternalForm(URL url, Object svc)
     {
         if (svc == null)
@@ -487,18 +487,18 @@
         }
         try
         {
-            try 
+            try
             {
-                String result = (String) TO_EXTERNAL_FORM.invoke( 
+                String result = (String) TO_EXTERNAL_FORM.invoke(
                     svc, new Object[]{url});
-                
-                // mika does return an invalid format if we have a url with the 
+
+                // mika does return an invalid format if we have a url with the
                 // protocol only (<proto>://null) - we catch this case now
                 if ((result != null) && (result.equals(url.getProtocol() + "://null")))
                 {
                     result = url.getProtocol() + ":";
                 }
-                
+
                 return result;
             }
             catch (InvocationTargetException ex)
@@ -521,16 +521,16 @@
         catch (NullPointerException ex)
         {
             // workaround for harmony and possibly J9. The issue is that
-            // their implementation of URLStreamHandler.toExternalForm() 
+            // their implementation of URLStreamHandler.toExternalForm()
             // assumes that URL.getFile() doesn't return null but in our
             // case it can -- hence, we catch the NPE and do the work
             // ourselvs. The only difference is that we check whether the
-            // URL.getFile() is null or not. 
+            // URL.getFile() is null or not.
             StringBuffer answer = new StringBuffer();
             answer.append(url.getProtocol());
             answer.append(':');
             String authority = url.getAuthority();
-            if ((authority != null) && (authority.length() > 0)) 
+            if ((authority != null) && (authority.length() > 0))
             {
                 answer.append("//"); //$NON-NLS-1$
                 answer.append(url.getAuthority());
@@ -542,19 +542,19 @@
             {
                 answer.append(file);
             }
-            if (ref != null) 
+            if (ref != null)
             {
                 answer.append('#');
                 answer.append(ref);
             }
             return answer.toString();
         }
-        catch (Exception ex)  
+        catch (Exception ex)
         {
             throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage());
         }
     }
-    
+
     /**
      * <p>
      * Private method to retrieve the stream handler service from the
@@ -572,12 +572,13 @@
         {
             // Get the framework instance associated with call stack.
             Object framework = URLHandlers.getFrameworkFromContext();
-    
-            if (framework == null) 
+         
+            if (framework == null)
             {
                 return m_builtIn;
             }
 
+
             Object service = null;
             if (framework instanceof Felix)
             {
@@ -586,11 +587,11 @@
             else
             {
                 service = m_action.invoke(
-                    m_action.getMethod(framework.getClass(), "getStreamHandlerService", STRING_TYPES), 
+                    m_action.getDeclaredMethod(framework.getClass(), "getStreamHandlerService", STRING_TYPES),
                     framework, new Object[]{m_protocol});
             }
 
-            if (service == null) 
+            if (service == null)
             {
                 return m_builtIn;
             }
@@ -599,8 +600,8 @@
                 return (URLStreamHandlerService) service;
             }
             return (URLStreamHandlerService) Proxy.newProxyInstance(
-                URLStreamHandlerService.class.getClassLoader(), 
-                new Class[]{URLStreamHandlerService.class}, 
+                URLStreamHandlerService.class.getClassLoader(),
+                new Class[]{URLStreamHandlerService.class},
                 new URLHandlersStreamHandlerProxy(service, m_action));
         }
         catch (ThreadDeath td)
@@ -610,12 +611,12 @@
         catch (Throwable t)
         {
             // In case that we are inside tomcat - the problem is that the webapp classloader
-            // creates a new url to load a class. This gets us to this method. Now, if we 
+            // creates a new url to load a class. This gets us to this method. Now, if we
             // trigger a classload while executing tomcat is creating a new url and we end-up with
-            // a loop which is cut short after two iterations (because of a circularclassload). 
+            // a loop which is cut short after two iterations (because of a circularclassload).
             // We catch this exception (and all others) and just return the built-in handler
-            // (if we have any) as this way we at least eventually get started (this just means 
-            // that we don't use the potentially provided built-in handler overwrite). 
+            // (if we have any) as this way we at least eventually get started (this just means
+            // that we don't use the potentially provided built-in handler overwrite).
             return m_builtIn;
         }
     }
@@ -625,18 +626,23 @@
     {
         try
         {
+
             Class[] types = method.getParameterTypes();
+            if (m_service == null)
+            {
+                return m_action.invoke(m_action.getMethod(this.getClass(), method.getName(), types), this, params);
+            }
             if ("parseURL".equals(method.getName()))
             {
                 types[0] = m_service.getClass().getClassLoader().loadClass(
                     URLStreamHandlerSetter.class.getName());
                 params[0] = Proxy.newProxyInstance(
-                    m_service.getClass().getClassLoader(), new Class[]{types[0]}, 
+                    m_service.getClass().getClassLoader(), new Class[]{types[0]},
                     (URLHandlersStreamHandlerProxy) params[0]);
             }
-            return m_action.invokeDirect(m_action.getMethod(m_service.getClass(), 
+            return m_action.invokeDirect(m_action.getDeclaredMethod(m_service.getClass(),
                 method.getName(), types), m_service, params);
-        } 
+        }
         catch (Exception ex)
         {
             throw ex;
diff --git a/framework/src/test/java/org/apache/felix/framework/URLHandlersTest.java b/framework/src/test/java/org/apache/felix/framework/URLHandlersTest.java
new file mode 100644
index 0000000..42f45e4
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/URLHandlersTest.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright 2013 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.framework;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import junit.framework.TestCase;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.launch.Framework;
+import org.osgi.service.url.URLConstants;
+import org.osgi.service.url.URLStreamHandlerService;
+import org.osgi.service.url.URLStreamHandlerSetter;
+
+/**
+ *
+ * @author pauls
+ */
+public class URLHandlersTest extends TestCase
+{
+    public void testURLHandlers() throws Exception
+    {
+        String mf = "Bundle-SymbolicName: url.test\n"
+            + "Bundle-Version: 1.0.0\n"
+            + "Bundle-ManifestVersion: 2\n"
+            + "Import-Package: org.osgi.framework,org.osgi.service.url\n"
+            + "Manifest-Version: 1.0\n"
+            + Constants.BUNDLE_ACTIVATOR + ": " + TestURLHandlersActivator.class.getName() + "\n\n";
+
+        File bundleFile = createBundle(mf, TestURLHandlersActivator.class);
+
+        Framework f = createFramework();
+        f.init();
+        f.start();
+
+        try
+        {
+            final Bundle bundle = f.getBundleContext().installBundle(bundleFile.toURI().toString());
+            bundle.start();
+        }
+        finally
+        {
+            try
+            {
+                f.stop();
+            }
+            catch (Throwable t)
+            {
+            }
+        }
+    }
+
+    public void testURLHandlersWithClassLoaderIsolation() throws Exception
+    {
+        DelegatingClassLoader cl1 = new DelegatingClassLoader(this.getClass().getClassLoader());
+        DelegatingClassLoader cl2 = new DelegatingClassLoader(this.getClass().getClassLoader());
+
+        Framework f = createFramework();
+        f.init();
+        f.start();
+
+        String mf = "Bundle-SymbolicName: url.test\n"
+            + "Bundle-Version: 1.0.0\n"
+            + "Bundle-ManifestVersion: 2\n"
+            + "Import-Package: org.osgi.framework\n"
+            + "Manifest-Version: 1.0\n"
+            + Constants.BUNDLE_ACTIVATOR + ": " + TestURLHandlersActivator.class.getName();
+
+        File bundleFile = createBundle(mf, TestURLHandlersActivator.class);
+
+        final Bundle bundle = f.getBundleContext().installBundle(bundleFile.toURI().toString());
+        bundle.start();
+
+        Class clazz1 = cl1.loadClass(URLHandlersTest.class.getName());
+
+        clazz1.getMethod("testURLHandlers").invoke(clazz1.newInstance());
+
+        bundle.stop();
+        bundle.start();
+        Class clazz2 = cl2.loadClass(URLHandlersTest.class.getName());
+
+        clazz2.getMethod("testURLHandlers").invoke(clazz2.newInstance());
+        bundle.stop();
+        bundle.start();
+        f.stop();
+    }
+
+    public static class DelegatingClassLoader extends ClassLoader
+    {
+        private final Object m_lock = new Object();
+        private final ClassLoader m_source;
+
+        public DelegatingClassLoader(ClassLoader source)
+        {
+            m_source = source;
+        }
+
+        @Override
+        public Class<?> loadClass(String name) throws ClassNotFoundException
+        {
+            synchronized (m_lock)
+            {
+                Class<?> result = findLoadedClass(name);
+                if (result != null)
+                {
+                    return result;
+                }
+            }
+            if (!name.startsWith("org.apache.felix") && !name.startsWith("org.osgi."))
+            {
+                return m_source.loadClass(name);
+            }
+            byte[] buffer = new byte[8 * 1024];
+            try
+            {
+                InputStream is = m_source.getResourceAsStream(name.replace('.', '/') + ".class");
+                ByteArrayOutputStream os = new ByteArrayOutputStream();
+                for (int i = is.read(buffer); i != -1; i = is.read(buffer))
+                {
+                    os.write(buffer, 0, i);
+
+                }
+                is.close();
+                os.close();
+                buffer = os.toByteArray();
+            }
+            catch (Exception ex)
+            {
+                throw new ClassNotFoundException("Unable to load class: " + name + " with cl: " + System.identityHashCode(this), ex);
+            }
+            return super.defineClass(name, buffer, 0, buffer.length, null);
+        }
+    }
+
+    public static class TestURLHandlersActivator implements BundleActivator, URLStreamHandlerService
+    {
+        private volatile ServiceRegistration m_reg = null;
+
+        public URLConnection openConnection(URL u) throws IOException
+        {
+            return null;//throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public void parseURL(URLStreamHandlerSetter realHandler, URL u, String spec, int start, int limit)
+        {
+            realHandler.setURL(u, spec, spec, start, spec, spec, spec, spec, spec);
+            //throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public String toExternalForm(URL u)
+        {
+            return u.toString();//throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public boolean equals(URL u1, URL u2)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public int getDefaultPort()
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public InetAddress getHostAddress(URL u)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public int hashCode(URL u)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public boolean hostsEqual(URL u1, URL u2)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public boolean sameFile(URL u1, URL u2)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public void start(final BundleContext context) throws Exception
+        {
+            try
+            {
+                new URL("test" + System.identityHashCode(TestURLHandlersActivator.this) + ":").openConnection();
+                throw new Exception("Unexpected url resolve");
+            }
+            catch (Exception ex)
+            {
+                // pass
+            }
+
+            Hashtable props = new Hashtable<String, String>();
+            props.put(URLConstants.URL_HANDLER_PROTOCOL, "test" + System.identityHashCode(TestURLHandlersActivator.this));
+
+            ServiceRegistration reg = context.registerService(URLStreamHandlerService.class, this, props);
+
+            new URL("test" + System.identityHashCode(TestURLHandlersActivator.this) + ":").openConnection();
+
+            reg.unregister();
+
+            try
+            {
+                new URL("test" + System.identityHashCode(TestURLHandlersActivator.this) + ":").openConnection();
+                throw new Exception("Unexpected url resolve");
+            }
+            catch (Exception ex)
+            {
+                // pass
+            }
+
+            Bundle bundle2 = null;
+            if (context.getBundle().getSymbolicName().equals("url.test"))
+            {
+
+                String mf = "Bundle-SymbolicName: url.test2\n"
+                    + "Bundle-Version: 1.0.0\n"
+                    + "Bundle-ManifestVersion: 2\n"
+                    + "Import-Package: org.osgi.framework,org.osgi.service.url\n"
+                    + "Manifest-Version: 1.0\n"
+                    + Constants.BUNDLE_ACTIVATOR + ": " + TestURLHandlersActivator.class.getName() + "\n\n";
+
+                File bundleFile = createBundle(mf, TestURLHandlersActivator.class);
+
+                bundle2 = context.installBundle(bundleFile.toURI().toURL().toString());
+            }
+            if (bundle2 != null)
+            {
+                try
+                {
+                    new URL("test" + System.identityHashCode(bundle2) + ":").openConnection();
+                    throw new Exception("Unexpected url2 resolve");
+                }
+                catch (Exception ex)
+                {
+                }
+                bundle2.start();
+                new URL("test" + System.identityHashCode(bundle2) + ":").openConnection();
+                bundle2.stop();
+                try
+                {
+                    new URL("test" + System.identityHashCode(bundle2) + ":").openConnection();
+                    throw new Exception("Unexpected url2 resolve");
+                }
+                catch (Exception ex)
+                {
+                }
+            }
+            else
+            {
+                try
+                {
+                    new URL("test" + System.identityHashCode(context.getBundle()) + ":").openConnection();
+                    throw new Exception("Unexpected url2 resolve");
+                }
+                catch (Exception ex)
+                {
+                }
+                props = new Hashtable();
+                props.put(URLConstants.URL_HANDLER_PROTOCOL, "test" + System.identityHashCode(context.getBundle()));
+                m_reg = context.registerService(URLStreamHandlerService.class, this, props);
+                new URL("test" + System.identityHashCode(context.getBundle()) + ":").openConnection();
+            }
+        }
+
+        private static File createBundle(String manifest, Class... classes) throws IOException
+        {
+            File f = File.createTempFile("felix-bundle", ".jar");
+            f.deleteOnExit();
+
+            Manifest mf = new Manifest(new ByteArrayInputStream(manifest.getBytes("utf-8")));
+            JarOutputStream os = new JarOutputStream(new FileOutputStream(f), mf);
+
+            for (Class clazz : classes)
+            {
+                String path = clazz.getName().replace('.', '/') + ".class";
+                os.putNextEntry(new ZipEntry(path));
+
+                InputStream is = clazz.getClassLoader().getResourceAsStream(path);
+                byte[] buffer = new byte[8 * 1024];
+                for (int i = is.read(buffer); i != -1; i = is.read(buffer))
+                {
+                    os.write(buffer, 0, i);
+                }
+                is.close();
+                os.closeEntry();
+            }
+            os.close();
+            return f;
+        }
+
+        public void stop(BundleContext context) throws Exception
+        {
+            if (m_reg != null)
+            {
+                m_reg.unregister();
+            }
+        }
+    }
+
+    private static File createBundle(String manifest, Class... classes) throws IOException
+    {
+        File f = File.createTempFile("felix-bundle", ".jar");
+        f.deleteOnExit();
+
+        Manifest mf = new Manifest(new ByteArrayInputStream(manifest.getBytes("utf-8")));
+        JarOutputStream os = new JarOutputStream(new FileOutputStream(f), mf);
+
+        for (Class clazz : classes)
+        {
+            String path = clazz.getName().replace('.', '/') + ".class";
+            os.putNextEntry(new ZipEntry(path));
+
+            InputStream is = clazz.getClassLoader().getResourceAsStream(path);
+            byte[] buffer = new byte[8 * 1024];
+            for (int i = is.read(buffer); i != -1; i = is.read(buffer))
+            {
+                os.write(buffer, 0, i);
+            }
+            is.close();
+            os.closeEntry();
+        }
+        os.close();
+        return f;
+    }
+
+    private static Felix createFramework() throws Exception
+    {
+        Map params = new HashMap();
+        params.put(Constants.FRAMEWORK_SYSTEMPACKAGES,
+            "org.osgi.framework; version=1.4.0,"
+            + "org.osgi.service.packageadmin; version=1.2.0,"
+            + "org.osgi.service.startlevel; version=1.1.0,"
+            + "org.osgi.util.tracker; version=1.3.3,"
+            + "org.osgi.service.url; version=1.0.0");
+        File cacheDir = File.createTempFile("felix-cache", ".dir");
+        if (!cacheDir.delete() || !cacheDir.mkdirs())
+        {
+            fail("Unable to set-up cache dir");
+        }
+        String cache = cacheDir.getPath();
+        params.put("felix.cache.profiledir", cache);
+        params.put("felix.cache.dir", cache);
+        params.put(Constants.FRAMEWORK_STORAGE, cache);
+
+        return new Felix(params);
+    }
+}