You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by se...@apache.org on 2008/09/01 17:08:10 UTC

svn commit: r690991 [8/20] - in /cxf/sandbox/dosgi: ./ discovery/ discovery/local/ discovery/local/src/ discovery/local/src/main/ discovery/local/src/main/java/ discovery/local/src/main/java/org/ discovery/local/src/main/java/org/apache/ discovery/loca...

Added: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersContentHandlerProxy.java
URL: http://svn.apache.org/viewvc/cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersContentHandlerProxy.java?rev=690991&view=auto
==============================================================================
--- cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersContentHandlerProxy.java (added)
+++ cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersContentHandlerProxy.java Mon Sep  1 08:08:01 2008
@@ -0,0 +1,136 @@
+/* 
+ * 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
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.felix.framework.util.SecureAction;
+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 final Map m_trackerMap = new HashMap();
+    private final String m_mimeType;
+    private final SecureAction m_action;
+
+    public URLHandlersContentHandlerProxy(String mimeType, SecureAction action)
+    {
+        m_mimeType = mimeType;
+        m_action = action;
+    }
+
+    //
+    // ContentHandler interface method.
+    //
+
+    public synchronized Object getContent(URLConnection urlc) throws IOException
+    {
+        ContentHandler svc = getContentHandlerService();
+        if (svc == null)
+        {
+            return urlc.getInputStream();
+        }
+        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.
+        Object 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.
+        Object tracker = m_trackerMap.get(framework);
+        try
+        {
+            if (tracker == null)
+            {
+                // 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 = m_action.invoke(m_action.getConstructor(
+                    framework.getClass().getClassLoader().loadClass(
+                    URLHandlersServiceTracker.class.getName()),
+                    new Class[]{framework.getClass(), String.class}), 
+                    new Object[]{framework, filter});
+                // Cache the simple service tracker.
+                m_trackerMap.put(framework, tracker);
+            }
+            return (ContentHandler) m_action.invoke(
+                m_action.getMethod(tracker.getClass(), "getService", null), 
+                tracker, null);
+        }
+        catch (Exception ex)
+        {
+            // TODO: log this or something
+            ex.printStackTrace();
+            return null;
+        }
+    }
+}
\ No newline at end of file

Propchange: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersContentHandlerProxy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersContentHandlerProxy.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersServiceTracker.java
URL: http://svn.apache.org/viewvc/cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersServiceTracker.java?rev=690991&view=auto
==============================================================================
--- cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersServiceTracker.java (added)
+++ cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersServiceTracker.java Mon Sep  1 08:08:01 2008
@@ -0,0 +1,176 @@
+/* 
+ * 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
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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(Felix framework, String filter)
+    {
+        m_context = ((FelixBundle) 
+            framework.getBundle(0)).getInfo().getBundleContext();
+        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) ||
+                        (event.getType() == ServiceEvent.MODIFIED))
+                    {
+                        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

Propchange: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersServiceTracker.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersServiceTracker.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java
URL: http://svn.apache.org/viewvc/cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java?rev=690991&view=auto
==============================================================================
--- cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java (added)
+++ cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java Mon Sep  1 08:08:01 2008
@@ -0,0 +1,285 @@
+/* 
+ * 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
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.*;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.felix.framework.util.SecureAction;
+import org.osgi.service.url.URLConstants;
+import org.osgi.service.url.URLStreamHandlerService;
+import org.osgi.service.url.URLStreamHandlerSetter;
+
+/**
+ * <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, InvocationHandler
+{
+    private final Map m_trackerMap = new HashMap();
+    private final String m_protocol;
+    private final Object m_service;
+    private final SecureAction m_action;
+
+    public URLHandlersStreamHandlerProxy(String protocol, SecureAction action)
+    {
+        m_protocol = protocol;
+        m_service = null;
+        m_action = action;
+    }
+    
+    private URLHandlersStreamHandlerProxy(Object service, SecureAction action)
+    {
+        m_protocol = null;
+        m_service = service;
+        m_action = action;
+    }
+
+    //
+    // 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.
+        Object 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.
+        Object tracker = m_trackerMap.get(framework);
+        try
+        {
+            if (tracker == null)
+            {
+                // 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 = m_action.invoke(m_action.getConstructor(
+                    framework.getClass().getClassLoader().loadClass(
+                    URLHandlersServiceTracker.class.getName()), 
+                    new Class[]{framework.getClass(), String.class}), 
+                    new Object[]{framework, filter});
+
+                // Cache the simple service tracker.
+                m_trackerMap.put(framework, tracker);
+            }
+            Object service = m_action.invoke(m_action.getMethod(
+                tracker.getClass(), "getService", null), tracker, null);
+            if (service == null)
+            {
+                return null;
+            }
+            if (service instanceof URLStreamHandlerService)
+            {
+                return (URLStreamHandlerService) service;
+            }
+            return (URLStreamHandlerService) Proxy.newProxyInstance(
+                URLStreamHandlerService.class.getClassLoader(), 
+                new Class[]{URLStreamHandlerService.class}, 
+                new URLHandlersStreamHandlerProxy(service, m_action));
+        }
+        catch (Exception ex)
+        {
+            // TODO: log this or something
+            ex.printStackTrace();
+            return null;
+        }
+    }
+
+    public Object invoke(Object obj, Method method, Object[] params)
+        throws Throwable
+    {
+        try
+        {
+            Class[] types = method.getParameterTypes();
+            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]}, 
+                    (URLHandlersStreamHandlerProxy) params[0]);
+            }
+            return m_action.invoke(m_action.getMethod(m_service.getClass(), 
+                method.getName(), types), m_service, params);
+        } 
+        catch (Exception ex)
+        {
+            throw ex;
+        }
+    }
+}
\ No newline at end of file

Propchange: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/URLHandlersStreamHandlerProxy.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
URL: http://svn.apache.org/viewvc/cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java?rev=690991&view=auto
==============================================================================
--- cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java (added)
+++ cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java Mon Sep  1 08:08:01 2008
@@ -0,0 +1,1187 @@
+/*
+ * 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
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.cache;
+
+import java.io.*;
+
+import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.util.ObjectInputStreamX;
+import org.apache.felix.moduleloader.IModule;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+
+/**
+ * <p>
+ * This class is a logical abstraction for a bundle archive. This class,
+ * combined with <tt>BundleCache</tt> and concrete <tt>BundleRevision</tt>
+ * subclasses, implement the bundle cache for Felix. The bundle archive
+ * abstracts the actual bundle content into revisions and the revisions
+ * provide access to the actual bundle content. When a bundle is
+ * installed it has one revision associated with its content. Updating a
+ * bundle adds another revision for the updated content. Any number of
+ * revisions can be associated with a bundle archive. When the bundle
+ * (or framework) is refreshed, then all old revisions are purged and only
+ * the most recent revision is maintained.
+ * </p>
+ * <p>
+ * The content associated with a revision can come in many forms, such as
+ * a standard JAR file or an exploded bundle directory. The bundle archive
+ * is responsible for creating all revision instances during invocations
+ * of the <tt>revise()</tt> method call. Internally, it determines the
+ * concrete type of revision type by examining the location string as an
+ * URL. Currently, it supports standard JAR files, referenced JAR files,
+ * and referenced directories. Examples of each type of URL are, respectively:
+ * </p>
+ * <ul>
+ *   <li><tt>http://www.foo.com/bundle.jar</tt></li>
+ *   <li><tt>reference:file:/foo/bundle.jar</tt></li>
+ *   <li><tt>reference:file:/foo/bundle/</tt></li>
+ * </ul>
+ * <p>
+ * The "<tt>reference:</tt>" notation signifies that the resource should be
+ * used "in place", meaning that they will not be copied. For referenced JAR
+ * files, some resources may still be copied, such as embedded JAR files or
+ * native libraries, but for referenced exploded bundle directories, nothing
+ * will be copied. Currently, reference URLs can only refer to "file:" targets.
+ * </p>
+ * @see org.apache.felix.framework.cache.BundleCache
+ * @see org.apache.felix.framework.cache.BundleRevision
+**/
+public class BundleArchive
+{
+    public static final transient String FILE_PROTOCOL = "file:";
+    public static final transient String REFERENCE_PROTOCOL = "reference:";
+    public static final transient String INPUTSTREAM_PROTOCOL = "inputstream:";
+
+    private static final transient String BUNDLE_ID_FILE = "bundle.id";
+    private static final transient String BUNDLE_LOCATION_FILE = "bundle.location";
+    private static final transient String CURRENT_LOCATION_FILE = "current.location";
+    private static final transient String REVISION_LOCATION_FILE = "revision.location";
+    private static final transient String BUNDLE_STATE_FILE = "bundle.state";
+    private static final transient String BUNDLE_START_LEVEL_FILE = "bundle.startlevel";
+    private static final transient String REFRESH_COUNTER_FILE = "refresh.counter";
+    private static final transient String BUNDLE_ACTIVATOR_FILE = "bundle.activator";
+    private static final transient String BUNDLE_LASTMODIFIED_FILE = "bundle.lastmodified";
+    private static final transient String REVISION_DIRECTORY = "version";
+    private static final transient String DATA_DIRECTORY = "data";
+    private static final transient String ACTIVE_STATE = "active";
+    private static final transient String INSTALLED_STATE = "installed";
+    private static final transient String UNINSTALLED_STATE = "uninstalled";
+
+    private Logger m_logger = null;
+    private long m_id = -1;
+    private File m_archiveRootDir = null;
+    private String m_originalLocation = null;
+    private String m_currentLocation = null;
+    private int m_persistentState = -1;
+    private int m_startLevel = -1;
+    private long m_lastModified = -1;
+    private BundleRevision[] m_revisions = null;
+
+    private long m_refreshCount = -1;
+
+    /**
+     * <p>
+     * This constructor is only used by the system bundle archive implementation
+     * because it is special an is not really an archive.
+     * </p>
+    **/
+    public BundleArchive()
+    {
+    }
+
+    /**
+     * <p>
+     * This constructor is used for creating new archives when a bundle is
+     * installed into the framework. Each archive receives a logger, a root
+     * directory, its associated bundle identifier, the associated bundle
+     * location string, and an input stream from which to read the bundle
+     * content. The root directory is where any required state can be
+     * stored. The input stream may be null, in which case the location is
+     * used as an URL to the bundle content.
+     * </p>
+     * @param logger the logger to be used by the archive.
+     * @param archiveRootDir the archive root directory for storing state.
+     * @param id the bundle identifier associated with the archive.
+     * @param location the bundle location string associated with the archive.
+     * @param is input stream from which to read the bundle content.
+     * @throws Exception if any error occurs.
+    **/
+    public BundleArchive(Logger logger, File archiveRootDir, long id,
+        String location, InputStream is) throws Exception
+    {
+        m_logger = logger;
+        m_archiveRootDir = archiveRootDir;
+        m_id = id;
+        if (m_id <= 0)
+        {
+            throw new IllegalArgumentException(
+                "Bundle ID cannot be less than or equal to zero.");
+        }
+        m_originalLocation = location;
+
+        // Save state.
+        initialize();
+
+        // Add a revision for the content.
+        revise(m_originalLocation, is);
+    }
+
+    /**
+     * <p>
+     * This constructor is called when an archive for a bundle is being
+     * reconstructed when the framework is restarted. Each archive receives
+     * a logger, a root directory, and its associated bundle identifier.
+     * The root directory is where any required state can be stored.
+     * </p>
+     * @param logger the logger to be used by the archive.
+     * @param archiveRootDir the archive root directory for storing state.
+     * @param id the bundle identifier associated with the archive.
+     * @throws Exception if any error occurs.
+    **/
+    public BundleArchive(Logger logger, File archiveRootDir)
+        throws Exception
+    {
+        m_logger = logger;
+        m_archiveRootDir = archiveRootDir;
+
+        // Add a revision for each one that already exists in the file
+        // system. The file system might contain more than one revision
+        // if the bundle was updated in a previous session, but the
+        // framework was not refreshed; this might happen if the framework
+        // did not exit cleanly. We must create the existing revisions so
+        // that they can be properly purged.
+        int revisionCount = 0;
+        while (true)
+        {
+            // Count the number of existing revision directories, which
+            // will be in a directory named like:
+            //     "${REVISION_DIRECTORY)${refresh-count}.${revision-count}"
+            File revisionRootDir = new File(m_archiveRootDir,
+                REVISION_DIRECTORY + getRefreshCount() + "." + revisionCount);
+            if (!BundleCache.getSecureAction().fileExists(revisionRootDir))
+            {
+                break;
+            }
+
+            // Increment the revision count.
+            revisionCount++;
+        }
+
+        // If there are multiple revisions in the file system, then create
+        // an array that is big enough to hold all revisions minus one; the
+        // call below to revise() will add the most recent revision. NOTE: We
+        // do not actually need to add a real revision object for the older
+        // revisions since they will be purged immediately on framework startup.
+        if (revisionCount > 1)
+        {
+            m_revisions = new BundleRevision[revisionCount - 1];
+        }
+
+        // Add the revision object for the most recent revision. We first try
+        // to read the location from the current revision - if that fails we
+        // likely have an old bundle cache and read the location the old way.
+        // The next revision will update the bundle cache.
+        revise(getRevisionLocation(revisionCount - 1), null);
+    }
+
+    /**
+     * <p>
+     * Returns the bundle identifier associated with this archive.
+     * </p>
+     * @return the bundle identifier associated with this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized long getId() throws Exception
+    {
+        if (m_id > 0)
+        {
+            return m_id;
+        }
+
+        // Read bundle location.
+        InputStream is = null;
+        BufferedReader br = null;
+        try
+        {
+            is = BundleCache.getSecureAction()
+                .getFileInputStream(new File(m_archiveRootDir, BUNDLE_ID_FILE));
+            br = new BufferedReader(new InputStreamReader(is));
+            m_id = Long.parseLong(br.readLine());
+        }
+        catch (FileNotFoundException ex)
+        {
+            // HACK: Get the bundle identifier from the archive root directory
+            // name, which is of the form "bundle<id>" where <id> is the bundle
+            // identifier numbers. This is a hack to deal with old archives that
+            // did not save their bundle identifier, but instead had it passed
+            // into them. Eventually, this can be removed.
+            m_id = Long.parseLong(
+                m_archiveRootDir.getName().substring(
+                    BundleCache.BUNDLE_DIR_PREFIX.length()));
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+
+        return m_id;
+    }
+
+    /**
+     * <p>
+     * Returns the location string associated with this archive.
+     * </p>
+     * @return the location string associated with this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized String getLocation() throws Exception
+    {
+        if (m_originalLocation != null)
+        {
+            return m_originalLocation;
+        }
+
+        // Read bundle location.
+        InputStream is = null;
+        BufferedReader br = null;
+        try
+        {
+            is = BundleCache.getSecureAction()
+                .getFileInputStream(new File(m_archiveRootDir, BUNDLE_LOCATION_FILE));
+            br = new BufferedReader(new InputStreamReader(is));
+            m_originalLocation = br.readLine();
+            return m_originalLocation;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the persistent state of this archive. The value returned is
+     * one of the following: <tt>Bundle.INSTALLED</tt>, <tt>Bundle.ACTIVE</tt>,
+     * or <tt>Bundle.UNINSTALLED</tt>.
+     * </p>
+     * @return the persistent state of this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized int getPersistentState() throws Exception
+    {
+        if (m_persistentState >= 0)
+        {
+            return m_persistentState;
+        }
+
+        // Get bundle state file.
+        File stateFile = new File(m_archiveRootDir, BUNDLE_STATE_FILE);
+
+        // If the state file doesn't exist, then
+        // assume the bundle was installed.
+        if (!BundleCache.getSecureAction().fileExists(stateFile))
+        {
+            return Bundle.INSTALLED;
+        }
+
+        // Read the bundle state.
+        InputStream is = null;
+        BufferedReader br = null;
+        try
+        {
+            is = BundleCache.getSecureAction()
+                .getFileInputStream(stateFile);
+            br = new BufferedReader(new InputStreamReader(is));
+            String s = br.readLine();
+            if (s.equals(ACTIVE_STATE))
+            {
+                m_persistentState = Bundle.ACTIVE;
+            }
+            else if (s.equals(UNINSTALLED_STATE))
+            {
+                m_persistentState = Bundle.UNINSTALLED;
+            }
+            else
+            {
+                m_persistentState = Bundle.INSTALLED;
+            }
+            return m_persistentState;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Sets the persistent state of this archive. The value is
+     * one of the following: <tt>Bundle.INSTALLED</tt>, <tt>Bundle.ACTIVE</tt>,
+     * or <tt>Bundle.UNINSTALLED</tt>.
+     * </p>
+     * @param state the persistent state value to set for this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized void setPersistentState(int state) throws Exception
+    {
+        // Write the bundle state.
+        OutputStream os = null;
+        BufferedWriter bw= null;
+        try
+        {
+            os = BundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_STATE_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            String s = null;
+            switch (state)
+            {
+                case Bundle.ACTIVE:
+                    s = ACTIVE_STATE;
+                    break;
+                case Bundle.UNINSTALLED:
+                    s = UNINSTALLED_STATE;
+                    break;
+                default:
+                    s = INSTALLED_STATE;
+                    break;
+            }
+            bw.write(s, 0, s.length());
+            m_persistentState = state;
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Unable to record state - " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the start level of this archive.
+     * </p>
+     * @return the start level of this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized int getStartLevel() throws Exception
+    {
+        if (m_startLevel >= 0)
+        {
+            return m_startLevel;
+        }
+
+        // Get bundle start level file.
+        File levelFile = new File(m_archiveRootDir, BUNDLE_START_LEVEL_FILE);
+
+        // If the start level file doesn't exist, then
+        // return an error.
+        if (!BundleCache.getSecureAction().fileExists(levelFile))
+        {
+            return -1;
+        }
+
+        // Read the bundle start level.
+        InputStream is = null;
+        BufferedReader br= null;
+        try
+        {
+            is = BundleCache.getSecureAction()
+                .getFileInputStream(levelFile);
+            br = new BufferedReader(new InputStreamReader(is));
+            m_startLevel = Integer.parseInt(br.readLine());
+            return m_startLevel;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Sets the the start level of this archive this archive.
+     * </p>
+     * @param level the start level to set for this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized void setStartLevel(int level) throws Exception
+    {
+        // Write the bundle start level.
+        OutputStream os = null;
+        BufferedWriter bw = null;
+        try
+        {
+            os = BundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_START_LEVEL_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            String s = Integer.toString(level);
+            bw.write(s, 0, s.length());
+            m_startLevel = level;
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Unable to record start level - " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the last modification time of this archive.
+     * </p>
+     * @return the last modification time of this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized long getLastModified() throws Exception
+    {
+        if (m_lastModified >= 0)
+        {
+            return m_lastModified;
+        }
+
+        // Get bundle last modification time file.
+        File lastModFile = new File(m_archiveRootDir, BUNDLE_LASTMODIFIED_FILE);
+
+        // If the last modification file doesn't exist, then
+        // return an error.
+        if (!BundleCache.getSecureAction().fileExists(lastModFile))
+        {
+            return 0;
+        }
+
+        // Read the bundle start level.
+        InputStream is = null;
+        BufferedReader br= null;
+        try
+        {
+            is = BundleCache.getSecureAction().getFileInputStream(lastModFile);
+            br = new BufferedReader(new InputStreamReader(is));
+            m_lastModified = Long.parseLong(br.readLine());
+            return m_lastModified;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Sets the the last modification time of this archive.
+     * </p>
+     * @param lastModified The time of the last modification to set for
+     *      this archive. According to the OSGi specification this time is
+     *      set each time a bundle is installed, updated or uninstalled.
+     *
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized void setLastModified(long lastModified) throws Exception
+    {
+        // Write the bundle last modification time.
+        OutputStream os = null;
+        BufferedWriter bw = null;
+        try
+        {
+            os = BundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_LASTMODIFIED_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            String s = Long.toString(lastModified);
+            bw.write(s, 0, s.length());
+            m_lastModified = lastModified;
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Unable to record last modification time - " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns a <tt>File</tt> object corresponding to the data file
+     * of the relative path of the specified string.
+     * </p>
+     * @return a <tt>File</tt> object corresponding to the specified file name.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized File getDataFile(String fileName) throws Exception
+    {
+        // Do some sanity checking.
+        if ((fileName.length() > 0) && (fileName.charAt(0) == File.separatorChar))
+            throw new IllegalArgumentException("The data file path must be relative, not absolute.");
+        else if (fileName.indexOf("..") >= 0)
+            throw new IllegalArgumentException("The data file path cannot contain a reference to the \"..\" directory.");
+
+        // Get bundle data directory.
+        File dataDir = new File(m_archiveRootDir, DATA_DIRECTORY);
+        // Create the data directory if necessary.
+        if (!BundleCache.getSecureAction().fileExists(dataDir))
+        {
+            if (!BundleCache.getSecureAction().mkdir(dataDir))
+            {
+                throw new IOException("Unable to create bundle data directory.");
+            }
+        }
+
+        // Return the data file.
+        return new File(dataDir, fileName);
+    }
+
+    /**
+     * <p>
+     * Returns the serialized activator for this archive. This is an
+     * extension to the OSGi specification.
+     * </p>
+     * @return the serialized activator for this archive.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized BundleActivator getActivator(IModule module)
+        throws Exception
+    {
+        // Get bundle activator file.
+        File activatorFile = new File(m_archiveRootDir, BUNDLE_ACTIVATOR_FILE);
+        // If the activator file doesn't exist, then
+        // assume there isn't one.
+        if (!BundleCache.getSecureAction().fileExists(activatorFile))
+        {
+            return null;
+        }
+
+        // Deserialize the activator object.
+        InputStream is = null;
+        ObjectInputStreamX ois = null;
+        try
+        {
+            is = BundleCache.getSecureAction()
+                .getFileInputStream(activatorFile);
+            ois = new ObjectInputStreamX(is, module);
+            Object o = ois.readObject();
+            return (BundleActivator) o;
+        }
+        catch (Exception ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Trying to deserialize - " + ex);
+        }
+        finally
+        {
+            if (ois != null) ois.close();
+            if (is != null) is.close();
+        }
+
+        return null;
+    }
+
+    /**
+     * <p>
+     * Serializes the activator for this archive.
+     * </p>
+     * @param obj the activator to serialize.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized void setActivator(Object obj) throws Exception
+    {
+        if (!(obj instanceof Serializable))
+        {
+            return;
+        }
+
+        // Serialize the activator object.
+        OutputStream os = null;
+        ObjectOutputStream oos = null;
+        try
+        {
+            os = BundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_ACTIVATOR_FILE));
+            oos = new ObjectOutputStream(os);
+            oos.writeObject(obj);
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Unable to serialize activator - " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (oos != null) oos.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the number of revisions available for this archive.
+     * </p>
+     * @return tthe number of revisions available for this archive.
+    **/
+    public synchronized int getRevisionCount()
+    {
+        return (m_revisions == null) ? 0 : m_revisions.length;
+    }
+
+    /**
+     * <p>
+     * Returns the revision object for the specified revision.
+     * </p>
+     * @return the revision object for the specified revision.
+    **/
+    public synchronized BundleRevision getRevision(int i)
+    {
+        if ((i >= 0) && (i < getRevisionCount()))
+        {
+            return m_revisions[i];
+        }
+        return null;
+    }
+
+    /**
+     * <p>
+     * This method adds a revision to the archive. The revision is created
+     * based on the specified location and/or input stream.
+     * </p>
+     * @param location the location string associated with the revision.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized void revise(String location, InputStream is)
+        throws Exception
+    {
+        // If we have an input stream, then we have to use it
+        // no matter what the update location is, so just ignore
+        // the update location and set the location to be input
+        // stream.
+        if (is != null)
+        {
+            location = "inputstream:";
+        }
+        BundleRevision revision = createRevisionFromLocation(location, is);
+        if (revision == null)
+        {
+            throw new Exception("Unable to revise archive.");
+        }
+
+        setRevisionLocation(location, (m_revisions == null) ? 0 : m_revisions.length);
+
+        // Add new revision to revision array.
+        if (m_revisions == null)
+        {
+            m_revisions = new BundleRevision[] { revision };
+        }
+        else
+        {
+            BundleRevision[] tmp = new BundleRevision[m_revisions.length + 1];
+            System.arraycopy(m_revisions, 0, tmp, 0, m_revisions.length);
+            tmp[m_revisions.length] = revision;
+            m_revisions = tmp;
+        }
+    }
+
+    /**
+     * <p>
+     * This method undoes the previous revision to the archive; this method will
+     * remove the latest revision from the archive. This method is only called
+     * when there are problems during an update after the revision has been
+     * created, such as errors in the update bundle's manifest. This method
+     * can only be called if there is more than one revision, otherwise there
+     * is nothing to undo.
+     * </p>
+     * @return true if the undo was a success false if there is no previous revision
+     * @throws Exception if any error occurs.
+     */
+    public synchronized boolean undoRevise() throws Exception
+    {
+        // Can only undo the revision if there is more than one.
+        if (getRevisionCount() <= 1)
+        {
+            return false;
+        }
+
+        String location = getRevisionLocation(m_revisions.length - 2);
+
+        try
+        {
+            m_revisions[m_revisions.length - 1].dispose();
+        }
+        catch(Exception ex)
+        {
+           m_logger.log(Logger.LOG_ERROR, getClass().getName() +
+               ": Unable to dispose latest revision", ex);
+        }
+
+        File revisionDir = new File(m_archiveRootDir, REVISION_DIRECTORY +
+            getRefreshCount() + "." + (m_revisions.length - 1));
+
+        if (BundleCache.getSecureAction().fileExists(revisionDir))
+        {
+            BundleCache.deleteDirectoryTree(revisionDir);
+        }
+
+        BundleRevision[] tmp = new BundleRevision[m_revisions.length - 1];
+        System.arraycopy(m_revisions, 0, tmp, 0, m_revisions.length - 1);
+
+        return true;
+    }
+
+    private synchronized String getRevisionLocation(int revision) throws Exception
+    {
+        InputStream is = null;
+        BufferedReader br = null;
+        try
+        {
+            is = BundleCache.getSecureAction().getFileInputStream(new File(
+                new File(m_archiveRootDir, REVISION_DIRECTORY +
+                getRefreshCount() + "." + revision), REVISION_LOCATION_FILE));
+
+            br = new BufferedReader(new InputStreamReader(is));
+            return br.readLine();
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    private synchronized void setRevisionLocation(String location, int revision) throws Exception
+    {
+        // Save current revision location.
+        OutputStream os = null;
+        BufferedWriter bw = null;
+        try
+        {
+            os = BundleCache.getSecureAction()
+                .getFileOutputStream(new File(
+                    new File(m_archiveRootDir, REVISION_DIRECTORY +
+                    getRefreshCount() + "." + revision), REVISION_LOCATION_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            bw.write(location, 0, location.length());
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * This method removes all old revisions associated with the archive
+     * and keeps only the current revision.
+     * </p>
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized void purge() throws Exception
+    {
+        // Get the current refresh count.
+        long refreshCount = getRefreshCount();
+        // Get the current revision count.
+        int count = getRevisionCount();
+
+        // Dispose and delete all but the current revision.
+        File revisionDir = null;
+        for (int i = 0; i < count - 1; i++)
+        {
+            // Dispose of the revision, but this might be null in certain
+            // circumstances, such as if this bundle archive was created
+            // for an existing bundle that was updated, but not refreshed
+            // due to a system crash; see the constructor code for details.
+            if (m_revisions[i] != null)
+            {
+                m_revisions[i].dispose();
+            }
+            revisionDir = new File(m_archiveRootDir, REVISION_DIRECTORY + refreshCount + "." + i);
+            if (BundleCache.getSecureAction().fileExists(revisionDir))
+            {
+                BundleCache.deleteDirectoryTree(revisionDir);
+            }
+        }
+
+        // We still need to dispose the current revision, but we
+        // don't want to delete it, because we want to rename it
+        // to the new refresh level.
+        m_revisions[count - 1].dispose();
+
+        // Save the current revision location for use later when
+        // we recreate the revision.
+        String location = getRevisionLocation(count -1);
+
+        // Increment the refresh count.
+        setRefreshCount(refreshCount + 1);
+
+        // Rename the current revision directory to be the zero revision
+        // of the new refresh level.
+        File currentDir = new File(m_archiveRootDir, REVISION_DIRECTORY + (refreshCount + 1) + ".0");
+        revisionDir = new File(m_archiveRootDir, REVISION_DIRECTORY + refreshCount + "." + (count - 1));
+        BundleCache.getSecureAction().renameFile(revisionDir, currentDir);
+
+        // Null the revision array since they are all invalid now.
+        m_revisions = null;
+        // Finally, recreate the revision for the current location.
+        BundleRevision revision = createRevisionFromLocation(location, null);
+        // Create new revision array.
+        m_revisions = new BundleRevision[] { revision };
+    }
+
+    /**
+     * <p>
+     * This method disposes removes the bundle archive directory.
+     * </p>
+     * @throws Exception if any error occurs.
+    **/
+    /* package */ void dispose() throws Exception
+    {
+        if (!BundleCache.deleteDirectoryTree(m_archiveRootDir))
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName()
+                    + ": Unable to delete archive directory - "
+                    + m_archiveRootDir);
+        }
+    }
+
+    /**
+     * <p>
+     * Initializes the bundle archive object by creating the archive
+     * root directory and saving the initial state.
+     * </p>
+     * @throws Exception if any error occurs.
+    **/
+    private void initialize() throws Exception
+    {
+        OutputStream os = null;
+        BufferedWriter bw = null;
+
+        try
+        {
+            // If the archive directory exists, then we don't
+            // need to initialize since it has already been done.
+            if (BundleCache.getSecureAction().fileExists(m_archiveRootDir))
+            {
+                return;
+            }
+
+            // Create archive directory, if it does not exist.
+            if (!BundleCache.getSecureAction().mkdir(m_archiveRootDir))
+            {
+                m_logger.log(
+                    Logger.LOG_ERROR,
+                    getClass().getName() + ": Unable to create archive directory.");
+                throw new IOException("Unable to create archive directory.");
+            }
+
+            // Save id.
+            os = BundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_ID_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            bw.write(Long.toString(m_id), 0, Long.toString(m_id).length());
+            bw.close();
+            os.close();
+
+            // Save location string.
+            os = BundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, BUNDLE_LOCATION_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            bw.write(m_originalLocation, 0, m_originalLocation.length());
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Returns the current location associated with the bundle archive,
+     * which is the last location from which the bundle was updated. It is
+     * necessary to keep track of this so it is possible to determine what
+     * kind of revision needs to be created when recreating revisions when
+     * the framework restarts.
+     * </p>
+     * @return the last update location.
+     * @throws Exception if any error occurs.
+    **/
+    private String getCurrentLocation() throws Exception
+    {
+        if (m_currentLocation != null)
+        {
+            return m_currentLocation;
+        }
+
+        // Read current location.
+        InputStream is = null;
+        BufferedReader br = null;
+        try
+        {
+            is = BundleCache.getSecureAction()
+                .getFileInputStream(new File(m_archiveRootDir, CURRENT_LOCATION_FILE));
+            br = new BufferedReader(new InputStreamReader(is));
+            m_currentLocation = br.readLine();
+            return m_currentLocation;
+        }
+        catch (FileNotFoundException ex)
+        {
+            return getLocation();
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Set the current location associated with the bundle archive,
+     * which is the last location from which the bundle was updated. It is
+     * necessary to keep track of this so it is possible to determine what
+     * kind of revision needs to be created when recreating revisions when
+     * the framework restarts.
+     * </p>
+     * @throws Exception if any error occurs.
+    **/
+    private void setCurrentLocation(String location) throws Exception
+    {
+        // Save current location.
+        OutputStream os = null;
+        BufferedWriter bw = null;
+        try
+        {
+            os = BundleCache.getSecureAction()
+                .getFileOutputStream(new File(m_archiveRootDir, CURRENT_LOCATION_FILE));
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            bw.write(location, 0, location.length());
+            m_currentLocation = location;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+
+    /**
+     * <p>
+     * Creates a revision based on the location string and/or input stream.
+     * </p>
+     * @return the location string associated with this archive.
+    **/
+    private BundleRevision createRevisionFromLocation(String location, InputStream is)
+        throws Exception
+    {
+        // The revision directory is named using the refresh count and
+        // the revision count. The revision count is obvious, but the
+        // refresh count is less obvious. This is necessary due to how
+        // native libraries are handled in Java; needless to say, every
+        // time a bundle is refreshed we must change the name of its
+        // native libraries so that we can reload them. Thus, we use the
+        // refresh counter as a way to change the name of the revision
+        // directory to give native libraries new absolute names.
+        File revisionRootDir = new File(m_archiveRootDir,
+            REVISION_DIRECTORY + getRefreshCount() + "." + getRevisionCount());
+
+        BundleRevision result = null;
+
+        try
+        {
+            // Check if the location string represents a reference URL.
+            if ((location != null) && location.startsWith(REFERENCE_PROTOCOL))
+            {
+                // Reference URLs only support the file protocol.
+                location = location.substring(REFERENCE_PROTOCOL.length());
+                if (!location.startsWith(FILE_PROTOCOL))
+                {
+                    throw new IOException("Reference URLs can only be files: " + location);
+                }
+
+                // Make sure the referenced file exists.
+                File file = new File(location.substring(FILE_PROTOCOL.length()));
+                if (!BundleCache.getSecureAction().fileExists(file))
+                {
+                    throw new IOException("Referenced file does not exist: " + file);
+                }
+
+                // If the referenced file is a directory, then create a directory
+                // revision; otherwise, create a JAR revision with the reference
+                // flag set to true.
+                if (BundleCache.getSecureAction().isFileDirectory(file))
+                {
+                    result = new DirectoryRevision(m_logger, revisionRootDir, location);
+                }
+                else
+                {
+                    result = new JarRevision(m_logger, revisionRootDir, location, true);
+                }
+            }
+            else if (location.startsWith(INPUTSTREAM_PROTOCOL))
+            {
+                // Assume all input streams point to JAR files.
+                result = new JarRevision(m_logger, revisionRootDir, location, false, is);
+            }
+            else
+            {
+                // Anything else is assumed to be a URL to a JAR file.
+                result = new JarRevision(m_logger, revisionRootDir, location, false);
+            }
+        }
+        catch (Exception ex)
+        {
+            if (BundleCache.getSecureAction().fileExists(revisionRootDir))
+            {
+                if (!BundleCache.deleteDirectoryTree(revisionRootDir))
+                {
+                    m_logger.log(
+                        Logger.LOG_ERROR,
+                        getClass().getName()
+                            + ": Unable to delete revision directory - "
+                            + revisionRootDir);
+                }
+            }
+            throw ex;
+        }
+
+        return result;
+    }
+
+    /**
+     * This utility method is used to retrieve the current refresh
+     * counter value for the bundle. This value is used when generating
+     * the bundle revision directory name where native libraries are extracted.
+     * This is necessary because Sun's JVM requires a one-to-one mapping
+     * between native libraries and class loaders where the native library
+     * is uniquely identified by its absolute path in the file system. This
+     * constraint creates a problem when a bundle is refreshed, because it
+     * gets a new class loader. Using the refresh counter to generate the name
+     * of the bundle revision directory resolves this problem because each time
+     * bundle is refresh, the native library will have a unique name.
+     * As a result of the unique name, the JVM will then reload the
+     * native library without a problem.
+    **/
+    private long getRefreshCount() throws Exception
+    {
+        // If we have already read the refresh counter file,
+        // then just return the result.
+        if (m_refreshCount >= 0)
+        {
+            return m_refreshCount;
+        }
+
+        // Get refresh counter file.
+        File counterFile = new File(m_archiveRootDir, REFRESH_COUNTER_FILE);
+
+        // If the refresh counter file doesn't exist, then
+        // assume the counter is at zero.
+        if (!BundleCache.getSecureAction().fileExists(counterFile))
+        {
+            return 0;
+        }
+
+        // Read the bundle refresh counter.
+        InputStream is = null;
+        BufferedReader br = null;
+        try
+        {
+            is = BundleCache.getSecureAction()
+                .getFileInputStream(counterFile);
+            br = new BufferedReader(new InputStreamReader(is));
+            long counter = Long.parseLong(br.readLine());
+            return counter;
+        }
+        finally
+        {
+            if (br != null) br.close();
+            if (is != null) is.close();
+        }
+    }
+
+    /**
+     * This utility method is used to retrieve the current refresh
+     * counter value for the bundle. This value is used when generating
+     * the bundle revision directory name where native libraries are extracted.
+     * This is necessary because Sun's JVM requires a one-to-one mapping
+     * between native libraries and class loaders where the native library
+     * is uniquely identified by its absolute path in the file system. This
+     * constraint creates a problem when a bundle is refreshed, because it
+     * gets a new class loader. Using the refresh counter to generate the name
+     * of the bundle revision directory resolves this problem because each time
+     * bundle is refresh, the native library will have a unique name.
+     * As a result of the unique name, the JVM will then reload the
+     * native library without a problem.
+    **/
+    private void setRefreshCount(long counter)
+        throws Exception
+    {
+        // Get refresh counter file.
+        File counterFile = new File(m_archiveRootDir, REFRESH_COUNTER_FILE);
+
+        // Write the refresh counter.
+        OutputStream os = null;
+        BufferedWriter bw = null;
+        try
+        {
+            os = BundleCache.getSecureAction()
+                .getFileOutputStream(counterFile);
+            bw = new BufferedWriter(new OutputStreamWriter(os));
+            String s = Long.toString(counter);
+            bw.write(s, 0, s.length());
+            m_refreshCount = counter;
+        }
+        catch (IOException ex)
+        {
+            m_logger.log(
+                Logger.LOG_ERROR,
+                getClass().getName() + ": Unable to write refresh counter: " + ex);
+            throw ex;
+        }
+        finally
+        {
+            if (bw != null) bw.close();
+            if (os != null) os.close();
+        }
+    }
+}

Propchange: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleArchive.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
URL: http://svn.apache.org/viewvc/cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java?rev=690991&view=auto
==============================================================================
--- cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java (added)
+++ cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java Mon Sep  1 08:08:01 2008
@@ -0,0 +1,385 @@
+/*
+ * 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
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.cache;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.felix.framework.Logger;
+import org.apache.felix.framework.util.SecureAction;
+
+/**
+ * <p>
+ * This class, combined with <tt>BundleArchive</tt>, and concrete
+ * <tt>BundleRevision</tt> subclasses, implement the Felix bundle cache.
+ * It is possible to configure the default behavior of this class by
+ * passing properties into Felix' constructor. The configuration properties
+ * for this class are:
+ * </p>
+ * <ul>
+ *   <li><tt>felix.cache.bufsize</tt> - Sets the buffer size to be used by
+ *       the cache; the default value is 4096. The integer
+ *       value of this string provides control over the size of the
+ *       internal buffer of the disk cache for performance reasons.
+ *   </li>
+ *   <li><tt>felix.cache.dir</tt> - Sets the directory to be used by the
+ *       cache as its cache directory. The cache directory is where all
+ *       profile directories are stored and a profile directory is where a
+ *       set of installed bundles are stored. By default, the cache
+ *       directory is <tt>.felix</tt> in the user's home directory. If
+ *       this property is specified, then its value will be used as the cache
+ *       directory instead of <tt>.felix</tt>. This directory will be created
+ *       if it does not exist.
+ *   </li>
+ *   <li><tt>felix.cache.profile</tt> - Sets the profile name that will be
+ *       used to create a profile directory inside of the cache directory.
+ *       The created directory will contained all installed bundles associated
+ *       with the profile.
+ *   </li>
+ *   <li><tt>felix.cache.profiledir</tt> - Sets the directory to use as the
+ *       profile directory for the bundle cache; by default the profile
+ *       name is used to create a directory in the <tt>.felix</tt> cache
+ *       directory. If this property is specified, then the cache directory
+ *       and profile name properties are ignored. The specified value of this
+ *       property is used directly as the directory to contain all cached
+ *       bundles. If this property is set, it is not necessary to set the
+ *       cache directory or profile name properties. This directory will be
+ *       created if it does not exist.
+ *   </li>
+ * </ul>
+ * <p>
+ * For specific information on how to configure Felix using system properties,
+ * refer to the Felix usage documentation.
+ * </p>
+ * @see org.apache.felix.framework.util.BundleArchive
+**/
+public class BundleCache
+{
+    public static final String CACHE_BUFSIZE_PROP = "felix.cache.bufsize";
+    public static final String CACHE_DIR_PROP = "felix.cache.dir";
+    public static final String CACHE_PROFILE_DIR_PROP = "felix.cache.profiledir";
+    public static final String CACHE_PROFILE_PROP = "felix.cache.profile";
+
+    protected static transient int BUFSIZE = 4096;
+    protected static transient final String CACHE_DIR_NAME = ".felix";
+    protected static transient final String BUNDLE_DIR_PREFIX = "bundle";
+
+    private Map m_configMap = null;
+    private Logger m_logger = null;
+    private File m_profileDir = null;
+    private BundleArchive[] m_archives = null;
+
+    private static SecureAction m_secureAction = new SecureAction();
+
+    public BundleCache(Logger logger, Map configMap)
+        throws Exception
+    {
+        m_configMap = configMap;
+        m_logger = logger;
+        initialize();
+    }
+
+    /* package */ static SecureAction getSecureAction()
+    {
+        return m_secureAction;
+    }
+
+    public synchronized BundleArchive[] getArchives()
+        throws Exception
+    {
+        return m_archives;
+    }
+
+    public synchronized BundleArchive getArchive(long id)
+        throws Exception
+    {
+        for (int i = 0; i < m_archives.length; i++)
+        {
+            if (m_archives[i].getId() == id)
+            {
+                return m_archives[i];
+            }
+        }
+        return null;
+    }
+
+    public synchronized int getArchiveIndex(BundleArchive ba)
+    {
+        for (int i = 0; i < m_archives.length; i++)
+        {
+            if (m_archives[i] == ba)
+            {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public synchronized BundleArchive create(
+        long id, String location, InputStream is)
+        throws Exception
+    {
+        // Construct archive root directory.
+        File archiveRootDir =
+            new File(m_profileDir, BUNDLE_DIR_PREFIX + Long.toString(id));
+
+        try
+        {
+            // Create the archive and add it to the list of archives.
+            BundleArchive ba =
+                new BundleArchive(m_logger, archiveRootDir, id, location, is);
+            BundleArchive[] tmp = new BundleArchive[m_archives.length + 1];
+            System.arraycopy(m_archives, 0, tmp, 0, m_archives.length);
+            tmp[m_archives.length] = ba;
+            m_archives = tmp;
+            return ba;
+        }
+        catch (Exception ex)
+        {
+            if (m_secureAction.fileExists(archiveRootDir))
+            {
+                if (!BundleCache.deleteDirectoryTree(archiveRootDir))
+                {
+                    m_logger.log(
+                        Logger.LOG_ERROR,
+                        getClass().getName()
+                            + ": Unable to delete the archive directory - "
+                            + archiveRootDir);
+                }
+            }
+            throw ex;
+        }
+    }
+
+    public synchronized void remove(BundleArchive ba)
+        throws Exception
+    {
+        if (ba != null)
+        {
+            // Remove the archive.
+            ba.dispose();
+            // Remove the archive from the cache.
+            int idx = getArchiveIndex(ba);
+            if (idx >= 0)
+            {
+                BundleArchive[] tmp =
+                    new BundleArchive[m_archives.length - 1];
+                System.arraycopy(m_archives, 0, tmp, 0, idx);
+                if (idx < tmp.length)
+                {
+                    System.arraycopy(m_archives, idx + 1, tmp, idx,
+                        tmp.length - idx);
+                }
+                m_archives = tmp;
+            }
+        }
+    }
+
+    /**
+     * Provides the system bundle access to its private storage area; this
+     * special case is necessary since the system bundle is not really a
+     * bundle and therefore must be treated in a special way.
+     * @param fileName the name of the file in the system bundle's private area.
+     * @return a <tt>File</tt> object corresponding to the specified file name.
+     * @throws Exception if any error occurs.
+    **/
+    public synchronized File getSystemBundleDataFile(String fileName) throws Exception
+    {
+        // Make sure system bundle directory exists.
+        File sbDir = new File(m_profileDir, BUNDLE_DIR_PREFIX + Long.toString(0));
+
+        // If the system bundle directory exists, then we don't
+        // need to initialize since it has already been done.
+        if (!getSecureAction().fileExists(sbDir))
+        {
+            // Create system bundle directory, if it does not exist.
+            if (!getSecureAction().mkdirs(sbDir))
+            {
+                m_logger.log(
+                    Logger.LOG_ERROR,
+                    getClass().getName() + ": Unable to create system bundle directory.");
+                throw new IOException("Unable to create system bundle directory.");
+            }
+        }
+
+        // Do some sanity checking.
+        if ((fileName.length() > 0) && (fileName.charAt(0) == File.separatorChar))
+            throw new IllegalArgumentException("The data file path must be relative, not absolute.");
+        else if (fileName.indexOf("..") >= 0)
+            throw new IllegalArgumentException("The data file path cannot contain a reference to the \"..\" directory.");
+
+        // Return the data file.
+        return new File(sbDir, fileName);
+    }
+
+    //
+    // Static file-related utility methods.
+    //
+
+    /**
+     * This method copies an input stream to the specified file.
+     * @param is the input stream to copy.
+     * @param outputFile the file to which the input stream should be copied.
+    **/
+    protected static void copyStreamToFile(InputStream is, File outputFile)
+        throws IOException
+    {
+        OutputStream os = null;
+
+        try
+        {
+            os = getSecureAction().getFileOutputStream(outputFile);
+            os = new BufferedOutputStream(os, BUFSIZE);
+            byte[] b = new byte[BUFSIZE];
+            int len = 0;
+            while ((len = is.read(b)) != -1)
+            {
+                os.write(b, 0, len);
+            }
+        }
+        finally
+        {
+            if (is != null) is.close();
+            if (os != null) os.close();
+        }
+    }
+
+    protected static boolean deleteDirectoryTree(File target)
+    {
+        if (!getSecureAction().fileExists(target))
+        {
+            return true;
+        }
+
+        if (getSecureAction().isFileDirectory(target))
+        {
+            File[] files = getSecureAction().listDirectory(target);
+            for (int i = 0; i < files.length; i++)
+            {
+                deleteDirectoryTree(files[i]);
+            }
+        }
+
+        return getSecureAction().deleteFile(target);
+    }
+
+    //
+    // Private methods.
+    //
+
+    private void initialize() throws Exception
+    {
+        // Get buffer size value.
+        try
+        {
+            String sBufSize = (String) m_configMap.get(CACHE_BUFSIZE_PROP);
+            if (sBufSize != null)
+            {
+                BUFSIZE = Integer.parseInt(sBufSize);
+            }
+        }
+        catch (NumberFormatException ne)
+        {
+            // Use the default value.
+        }
+
+        // See if the profile directory is specified.
+        String profileDirStr = (String) m_configMap.get(CACHE_PROFILE_DIR_PROP);
+        if (profileDirStr != null)
+        {
+            m_profileDir = new File(profileDirStr);
+        }
+        else
+        {
+            // Since no profile directory was specified, then the profile
+            // directory will be a directory in the cache directory named
+            // after the profile.
+
+            // First, determine the location of the cache directory; it
+            // can either be specified or in the default location.
+            String cacheDirStr = (String) m_configMap.get(CACHE_DIR_PROP);
+            if (cacheDirStr == null)
+            {
+                // Since no cache directory was specified, put it
+                // ".felix" in the user's home by default.
+                cacheDirStr = System.getProperty("user.home");
+                cacheDirStr = cacheDirStr.endsWith(File.separator)
+                    ? cacheDirStr : cacheDirStr + File.separator;
+                cacheDirStr = cacheDirStr + CACHE_DIR_NAME;
+            }
+
+            // Now, get the profile name.
+            String profileName = (String) m_configMap.get(CACHE_PROFILE_PROP);
+            if (profileName == null)
+            {
+                throw new IllegalArgumentException(
+                    "No profile name or directory has been specified.");
+            }
+            // Profile name cannot contain the File.separator char.
+            else if (profileName.indexOf(File.separator) >= 0)
+            {
+                throw new IllegalArgumentException(
+                    "The profile name cannot contain the file separator character.");
+            }
+
+            m_profileDir = new File(cacheDirStr, profileName);
+        }
+
+        // Create profile directory, if it does not exist.
+        if (!getSecureAction().fileExists(m_profileDir))
+        {
+            if (!getSecureAction().mkdirs(m_profileDir))
+            {
+                m_logger.log(
+                    Logger.LOG_ERROR,
+                    getClass().getName() + ": Unable to create directory: "
+                        + m_profileDir);
+                throw new RuntimeException("Unable to create profile directory.");
+            }
+        }
+
+        // Create the existing bundle archives in the profile directory,
+        // if any exist.
+        List archiveList = new ArrayList();
+        File[] children = getSecureAction().listDirectory(m_profileDir);
+        for (int i = 0; (children != null) && (i < children.length); i++)
+        {
+            // Ignore directories that aren't bundle directories or
+            // is the system bundle directory.
+            if (children[i].getName().startsWith(BUNDLE_DIR_PREFIX) &&
+                !children[i].getName().equals(BUNDLE_DIR_PREFIX + Long.toString(0)))
+            {
+                // Recreate the bundle archive.
+                try
+                {
+                    archiveList.add(new BundleArchive(m_logger, children[i]));
+                }
+                catch (Exception ex)
+                {
+                    // Log and ignore.
+                    m_logger.log(Logger.LOG_ERROR,
+                        getClass().getName() + ": Error creating archive.", ex);
+                }
+            }
+        }
+
+        m_archives = (BundleArchive[])
+            archiveList.toArray(new BundleArchive[archiveList.size()]);
+    }
+}
\ No newline at end of file

Propchange: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cxf/sandbox/dosgi/felix/framework/src/main/java/org/apache/felix/framework/cache/BundleCache.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date