These classes make up the core of the new URL Handlers service implementation.


git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@331741 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/org/apache/felix/framework/URLHandlers.java b/framework/src/org/apache/felix/framework/URLHandlers.java
new file mode 100644
index 0000000..8a2865d
--- /dev/null
+++ b/framework/src/org/apache/felix/framework/URLHandlers.java
@@ -0,0 +1,276 @@
+/*
+ *   Copyright 2005 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.net.*;
+import java.util.*;
+
+import org.apache.felix.framework.util.FelixConstants;
+import org.apache.felix.framework.util.SecurityManagerEx;
+import org.apache.felix.moduleloader.ModuleClassLoader;
+import org.osgi.framework.BundleContext;
+
+/**
+ * <p>
+ * This class is a singleton and implements the stream and content handler
+ * factories for all framework instances executing within the JVM. Any
+ * calls to retrieve stream or content handlers is routed through this class
+ * and it acts as a multiplexer for all framework instances. To achieve this,
+ * all framework instances register with this class when they are created so
+ * that it can maintain a centralized registry of instances.
+ * </p>
+ * <p>
+ * When this class receives a request for a stream or content handler, it
+ * always returns a proxy handler instead of only returning a proxy if a
+ * handler currently exists. This approach is used for three reasons:
+ * </p>
+ * <ol>
+ *   <li>Caching behavior by the JVM of stream handlers does not give you
+ *       a second chance to provide a handler.
+ *   </li>
+ *   <li>Due to the dynamic nature of OSGi services, handlers may appear at
+ *       any time, so always creating a proxy makes sense.
+ *   </li>
+ *   <li>Since these handler factories service all framework instances,
+ *       some instances may have handlers and others may not, so returning
+ *       a proxy is the only answer that makes sense.
+ *   </li>
+ * </ol>
+ * <p>
+ * It is possible to disable the URL Handlers service by setting the
+ * <tt>framework.service.urlhandlers</tt> configuration property to <tt>false</tt>.
+ * When multiple framework instances are in use, if no framework instances enable
+ * the URL Handlers service, then the singleton stream and content factories will
+ * never be set (i.e., <tt>URL.setURLStreamHandlerFactory()</tt> and
+ * <tt>URLConnection.setContentHandlerFactory()</tt>). However, if one instance
+ * enables URL Handlers service, then the factory methods will be invoked. In
+ * that case, framework instances that disable the URL Handlers service will
+ * simply not provide that services to their contained bundles, while framework
+ * instances with the service enabled will.
+ * </p>
+**/
+class URLHandlers implements URLStreamHandlerFactory, ContentHandlerFactory
+{
+    private static String m_lock = new String("string-lock");
+    private static SecurityManagerEx m_sm = null;
+    private static URLHandlers m_handler = null;
+    private static int m_frameworkCount = 0;
+    private static List m_frameworkList = null;
+    private static Map m_contentHandlerCache = null;
+    private static URLHandlersBundleStreamHandler m_bundleHandler = null;
+
+    /**
+     * <p>
+     * Only one instance of this class is created in a static initializer
+     * and that one instance is registered as the stream and content handler
+     * factories for the JVM.
+     * </p> 
+    **/
+    private URLHandlers()
+    {
+System.out.println("SETTING HANDLERS");
+        // No one can create an instance, but we need an instance
+        // so we can set this as the stream and content handler factory.
+        URL.setURLStreamHandlerFactory(this);
+        URLConnection.setContentHandlerFactory(this);
+    }
+
+    /**
+     * <p>
+     * This is a method implementation for the <tt>URLStreamHandlerFactory</tt>
+     * interface. It simply creates a stream handler proxy object for the
+     * specified protocol. It does not perform caching of the return proxies,
+     * since this is done by the Java runtime.
+     * </p>
+     * @param protocol the protocol for which a stream handler should be returned.
+     * @return a stream handler proxy for the specified protocol.
+    **/
+    public URLStreamHandler createURLStreamHandler(String protocol)
+    {
+        synchronized (this)
+        {
+            // TODO: Determine the best way to handle internal handlers.
+            if (protocol.equals("file"))
+            {
+                return null;
+            }
+            else if (protocol.equals(FelixConstants.BUNDLE_URL_PROTOCOL))
+            {
+                if (m_bundleHandler == null)
+                {
+                    m_bundleHandler = new URLHandlersBundleStreamHandler(null);
+                }
+                return m_bundleHandler;
+            }
+            return new URLHandlersStreamHandlerProxy(protocol);
+        }
+    }
+
+    /**
+     * <p>
+     * This is a method implementation for the <tt>ContentHandlerFactory</tt>
+     * interface. It simply creates a content handler proxy object for the
+     * specified mime type. It also performs caching of the return proxies,
+     * since this is not done by the Java runtime.
+     * </p>
+     * @param mimeType the mime type for which a content handler should be returned.
+     * @return a content handler proxy for the specified mime type.
+    **/
+    public ContentHandler createContentHandler(String mimeType)
+    {
+        synchronized (m_lock)
+        {
+            // See if we have a cached content handler.
+            ContentHandler hdlr = (m_contentHandlerCache == null)
+                ? null
+                : (ContentHandler) m_contentHandlerCache.get(mimeType);
+            // If no cache content handler, then create one.
+            if (hdlr == null)
+            {
+                hdlr = new URLHandlersContentHandlerProxy(mimeType);
+                if (m_contentHandlerCache == null)
+                {
+                    m_contentHandlerCache = new HashMap();
+                }
+                m_contentHandlerCache.put(mimeType, hdlr);
+            }
+            return hdlr;
+        }
+    }
+
+    /**
+     * <p>
+     * Static method that adds a framework instance to the centralized
+     * instance registry.
+     * </p>
+     * @param framework the framework instance to be added to the instance
+     *        registry.
+     * @param context the system bundle context associated with the framework
+     *        instance.
+     * @param enable a flag indicating whether or not the framework wants to
+     *        enable the URL Handlers service.
+    **/
+    public static void registerInstance(
+        Felix framework, BundleContext context, boolean enable)
+    {
+        synchronized (m_lock)
+        {
+            // Increment framework instance count.
+            m_frameworkCount++;
+
+            // If the URL Handlers service is not going to be enabled,
+            // then return immediately.
+            if (enable)
+            {
+                // We need to create an instance if this is the first
+                // time this method is called, which will set the handler
+                // factories.
+                if (m_handler == null)
+                {
+                    m_sm = new SecurityManagerEx();
+                    m_handler = new URLHandlers();
+                }
+    
+                // Create the framework list, if necessary, and add the
+                // new framework instance to it.
+                if (m_frameworkList == null)
+                {
+                    m_frameworkList = new ArrayList();
+                }
+                m_frameworkList.add(framework);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * Static method that removes a framework instance from the centralized
+     * instance registry.
+     * </p>
+     * @param framework the framework instance to be removed from the instance
+     *        registry.
+    **/
+    public static void unregisterInstance(Felix framework)
+    {
+        synchronized (m_lock)
+        {
+            m_frameworkCount--;
+            if (m_frameworkList != null)
+            {
+                m_frameworkList.remove(framework);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * This method returns the system bundle context for the caller.
+     * It determines the appropriate system bundle by retrieving the
+     * class call stack and find the first class that is loaded from
+     * a bundle. It then checks to see which of the registered framework
+     * instances owns the class and returns its system bundle context.
+     * </p>
+     * @return the system bundle context associated with the caller or
+     *         <tt>null</tt> if no associated framework was found.
+    **/
+    public static Felix getFrameworkFromContext()
+    {
+        synchronized (m_lock)
+        {
+            if (m_frameworkList != null)
+            {
+                // First, perform a simple short cut, if there is only
+                // one framework instance registered, assume that this
+                // is the bundle context to be returned and just return
+                // it immediately.
+                if ((m_frameworkList.size() == 1) && (m_frameworkCount == 1))
+                {
+                    return (Felix) m_frameworkList.get(0);
+                }
+    
+                // If there is more than one registered framework instance,
+                // then get the current class call stack.
+                Class[] stack = m_sm.getClassContext();
+                // Find the first class that is loaded from a bundle.
+                Class targetClass = null;
+                for (int i = 0; i < stack.length; i++)
+                {
+                    if (stack[i].getClassLoader() instanceof ModuleClassLoader)
+                    {
+                        targetClass = stack[i];
+                        break;
+                    }
+                }
+                // If we found a class loaded from a bundle, then iterate
+                // over the framework instances and see which framework owns
+                // the bundle that loaded the class.
+                if (targetClass != null)
+                {
+                    // Check the registry of framework instances
+                    for (int i = 0; i < m_frameworkList.size(); i++)
+                    {
+                        if (((Felix) m_frameworkList.get(i)).isBundleClass(targetClass))
+                        {
+                            return (Felix) m_frameworkList.get(i);
+                        }
+                    }
+                }
+            }
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/framework/src/org/apache/felix/framework/URLHandlersActivator.java b/framework/src/org/apache/felix/framework/URLHandlersActivator.java
new file mode 100644
index 0000000..8851a6a
--- /dev/null
+++ b/framework/src/org/apache/felix/framework/URLHandlersActivator.java
@@ -0,0 +1,62 @@
+/*
+ *   Copyright 2005 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 org.apache.felix.framework.util.FelixConstants;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * <p>
+ * Simple activator class used by the system bundle to enable the
+ * URL Handlers service. The only purpose of this class is to call
+ * <tt>URLHandlers.registerInstance()</tt> when the framework is
+ * started and <tt>URLHandlers.unregisterInstance()</tt> when the
+ * framework is stopped.
+ *</p>
+**/
+class URLHandlersActivator implements BundleActivator
+{
+    private Felix m_framework = null;
+    private BundleContext m_context = null;
+
+    public URLHandlersActivator(Felix framework)
+    {
+        m_framework = framework;
+    }
+
+    //
+    // Bundle activator methods.
+    //
+
+    public void start(BundleContext context)
+    {
+        m_context = context;
+        // Only register the framework with the URL Handlers service
+        // if the service is enabled.
+        boolean enable = (m_framework.getConfig().get(
+                FelixConstants.SERVICE_URLHANDLERS_PROP) == null)
+                ? true
+                : !m_framework.getConfig().get(FelixConstants.SERVICE_URLHANDLERS_PROP).equals("false");
+        URLHandlers.registerInstance(m_framework, m_context, enable);
+    }
+
+    public void stop(BundleContext context)
+    {
+        URLHandlers.unregisterInstance(m_framework);
+    }
+}
\ No newline at end of file
diff --git a/framework/src/org/apache/felix/framework/URLHandlersBundleStreamHandler.java b/framework/src/org/apache/felix/framework/URLHandlersBundleStreamHandler.java
new file mode 100644
index 0000000..19f017a
--- /dev/null
+++ b/framework/src/org/apache/felix/framework/URLHandlersBundleStreamHandler.java
@@ -0,0 +1,35 @@
+/*
+ *   Copyright 2005 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.IOException;
+import java.net.*;
+
+class URLHandlersBundleStreamHandler extends URLStreamHandler
+{
+    private Felix m_framework = null;
+
+    public URLHandlersBundleStreamHandler(Felix framework)
+    {
+        m_framework = framework;
+    }
+
+    protected synchronized URLConnection openConnection(URL url) throws IOException
+    {
+        return new URLHandlersBundleURLConnection(url, m_framework);
+    }
+}
\ No newline at end of file
diff --git a/framework/src/org/apache/felix/framework/URLHandlersBundleURLConnection.java b/framework/src/org/apache/felix/framework/URLHandlersBundleURLConnection.java
new file mode 100644
index 0000000..551373e
--- /dev/null
+++ b/framework/src/org/apache/felix/framework/URLHandlersBundleURLConnection.java
@@ -0,0 +1,139 @@
+/*
+ *   Copyright 2005 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.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.Permission;
+
+class URLHandlersBundleURLConnection extends URLConnection
+{
+    private Felix m_framework;
+    private int m_contentLength;
+    private long m_contentTime;
+    private String m_contentType;
+    private InputStream m_is;
+
+    public URLHandlersBundleURLConnection(URL url, Felix framework)
+    {
+        super(url);
+        m_framework = framework;
+    }
+
+    public void connect() throws IOException
+    {
+        if (!connected)
+        {
+            // If we don't have a framework instance, try to find
+            // one from the call context.
+            if (m_framework == null)
+            {
+                m_framework = URLHandlers.getFrameworkFromContext();
+            }
+
+            // If the framework has disabled the URL Handlers service,
+            // then it will not be found so just return null.
+            if (m_framework == null)
+            {
+                throw new IOException("Unable to find framework instance from context.");
+            }
+
+            m_is = m_framework.getBundleResourceInputStream(url);
+            m_contentLength = m_is.available();
+            m_contentTime = 0L;
+            m_contentType = URLConnection.guessContentTypeFromName(url.getFile());
+            connected = true;
+        }
+    }
+
+    public InputStream getInputStream()
+        throws IOException
+    {
+        if (!connected)
+        {
+            connect();
+        }
+        return m_is;
+    }
+
+    public int getContentLength()
+    {
+        if (!connected)
+        {
+            try
+            {
+                connect();
+            }
+            catch(IOException ex)
+            {
+                return -1;
+            }
+        }
+        return m_contentLength;
+    }
+
+    public long getLastModified()
+    {
+        if (!connected)
+        {
+            try
+            {
+                connect();
+            }
+            catch(IOException ex)
+            {
+                return 0;
+            }
+        }
+        if (m_contentTime != -1L)
+        {
+            return m_contentTime;
+        }
+        else
+        {
+            return 0L;
+        }
+    }
+
+    public String getContentType()
+    {
+        if (!connected)
+        {
+            try
+            {
+                connect();
+            }
+            catch (IOException ex)
+            {
+                return null;
+            }
+        }
+        return m_contentType;
+    }
+
+    public Permission getPermission()
+    {
+        // TODO: This should probably return a FilePermission
+        // to access the bundle JAR file, but we don't have the
+        // necessary information here to construct the absolute
+        // path of the JAR file...so it would take some
+        // re-arranging to get this to work.
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/framework/src/org/apache/felix/framework/URLHandlersContentHandlerProxy.java b/framework/src/org/apache/felix/framework/URLHandlersContentHandlerProxy.java
new file mode 100644
index 0000000..42cfefe
--- /dev/null
+++ b/framework/src/org/apache/felix/framework/URLHandlersContentHandlerProxy.java
@@ -0,0 +1,124 @@
+/*
+ *   Copyright 2005 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.IOException;
+import java.net.ContentHandler;
+import java.net.URLConnection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.url.URLConstants;
+
+/**
+ * <p>
+ * This class implements a content handler proxy. When the content handler
+ * proxy instance is created, it is associated with a particular mime type
+ * and will answer all future requests for content of that type. It does
+ * not directly handle the content requests, but delegates the requests to
+ * an underlying content handler service.
+ * </p>
+ * <p>
+ * The proxy for a particular mime type is used for all framework instances
+ * that may contain their own content handler services. When performing a
+ * content handler operation, the proxy retrieves the handler service from
+ * the framework instance associated with the current call stack and delegates
+ * the call to the handler service.
+ * </p>
+ * <p>
+ * The proxy will create simple content handler service trackers for each
+ * framework instance. The trackers will listen to service events in its
+ * respective framework instance to maintain a reference to the "best"
+ * content handler service at any given time.
+ * </p>
+**/
+class URLHandlersContentHandlerProxy extends ContentHandler
+{
+    private Map m_trackerMap = new HashMap();
+    private String m_mimeType = null;
+
+    public URLHandlersContentHandlerProxy(String mimeType)
+    {
+        m_mimeType = mimeType;
+    }
+
+    //
+    // ContentHandler interface method.
+    //
+
+    public synchronized Object getContent(URLConnection urlc) throws IOException
+    {
+        ContentHandler svc = getContentHandlerService();
+        if (svc == null)
+        {
+            throw new IOException("Content handler unavailable: "
+                + urlc.getContentType());
+        }
+        return svc.getContent(urlc);
+    }
+
+    /**
+     * <p>
+     * Private method to retrieve the content handler service from the
+     * framework instance associated with the current call stack. A
+     * simple service tracker is created and cached for the associated
+     * framework instance when this method is called.
+     * </p>
+     * @return the content handler service from the framework instance
+     *         associated with the current call stack or <tt>null</tt>
+     *         is no service is available.
+    **/
+    private ContentHandler getContentHandlerService()
+    {
+        // Get the framework instance associated with call stack.
+        Felix framework = URLHandlers.getFrameworkFromContext();
+
+        // If the framework has disabled the URL Handlers service,
+        // then it will not be found so just return null.
+        if (framework == null)
+        {
+            return null;
+        }
+
+        // Get the service tracker for the framework instance or create one.
+        URLHandlersServiceTracker tracker =
+            (URLHandlersServiceTracker) m_trackerMap.get(framework);
+        if (tracker == null)
+        {
+            // Get the framework's system bundle context.
+            BundleContext context =
+                ((SystemBundleActivator)
+                    ((SystemBundle) framework.getBundle(0)).getActivator())
+                        .getBundleContext();
+            // Create a filter for the mime type.
+            String filter = 
+                "(&(objectClass="
+                + ContentHandler.class.getName()
+                + ")("
+                + URLConstants.URL_CONTENT_MIMETYPE
+                + "="
+                + m_mimeType
+                + "))";
+            // Create a simple service tracker for the framework.
+            tracker = new URLHandlersServiceTracker(context, filter);
+            // Cache the simple service tracker.
+            m_trackerMap.put(framework, tracker);
+        }
+        return (ContentHandler) tracker.getService();
+    }
+}
\ No newline at end of file
diff --git a/framework/src/org/apache/felix/framework/URLHandlersServiceTracker.java b/framework/src/org/apache/felix/framework/URLHandlersServiceTracker.java
new file mode 100644
index 0000000..13e7861
--- /dev/null
+++ b/framework/src/org/apache/felix/framework/URLHandlersServiceTracker.java
@@ -0,0 +1,172 @@
+/*
+ *   Copyright 2005 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 org.apache.felix.framework.util.FelixConstants;
+import org.osgi.framework.*;
+
+/**
+ * <p>
+ * This class implements a simple service tracker that maintains a
+ * service object reference to the "best" service available at any
+ * given time that matches the filter associated with the tracker.
+ * The best service is the one with the one with the highest ranking
+ * and lowest service identifier.
+ *</p>
+**/
+class URLHandlersServiceTracker
+{
+    private BundleContext m_context = null;
+    private String m_filter = null;
+    private ServiceReference m_ref = null;
+    private Object m_svcObj = null;
+    private long m_id = -1;
+    private int m_rank = -1;
+
+    /**
+     * <p>
+     * Creates a simple service tracker associated with the specified bundle
+     * context for services matching the specified filter.
+     * </p>
+     * @param context the bundle context used for tracking services.
+     * @param filter the filter used for matching services.
+    **/
+    public URLHandlersServiceTracker(BundleContext context, String filter)
+    {
+        m_context = context;
+        m_filter = filter;
+
+        synchronized (this)
+        {
+            // Add a service listener to track service changes
+            // for services matching the specified filter.
+            ServiceListener sl = new ServiceListener() {
+                public void serviceChanged(ServiceEvent event)
+                {
+                    ServiceReference eventRef = event.getServiceReference();
+                    if (event.getType() == ServiceEvent.REGISTERED)
+                    {
+                        synchronized (URLHandlersServiceTracker.this)
+                        {
+                            Long idObj = (Long) eventRef.getProperty(FelixConstants.SERVICE_ID);
+                            Integer rankObj = (Integer) eventRef.getProperty(FelixConstants.SERVICE_RANKING);
+                            int rank = (rankObj == null) ? 0 : rankObj.intValue();
+                            if ((rank > m_rank) ||
+                                ((rank == m_rank) && (idObj.longValue() < m_id)))
+                            {
+                                if (m_ref != null)
+                                {
+                                    m_context.ungetService(m_ref);
+                                }
+                                m_ref = eventRef;
+                                m_rank = rank;
+                                m_id = idObj.longValue();
+                                m_svcObj = m_context.getService(m_ref);
+                            }
+                        }
+                    }
+                    else if (event.getType() == ServiceEvent.UNREGISTERING)
+                    {
+                        synchronized (URLHandlersServiceTracker.this)
+                        {
+                            if (eventRef == m_ref)
+                            {
+                                selectBestService();
+                            }
+                        }
+                    }
+                }
+            };
+            try
+            {
+                m_context.addServiceListener(sl, m_filter);
+            }
+            catch (InvalidSyntaxException ex)
+            {
+                System.out.println("Cannot add service listener." + ex);
+            }
+
+            // Select the best service object.
+            selectBestService();
+
+        } // End of synchronized block.
+    }
+
+    public Object getService()
+    {
+        return m_svcObj;
+    }
+
+    /**
+     * <p>
+     * This method selects the highest ranking service object with the
+     * lowest service identifier out of the services selected by the
+     * service filter associated with this proxy. This method is called
+     * to initialize the proxy and any time when the service object
+     * being used is unregistered. If there is an existing service
+     * selected when this method is called, it will unget the existing
+     * service before selecting the best available service.
+     * </p>
+    **/
+    private void selectBestService()
+    {
+        // If there is an existing service, then unget it.
+        if (m_ref != null)
+        {
+            m_context.ungetService(m_ref);
+            m_ref = null;
+            m_svcObj = null;
+            m_id = -1;
+            m_rank = -1;
+        }
+
+        try
+        {
+            // Get all service references matching the service filter
+            // associated with this proxy.
+            ServiceReference[] refs = m_context.getServiceReferences(null, m_filter);
+            // Loop through all service references and select the reference
+            // with the highest ranking and lower service identifier.
+            for (int i = 0; (refs != null) && (i < refs.length); i++)
+            {
+                Long idObj = (Long) refs[i].getProperty(FelixConstants.SERVICE_ID);
+                Integer rankObj = (Integer) refs[i].getProperty(FelixConstants.SERVICE_RANKING);
+                // Ranking value defaults to zero.
+                int rank = (rankObj == null) ? 0 : rankObj.intValue();
+                if ((rank > m_rank) ||
+                    ((rank == m_rank) && (idObj.longValue() < m_id)))
+                {
+                    m_ref = refs[i];
+                    m_rank = rank;
+                    m_id = idObj.longValue();
+                }
+            }
+
+            // If a service reference was selected, then
+            // get its service object.
+            if (m_ref != null)
+            {
+                m_svcObj = m_context.getService(m_ref);
+            }
+        }
+        catch (InvalidSyntaxException ex)
+        {
+//TODO: LOGGER.
+            System.err.println("URLHandlersServiceTracker: " + ex);
+        }
+    }
+}
\ No newline at end of file
diff --git a/framework/src/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java b/framework/src/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java
new file mode 100644
index 0000000..ed2e8bb
--- /dev/null
+++ b/framework/src/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java
@@ -0,0 +1,223 @@
+/*
+ *   Copyright 2005 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.IOException;
+import java.net.*;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.url.*;
+
+/**
+ * <p>
+ * This class implements a stream handler proxy. When the stream handler
+ * proxy instance is created, it is associated with a particular protocol
+ * and will answer all future requests for handling of that stream type. It
+ * does not directly handle the stream handler requests, but delegates the
+ * requests to an underlying stream handler service.
+ * </p>
+ * <p>
+ * The proxy instance for a particular protocol is used for all framework
+ * instances that may contain their own stream handler services. When
+ * performing a stream handler operation, the proxy retrieves the handler
+ * service from the framework instance associated with the current call
+ * stack and delegates the call to the handler service.
+ * </p>
+ * <p>
+ * The proxy will create simple stream handler service trackers for each
+ * framework instance. The trackers will listen to service events in its
+ * respective framework instance to maintain a reference to the "best"
+ * stream handler service at any given time.
+ * </p>
+**/
+public class URLHandlersStreamHandlerProxy extends URLStreamHandler
+    implements URLStreamHandlerSetter
+{
+    private Map m_trackerMap = new HashMap();
+    private String m_protocol = null;
+
+    public URLHandlersStreamHandlerProxy(String protocol)
+    {
+        m_protocol = protocol;
+    }
+
+    //
+    // URLStreamHandler interface methods.
+    //
+
+    protected synchronized boolean equals(URL url1, URL url2)
+    {
+        URLStreamHandlerService svc = getStreamHandlerService();
+        if (svc == null)
+        {
+            throw new IllegalStateException(
+                "Unknown protocol: " + url1.getProtocol());
+        }
+        return svc.equals(url1, url2);
+    }
+
+    protected synchronized int getDefaultPort()
+    {
+        URLStreamHandlerService svc = getStreamHandlerService();
+        if (svc == null)
+        {
+            throw new IllegalStateException("Stream handler unavailable.");
+        }
+        return svc.getDefaultPort();
+    }
+
+    protected synchronized InetAddress getHostAddress(URL url)
+    {
+        URLStreamHandlerService svc = getStreamHandlerService();
+        if (svc == null)
+        {
+            throw new IllegalStateException(
+                "Unknown protocol: " + url.getProtocol());
+        }
+        return svc.getHostAddress(url);
+    }
+
+    protected synchronized int hashCode(URL url)
+    {
+        URLStreamHandlerService svc = getStreamHandlerService();
+        if (svc == null)
+        {
+            throw new IllegalStateException(
+                "Unknown protocol: " + url.getProtocol());
+        }
+        return svc.hashCode(url);
+    }
+
+    protected synchronized boolean hostsEqual(URL url1, URL url2)
+    {
+        URLStreamHandlerService svc = getStreamHandlerService();
+        if (svc == null)
+        {
+            throw new IllegalStateException(
+                "Unknown protocol: " + url1.getProtocol());
+        }
+        return svc.hostsEqual(url1, url2);
+    }
+
+    protected synchronized URLConnection openConnection(URL url) throws IOException
+    {
+        URLStreamHandlerService svc = getStreamHandlerService();
+        if (svc == null)
+        {
+            throw new MalformedURLException("Unknown protocol: " + url.toString());
+        }
+        return svc.openConnection(url);
+    }
+
+    protected synchronized void parseURL(URL url, String spec, int start, int limit)
+    {
+        URLStreamHandlerService svc = getStreamHandlerService();
+        if (svc == null)
+        {
+            throw new IllegalStateException(
+                "Unknown protocol: " + url.getProtocol());
+        }
+        svc.parseURL(this, url, spec, start, limit);
+    }
+
+    protected synchronized boolean sameFile(URL url1, URL url2)
+    {
+        URLStreamHandlerService svc = getStreamHandlerService();
+        if (svc == null)
+        {
+            throw new IllegalStateException(
+                "Unknown protocol: " + url1.getProtocol());
+        }
+        return svc.sameFile(url1, url2);
+    }
+
+    public void setURL(
+        URL url, String protocol, String host, int port, String authority,
+        String userInfo, String path, String query, String ref)
+    {
+        super.setURL(url, protocol, host, port, authority, userInfo, path, query, ref);
+    }
+
+    public void setURL(
+        URL url, String protocol, String host, int port, String file, String ref)
+    {
+        super.setURL(url, protocol, host, port, null, null, file, null, ref);
+    }
+
+    protected synchronized String toExternalForm(URL url)
+    {
+        URLStreamHandlerService svc = getStreamHandlerService();
+        if (svc == null)
+        {
+            throw new IllegalStateException(
+                "Unknown protocol: " + url.getProtocol());
+        }
+        return svc.toExternalForm(url);
+    }
+
+    /**
+     * <p>
+     * Private method to retrieve the stream handler service from the
+     * framework instance associated with the current call stack. A
+     * simple service tracker is created and cached for the associated
+     * framework instance when this method is called.
+     * </p>
+     * @return the stream handler service from the framework instance
+     *         associated with the current call stack or <tt>null</tt>
+     *         is no service is available.
+    **/
+    private URLStreamHandlerService getStreamHandlerService()
+    {
+        // Get the framework instance associated with call stack.
+        Felix framework = URLHandlers.getFrameworkFromContext();
+
+        // If the framework has disabled the URL Handlers service,
+        // then it will not be found so just return null.
+        if (framework == null)
+        {
+            return null;
+        }
+
+        // Get the service tracker for the framework instance or create one.
+        URLHandlersServiceTracker tracker =
+            (URLHandlersServiceTracker) m_trackerMap.get(framework);
+        if (tracker == null)
+        {
+            // Get the framework's system bundle context.
+            BundleContext context =
+                ((SystemBundleActivator)
+                    ((SystemBundle) framework.getBundle(0)).getActivator())
+                        .getBundleContext();
+            // Create a filter for the protocol.
+            String filter = 
+                "(&(objectClass="
+                + URLStreamHandlerService.class.getName()
+                + ")("
+                + URLConstants.URL_HANDLER_PROTOCOL
+                + "="
+                + m_protocol
+                + "))";
+            // Create a simple service tracker for the framework.
+            tracker = new URLHandlersServiceTracker(context, filter);
+            // Cache the simple service tracker.
+            m_trackerMap.put(framework, tracker);
+        }
+        return (URLStreamHandlerService) tracker.getService();
+    }
+}
\ No newline at end of file