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