Initial source commit.
git-svn-id: https://svn.apache.org/repos/asf/incubator/oscar/trunk@233031 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/org/apache/osgi/bundle/bundlerepository/Activator.java b/src/org/apache/osgi/bundle/bundlerepository/Activator.java
new file mode 100644
index 0000000..c50192e
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/Activator.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator
+{
+ private transient BundleContext m_context = null;
+ private transient BundleRepositoryImpl m_br = null;
+
+ public void start(BundleContext context)
+ {
+ m_context = context;
+
+ // Register bundle repository service.
+ m_br = new BundleRepositoryImpl(m_context);
+ context.registerService(
+ org.apache.osgi.service.bundlerepository.BundleRepository.class.getName(),
+ m_br, null);
+
+ // We dynamically import the shell service API, so it
+ // might not actually be available, so be ready to catch
+ // the exception when we try to register the command service.
+ try
+ {
+ // Register "obr" shell command service as a
+ // wrapper for the bundle repository service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new ObrCommandImpl(m_context, m_br), null);
+ }
+ catch (Throwable th)
+ {
+ // Ignore.
+ }
+ }
+
+ public void stop(BundleContext context)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/BundleRepositoryImpl.java b/src/org/apache/osgi/bundle/bundlerepository/BundleRepositoryImpl.java
new file mode 100644
index 0000000..07f5828
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/BundleRepositoryImpl.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import java.io.PrintStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.osgi.service.bundlerepository.*;
+import org.osgi.framework.*;
+
+public class BundleRepositoryImpl implements BundleRepository
+{
+ private BundleContext m_context = null;
+ private RepositoryState m_repo = null;
+
+ public BundleRepositoryImpl(BundleContext context)
+ {
+ m_context = context;
+ m_repo = new RepositoryState(m_context);
+ }
+
+ public String[] getRepositoryURLs()
+ {
+ return m_repo.getURLs();
+ }
+
+ public synchronized void setRepositoryURLs(String[] urls)
+ {
+ m_repo.setURLs(urls);
+ }
+
+ /**
+ * Get the number of bundles available in the repository.
+ * @return the number of available bundles.
+ **/
+ public synchronized BundleRecord[] getBundleRecords()
+ {
+ return m_repo.getRecords();
+ }
+
+ /**
+ * Get the specified bundle record from the repository.
+ * @param i the bundle record index to retrieve.
+ * @return the associated bundle record or <tt>null</tt>.
+ **/
+ public synchronized BundleRecord[] getBundleRecords(String symName)
+ {
+ return m_repo.getRecords(symName);
+ }
+
+ /**
+ * Get bundle record for the bundle with the specified name
+ * and version from the repository.
+ * @param name the bundle record name to retrieve.
+ * @param version three-interger array of the version associated with
+ * the name to retrieve.
+ * @return the associated bundle record or <tt>null</tt>.
+ **/
+ public synchronized BundleRecord getBundleRecord(String symName, int[] version)
+ {
+ return m_repo.getRecord(symName, version);
+ }
+
+ public boolean deployBundle(
+ PrintStream out, PrintStream err, String symName, int[] version,
+ boolean isResolve, boolean isStart)
+ {
+ // List to hold bundles that need to be started.
+ List startList = null;
+
+ // Get the bundle record of the remote bundle to be deployed.
+ BundleRecord targetRecord = m_repo.getRecord(symName, version);
+ if (targetRecord == null)
+ {
+ err.println("No such bundle in repository.");
+ return false;
+ }
+
+ // Create an editable snapshot of the current set of
+ // locally installed bundles.
+ LocalState localState = new LocalState(m_context);
+
+ // If the precise bundle is already deployed, then we are done.
+ if (localState.findBundle(symName, version) != null)
+ {
+ return true;
+ }
+
+ // Create the transitive closure all bundles that must be
+ // deployed as a result of deploying the target bundle;
+ // use a list because this will keep everything in order.
+ BundleRecord[] deployRecords = null;
+ // If the resolve flag is set, then get its imports to
+ // calculate the transitive closure of its dependencies.
+ if (isResolve)
+ {
+// Package[] imports = (Package[])
+// targetRecord.getAttribute(BundleRecord.IMPORT_PACKAGE);
+ Filter[] reqs = (Filter[])
+ targetRecord.getAttribute("requirement");
+ try
+ {
+ deployRecords = m_repo.resolvePackages(localState, reqs);
+ }
+ catch (ResolveException ex)
+ {
+ err.println("Resolve error: " + ex.getPackage());
+ return false;
+ }
+ }
+
+ // Add the target bundle since it will not be
+ // included in the array of records to deploy.
+ if (deployRecords == null)
+ {
+ deployRecords = new BundleRecord[] { targetRecord };
+ }
+ else
+ {
+ // Create a new array containing the target and put it first,
+ // since the array will be process in reverse order.
+ BundleRecord[] newRecs = new BundleRecord[deployRecords.length + 1];
+ newRecs[0] = targetRecord;
+ System.arraycopy(deployRecords, 0, newRecs, 1, deployRecords.length);
+ deployRecords = newRecs;
+ }
+
+ // Now iterate through all bundles in the deploy list
+ // in reverse order and deploy each; the order is not
+ // so important, but by reversing them at least the
+ // dependencies will be printed first and perhaps it
+ // will avoid some ordering issues when we are starting
+ // bundles.
+ for (int i = 0; i < deployRecords.length; i++)
+ {
+ LocalState.LocalBundleRecord updateRecord =
+ localState.findUpdatableBundle(deployRecords[i]);
+ if (updateRecord != null)
+ {
+// TODO: Should check to make sure that update bundle isn't already the
+// correct version.
+ // Modify our copy of the local state to reflect
+ // that the bundle is now updated.
+ localState.update(updateRecord, deployRecords[i]);
+
+ // Print out an "updating" message.
+ if (deployRecords[i] != targetRecord)
+ {
+ out.print("Updating dependency: ");
+ }
+ else
+ {
+ out.print("Updating: ");
+ }
+ out.println(Util.getBundleName(updateRecord.getBundle()));
+
+ // Actually perform the update.
+ try
+ {
+ URL url = new URL(
+ (String) deployRecords[i].getAttribute(BundleRecord.BUNDLE_URL));
+ updateRecord.getBundle().update(url.openStream());
+
+ // If necessary, save the updated bundle to be
+ // started later.
+ if (isStart)
+ {
+ if (startList == null)
+ {
+ startList = new ArrayList();
+ }
+ startList.add(updateRecord.getBundle());
+ }
+ }
+ catch (Exception ex)
+ {
+ err.println("Update error: " + Util.getBundleName(updateRecord.getBundle()));
+ ex.printStackTrace(err);
+ return false;
+ }
+ }
+ else
+ {
+ // Print out an "installing" message.
+ if (deployRecords[i] != targetRecord)
+ {
+ out.print("Installing dependency: ");
+ }
+ else
+ {
+ out.print("Installing: ");
+ }
+ out.println(deployRecords[i].getAttribute(BundleRecord.BUNDLE_NAME));
+
+ try
+ {
+ // Actually perform the install, but do not use the actual
+ // bundle JAR URL for the bundle location, since this will
+ // limit OBR's ability to manipulate bundle versions. Instead,
+ // use a unique timestamp as the bundle location.
+ URL url = new URL(
+ (String) deployRecords[i].getAttribute(BundleRecord.BUNDLE_URL));
+ Bundle bundle = m_context.installBundle(
+ "obr://"
+ + deployRecords[i].getAttribute(BundleRecord.BUNDLE_NAME)
+ + "/" + System.currentTimeMillis(),
+ url.openStream());
+
+ // If necessary, save the installed bundle to be
+ // started later.
+ if (isStart)
+ {
+ if (startList == null)
+ {
+ startList = new ArrayList();
+ }
+ startList.add(bundle);
+ }
+ }
+ catch (Exception ex)
+ {
+ err.println("Install error: "
+ + deployRecords[i].getAttribute(BundleRecord.BUNDLE_NAME));
+ ex.printStackTrace(err);
+ return false;
+ }
+ }
+ }
+
+ // If necessary, start bundles after installing them all.
+ if (isStart)
+ {
+ for (int i = 0; (startList != null) && (i < startList.size()); i++)
+ {
+ Bundle bundle = (Bundle) startList.get(i);
+ try
+ {
+ bundle.start();
+ }
+ catch (BundleException ex)
+ {
+ err.println("Update error: " + Util.getBundleName(bundle));
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public BundleRecord[] resolvePackages(IPackage[] pkgs)
+ throws ResolveException
+ {
+// TODO: FIX
+// return m_repo.resolvePackages(new LocalState(m_context), pkgs);
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/FileUtil.java b/src/org/apache/osgi/bundle/bundlerepository/FileUtil.java
new file mode 100644
index 0000000..9f27a0d
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/FileUtil.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+public class FileUtil
+{
+ public static void downloadSource(
+ PrintStream out, PrintStream err,
+ String srcURL, String dirStr, boolean extract)
+ {
+ // Get the file name from the URL.
+ String fileName = (srcURL.lastIndexOf('/') > 0)
+ ? srcURL.substring(srcURL.lastIndexOf('/') + 1)
+ : srcURL;
+
+ try
+ {
+ out.println("Connecting...");
+
+ File dir = new File(dirStr);
+ if (!dir.exists())
+ {
+ err.println("Destination directory does not exist.");
+ }
+ File file = new File(dir, fileName);
+
+ OutputStream os = new FileOutputStream(file);
+ URLConnection conn = new URL(srcURL).openConnection();
+ int total = conn.getContentLength();
+ InputStream is = conn.getInputStream();
+
+ if (total > 0)
+ {
+ out.println("Downloading " + fileName
+ + " ( " + total + " bytes ).");
+ }
+ else
+ {
+ out.println("Downloading " + fileName + ".");
+ }
+ byte[] buffer = new byte[4096];
+ int count = 0;
+ for (int len = is.read(buffer); len > 0; len = is.read(buffer))
+ {
+ count += len;
+ os.write(buffer, 0, len);
+ }
+
+ os.close();
+ is.close();
+
+ if (extract)
+ {
+ is = new FileInputStream(file);
+ JarInputStream jis = new JarInputStream(is);
+ out.println("Extracting...");
+ unjar(jis, dir);
+ jis.close();
+ file.delete();
+ }
+ }
+ catch (Exception ex)
+ {
+ err.println(ex);
+ }
+ }
+
+ public static void unjar(JarInputStream jis, File dir)
+ throws IOException
+ {
+ // Reusable buffer.
+ byte[] buffer = new byte[4096];
+
+ // Loop through JAR entries.
+ for (JarEntry je = jis.getNextJarEntry();
+ je != null;
+ je = jis.getNextJarEntry())
+ {
+ if (je.getName().startsWith("/"))
+ {
+ throw new IOException("JAR resource cannot contain absolute paths.");
+ }
+
+ File target = new File(dir, je.getName());
+
+ // Check to see if the JAR entry is a directory.
+ if (je.isDirectory())
+ {
+ if (!target.exists())
+ {
+ if (!target.mkdirs())
+ {
+ throw new IOException("Unable to create target directory: "
+ + target);
+ }
+ }
+ // Just continue since directories do not have content to copy.
+ continue;
+ }
+
+ int lastIndex = je.getName().lastIndexOf('/');
+ String name = (lastIndex >= 0) ?
+ je.getName().substring(lastIndex + 1) : je.getName();
+ String destination = (lastIndex >= 0) ?
+ je.getName().substring(0, lastIndex) : "";
+
+ // JAR files use '/', so convert it to platform separator.
+ destination = destination.replace('/', File.separatorChar);
+ copy(jis, dir, name, destination, buffer);
+ }
+ }
+
+ public static void copy(
+ InputStream is, File dir, String destName, String destDir, byte[] buffer)
+ throws IOException
+ {
+ if (destDir == null)
+ {
+ destDir = "";
+ }
+
+ // Make sure the target directory exists and
+ // that is actually a directory.
+ File targetDir = new File(dir, destDir);
+ if (!targetDir.exists())
+ {
+ if (!targetDir.mkdirs())
+ {
+ throw new IOException("Unable to create target directory: "
+ + targetDir);
+ }
+ }
+ else if (!targetDir.isDirectory())
+ {
+ throw new IOException("Target is not a directory: "
+ + targetDir);
+ }
+
+ BufferedOutputStream bos = new BufferedOutputStream(
+ new FileOutputStream(new File(targetDir, destName)));
+ int count = 0;
+ while ((count = is.read(buffer)) > 0)
+ {
+ bos.write(buffer, 0, count);
+ }
+ bos.close();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/IteratorToEnumeration.java b/src/org/apache/osgi/bundle/bundlerepository/IteratorToEnumeration.java
new file mode 100644
index 0000000..edd749f
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/IteratorToEnumeration.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+
+public class IteratorToEnumeration implements Enumeration
+{
+ private Iterator m_iter = null;
+
+ public IteratorToEnumeration(Iterator iter)
+ {
+ m_iter = iter;
+ }
+
+ public boolean hasMoreElements()
+ {
+ if (m_iter == null)
+ return false;
+ return m_iter.hasNext();
+ }
+
+ public Object nextElement()
+ {
+ if (m_iter == null)
+ return null;
+ return m_iter.next();
+ }
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/LocalState.java b/src/org/apache/osgi/bundle/bundlerepository/LocalState.java
new file mode 100644
index 0000000..6c91952
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/LocalState.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import java.util.*;
+
+import org.apache.osgi.service.bundlerepository.BundleRecord;
+import org.apache.osgi.service.bundlerepository.IPackage;
+import org.osgi.framework.*;
+
+public class LocalState
+{
+ private BundleContext m_context = null;
+ private List m_localRecordList = new ArrayList();
+
+ public LocalState(BundleContext context)
+ {
+ m_context = context;
+ initialize();
+ }
+
+ public BundleRecord findBundle(String symName, int[] version)
+ {
+ for (int i = 0; i < m_localRecordList.size(); i++)
+ {
+ BundleRecord brLocal = (BundleRecord) m_localRecordList.get(i);
+ String localSymName = (String)
+ brLocal.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+ int[] localVersion = Util.parseVersionString((String)
+ brLocal.getAttribute(BundleRecord.BUNDLE_VERSION));
+ if ((localSymName != null) &&
+ localSymName.equals(symName) &&
+ (Util.compareVersion(localVersion, version) == 0))
+ {
+ return brLocal;
+ }
+ }
+ return null;
+ }
+
+ public BundleRecord[] findBundles(String symName)
+ {
+ List matchList = new ArrayList();
+ for (int i = 0; i < m_localRecordList.size(); i++)
+ {
+ BundleRecord brLocal = (BundleRecord) m_localRecordList.get(i);
+ String localSymName = (String)
+ brLocal.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+ if ((localSymName != null) && localSymName.equals(symName))
+ {
+ matchList.add(brLocal);
+ }
+ }
+ return (BundleRecord[]) matchList.toArray(new BundleRecord[matchList.size()]);
+ }
+
+ public void update(BundleRecord oldRecord, BundleRecord newRecord)
+ {
+ // To update the old record we need to replace it with
+ // a new one, since BundleRecords are immutable. Make
+ // a new record that contains the attributes of the new
+ // record, but is associated with the local bundle of
+ // the old record.
+ if (oldRecord instanceof LocalBundleRecord)
+ {
+ String[] keys = newRecord.getAttributes();
+ Map map = new HashMap();
+ for (int i = 0; i < keys.length; i++)
+ {
+ map.put(keys, newRecord.getAttribute(keys[i]));
+ }
+ BundleRecord updatedRecord =
+ new LocalBundleRecord(
+ map, ((LocalBundleRecord) oldRecord).getBundle());
+ int idx = m_localRecordList.indexOf(oldRecord);
+ if (idx >= 0)
+ {
+ m_localRecordList.set(idx, updatedRecord);
+ }
+ }
+ }
+
+ public LocalBundleRecord findUpdatableBundle(BundleRecord record)
+ {
+ // Determine if any bundles with the specified symbolic
+ // name are already installed locally.
+ BundleRecord[] localRecords = findBundles(
+ (String)record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME));
+ if (localRecords != null)
+ {
+ // Since there are local bundles with the same symbolic
+ // name installed, then we must determine if we can
+ // update an existing bundle or if we must install
+ // another one. Loop through all local bundles with same
+ // symbolic name and find the first one that can be updated
+ // without breaking constraints of existing bundles.
+ for (int i = 0; i < localRecords.length; i++)
+ {
+ // Check to make sure that the version of the target
+ // record is greater than the local bundle version,
+ // since we do not want to perform a downgrade.
+// int[] vLocal = Util.parseVersionString((String)
+// localRecords[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+// int[] vTarget = Util.parseVersionString((String)
+// record.getAttribute(BundleRecord.BUNDLE_VERSION));
+// TODO: VERIFY WHAT IS GOING ON HERE.
+ // If the target bundle is a newer version and it is
+ // export compatible with the local bundle, then return it.
+ if (isUpdatable(localRecords[i], record))
+ {
+ return (LocalBundleRecord) localRecords[i];
+ }
+ }
+ }
+ return null;
+ }
+
+ public boolean isUpdatable(BundleRecord oldVersion, BundleRecord newVersion)
+ {
+ // First get all of the potentially resolvable package declarations
+ // from the local bundles for the old version of the bundle.
+ Filter[] reqFilters = getResolvableImportDeclarations(oldVersion);
+ if (reqFilters == null)
+ {
+ return true;
+ }
+ // Now make sure that all of the resolvable import declarations
+ // for the old version of the bundle can also be satisfied by
+ // the new version of the bundle.
+ Map[] capMaps = (Map[])
+ newVersion.getAttribute("capability");
+ if (capMaps == null)
+ {
+ return false;
+ }
+ MapToDictionary mapDict = new MapToDictionary(null);
+ for (int reqIdx = 0; reqIdx < reqFilters.length; reqIdx++)
+ {
+ boolean satisfied = false;
+ for (int capIdx = 0; !satisfied && (capIdx < capMaps.length); capIdx++)
+ {
+ mapDict.setSourceMap(capMaps[capIdx]);
+ if (reqFilters[reqIdx].match(mapDict))
+ {
+ satisfied = true;
+ }
+ }
+
+ // If any of the previously resolvable package declarations
+ // cannot be resolved, then the bundle is not updatable.
+ if (!satisfied)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public Filter[] getResolvableImportDeclarations(BundleRecord record)
+ {
+ Map[] capMaps = (Map[])
+ record.getAttribute("capability");
+ if ((capMaps != null) && (capMaps.length > 0))
+ {
+ List filterList = new ArrayList();
+ // Use brute force to determine if any of the exports
+ // could possibly resolve any of the imports.
+ MapToDictionary mapDict = new MapToDictionary(null);
+ for (int capIdx = 0; capIdx < capMaps.length; capIdx++)
+ {
+ boolean added = false;
+ for (int recIdx = 0; !added && (recIdx < m_localRecordList.size()); recIdx++)
+ {
+ BundleRecord brLocal = (BundleRecord) m_localRecordList.get(recIdx);
+ Filter[] reqFilters = (Filter[])
+ brLocal.getAttribute("requirement");
+ for (int reqIdx = 0;
+ (reqFilters != null) && (reqIdx < reqFilters.length);
+ reqIdx++)
+ {
+ mapDict.setSourceMap(capMaps[capIdx]);
+ if (reqFilters[reqIdx].match(mapDict))
+ {
+ added = true;
+ filterList.add(reqFilters[reqIdx]);
+ }
+ }
+ }
+ }
+ return (Filter[])
+ filterList.toArray(new Filter[filterList.size()]);
+ }
+ return null;
+ }
+
+ public boolean isResolvable(Filter reqFilter)
+ {
+ MapToDictionary mapDict = new MapToDictionary(null);
+ for (int brIdx = 0; brIdx < m_localRecordList.size(); brIdx++)
+ {
+ BundleRecord brLocal = (BundleRecord) m_localRecordList.get(brIdx);
+ Map[] capMaps = (Map[]) brLocal.getAttribute("capability");
+ for (int capIdx = 0; (capMaps != null) && (capIdx < capMaps.length); capIdx++)
+ {
+ mapDict.setSourceMap(capMaps[capIdx]);
+ if (reqFilter.match(mapDict))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void initialize()
+ {
+ Bundle[] bundles = m_context.getBundles();
+ for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+ {
+ Dictionary dict = bundles[i].getHeaders();
+ // Create a case-insensitive map.
+ Map bundleMap = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+
+ for (Enumeration keys = dict.keys(); keys.hasMoreElements(); )
+ {
+ Object key = keys.nextElement();
+ bundleMap.put(key, dict.get(key));
+ }
+
+ // Remove and convert any import package declarations
+ // into requirement filters.
+ String target = (String) bundleMap.remove(BundleRecord.IMPORT_PACKAGE);
+ if (target != null)
+ {
+ IPackage[] pkgs = R4Package.parseImportOrExportHeader(target);
+ Filter[] filters = new Filter[(pkgs == null) ? 0 : pkgs.length];
+ for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+ {
+ try
+ {
+ String low = pkgs[pkgIdx].getVersionLow().isInclusive()
+ ? "(version>=" + pkgs[pkgIdx].getVersionLow() + ")"
+ : "(!(version<=" + pkgs[pkgIdx].getVersionLow() + ")";
+
+ if (pkgs[pkgIdx].getVersionHigh() != null)
+ {
+ String high = pkgs[pkgIdx].getVersionHigh().isInclusive()
+ ? "(version<=" + pkgs[pkgIdx].getVersionHigh() + ")"
+ : "(!(version>=" + pkgs[pkgIdx].getVersionHigh() + ")";
+ filters[pkgIdx] = m_context.createFilter(
+ "(&(type=Export-Package)(name="
+ + pkgs[pkgIdx].getId() + ")"
+ + low + high + ")");
+ }
+ else
+ {
+ filters[pkgIdx] = m_context.createFilter(
+ "(&(type=Export-Package)(name="
+ + pkgs[pkgIdx].getId() + ")"
+ + low + ")");
+ }
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ // Ignore, since it should not happen.
+ }
+ }
+ bundleMap.put("requirement", filters);
+ }
+
+ // Remove and convert any export package declarations
+ // into capability maps.
+ target = (String) bundleMap.remove(BundleRecord.EXPORT_PACKAGE);
+ if (target != null)
+ {
+ IPackage[] pkgs = R4Package.parseImportOrExportHeader(target);
+ Map[] capMaps = new Map[(pkgs == null) ? 0 : pkgs.length];
+ for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+ {
+ // Create a case-insensitive map.
+ capMaps[pkgIdx] = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+ capMaps[pkgIdx].put("type", "Export-Package");
+ capMaps[pkgIdx].put("name", pkgs[pkgIdx].getId());
+ capMaps[pkgIdx].put("version", pkgs[pkgIdx].getVersionLow());
+ }
+ bundleMap.put("capability", capMaps);
+ }
+
+ // For the system bundle, add a special platform capability.
+ if (bundles[i].getBundleId() == 0)
+ {
+ // Create a case-insensitive map.
+ Map map = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+ map.put(
+ Constants.FRAMEWORK_VERSION,
+ m_context.getProperty(Constants.FRAMEWORK_VERSION));
+ map.put(
+ Constants.FRAMEWORK_VENDOR,
+ m_context.getProperty(Constants.FRAMEWORK_VENDOR));
+ map.put(
+ Constants.FRAMEWORK_LANGUAGE,
+ m_context.getProperty(Constants.FRAMEWORK_LANGUAGE));
+ map.put(
+ Constants.FRAMEWORK_OS_NAME,
+ m_context.getProperty(Constants.FRAMEWORK_OS_NAME));
+ map.put(
+ Constants.FRAMEWORK_OS_VERSION,
+ m_context.getProperty(Constants.FRAMEWORK_OS_VERSION));
+ map.put(
+ Constants.FRAMEWORK_PROCESSOR,
+ m_context.getProperty(Constants.FRAMEWORK_PROCESSOR));
+// map.put(
+// FelixConstants.FELIX_VERSION_PROPERTY,
+// m_context.getProperty(FelixConstants.FELIX_VERSION_PROPERTY));
+ Map[] capMaps = (Map[]) bundleMap.get("capability");
+ if (capMaps == null)
+ {
+ capMaps = new Map[] { map };
+ }
+ else
+ {
+ Map[] newCaps = new Map[capMaps.length + 1];
+ newCaps[0] = map;
+ System.arraycopy(capMaps, 0, newCaps, 1, capMaps.length);
+ capMaps = newCaps;
+ }
+ bundleMap.put("capability", capMaps);
+ }
+ m_localRecordList.add(new LocalBundleRecord(bundleMap, bundles[i]));
+ }
+ }
+
+ public static class LocalBundleRecord extends BundleRecord
+ {
+ private Bundle m_bundle = null;
+
+ LocalBundleRecord(Map attrMap, Bundle bundle)
+ {
+ super(attrMap);
+ m_bundle = bundle;
+ }
+
+ public Bundle getBundle()
+ {
+ return m_bundle;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/MapToDictionary.java b/src/org/apache/osgi/bundle/bundlerepository/MapToDictionary.java
new file mode 100644
index 0000000..d7806c6
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/MapToDictionary.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import java.util.*;
+
+
+/**
+ * This is a simple class that implements a <tt>Dictionary</tt>
+ * from a <tt>Map</tt>. The resulting dictionary is immutatable.
+**/
+public class MapToDictionary extends Dictionary
+{
+ /**
+ * Map source.
+ **/
+ private Map m_map = null;
+
+ public MapToDictionary(Map map)
+ {
+ m_map = map;
+ }
+
+ public void setSourceMap(Map map)
+ {
+ m_map = map;
+ }
+
+ public Enumeration elements()
+ {
+ if (m_map == null)
+ {
+ return null;
+ }
+ return new IteratorToEnumeration(m_map.values().iterator());
+ }
+
+ public Object get(Object key)
+ {
+ if (m_map == null)
+ {
+ return null;
+ }
+ return m_map.get(key);
+ }
+
+ public boolean isEmpty()
+ {
+ if (m_map == null)
+ {
+ return true;
+ }
+ return m_map.isEmpty();
+ }
+
+ public Enumeration keys()
+ {
+ if (m_map == null)
+ {
+ return null;
+ }
+ return new IteratorToEnumeration(m_map.keySet().iterator());
+ }
+
+ public Object put(Object key, Object value)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object remove(Object key)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size()
+ {
+ if (m_map == null)
+ {
+ return 0;
+ }
+ return m_map.size();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java b/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java
new file mode 100644
index 0000000..10465a0
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/ObrCommandImpl.java
@@ -0,0 +1,1374 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.osgi.service.bundlerepository.BundleRecord;
+import org.apache.osgi.service.bundlerepository.BundleRepository;
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class ObrCommandImpl implements Command
+{
+ private static final String HELP_CMD = "help";
+ private static final String URLS_CMD = "urls";
+ private static final String LIST_CMD = "list";
+ private static final String INFO_CMD = "info";
+ private static final String DEPLOY_CMD = "deploy";
+// private static final String INSTALL_CMD = "install";
+ private static final String START_CMD = "start";
+// private static final String UPDATE_CMD = "update";
+ private static final String SOURCE_CMD = "source";
+
+ private static final String NODEPS_SWITCH = "-nodeps";
+ private static final String CHECK_SWITCH = "-check";
+ private static final String EXTRACT_SWITCH = "-x";
+
+ private BundleContext m_context = null;
+ private BundleRepository m_repo = null;
+
+ public ObrCommandImpl(BundleContext context, BundleRepository repo)
+ {
+ m_context = context;
+ m_repo = repo;
+ }
+
+ public String getName()
+ {
+ return "obr";
+ }
+
+ public String getUsage()
+ {
+ return "obr help";
+ }
+
+ public String getShortDescription()
+ {
+ return "OSGi bundle repository.";
+ }
+
+ public synchronized void execute(String commandLine, PrintStream out, PrintStream err)
+ {
+ try
+ {
+ // Parse the commandLine to get the OBR command.
+ StringTokenizer st = new StringTokenizer(commandLine);
+ // Ignore the invoking command.
+ st.nextToken();
+ // Try to get the OBR command, default is HELP command.
+ String command = HELP_CMD;
+ try
+ {
+ command = st.nextToken();
+ }
+ catch (Exception ex)
+ {
+ // Ignore.
+ }
+
+ // Perform the specified command.
+ if ((command == null) || (command.equals(HELP_CMD)))
+ {
+ help(out, st);
+ }
+ else
+ {
+ if (command.equals(URLS_CMD))
+ {
+ urls(commandLine, command, out, err);
+ }
+ else if (command.equals(LIST_CMD))
+ {
+ list(commandLine, command, out, err);
+ }
+ else if (command.equals(INFO_CMD))
+ {
+ info(commandLine, command, out, err);
+ }
+ else if (command.equals(DEPLOY_CMD) || command.equals(START_CMD))
+ {
+ deploy(commandLine, command, out, err);
+ }
+/*
+ else if (command.equals(INSTALL_CMD) || command.equals(START_CMD))
+ {
+ install(commandLine, command, out, err);
+ }
+ else if (command.equals(UPDATE_CMD))
+ {
+ update(commandLine, command, out, err);
+ }
+*/
+ else if (command.equals(SOURCE_CMD))
+ {
+ source(commandLine, command, out, err);
+ }
+ else
+ {
+ err.println("Unknown command: " + command);
+ }
+ }
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ err.println("Syntax error: " + ex.getMessage());
+ }
+ catch (IOException ex)
+ {
+ err.println("Error: " + ex);
+ }
+ }
+
+ private void urls(
+ String commandLine, String command, PrintStream out, PrintStream err)
+ throws IOException
+ {
+ // Parse the commandLine.
+ StringTokenizer st = new StringTokenizer(commandLine);
+ // Ignore the "obr" command.
+ st.nextToken();
+ // Ignore the "urls" command.
+ st.nextToken();
+
+ int count = st.countTokens();
+ String[] urls = new String[count];
+ for (int i = 0; i < count; i++)
+ {
+ urls[i] = st.nextToken();
+ }
+
+ if (count > 0)
+ {
+ m_repo.setRepositoryURLs(urls);
+ }
+ else
+ {
+ urls = m_repo.getRepositoryURLs();
+ if (urls != null)
+ {
+ for (int i = 0; i < urls.length; i++)
+ {
+ out.println(urls[i]);
+ }
+ }
+ else
+ {
+ out.println("No repository URLs are set.");
+ }
+ }
+ }
+
+ private void list(
+ String commandLine, String command, PrintStream out, PrintStream err)
+ throws IOException
+ {
+ // Create a stream tokenizer for the command line string,
+ // since the syntax for install/start is more sophisticated.
+ StringReader sr = new StringReader(commandLine);
+ StreamTokenizer tokenizer = new StreamTokenizer(sr);
+ tokenizer.resetSyntax();
+ tokenizer.quoteChar('\'');
+ tokenizer.quoteChar('\"');
+ tokenizer.whitespaceChars('\u0000', '\u0020');
+ tokenizer.wordChars('A', 'Z');
+ tokenizer.wordChars('a', 'z');
+ tokenizer.wordChars('0', '9');
+ tokenizer.wordChars('\u00A0', '\u00FF');
+ tokenizer.wordChars('.', '.');
+ tokenizer.wordChars('-', '-');
+ tokenizer.wordChars('_', '_');
+
+ // Ignore the invoking command name and the OBR command.
+ int type = tokenizer.nextToken();
+ type = tokenizer.nextToken();
+
+ String substr = null;
+
+ for (type = tokenizer.nextToken();
+ type != StreamTokenizer.TT_EOF;
+ type = tokenizer.nextToken())
+ {
+ // Add a space in between tokens.
+ if (substr == null)
+ {
+ substr = "";
+ }
+ else
+ {
+ substr += " ";
+ }
+
+ if ((type == StreamTokenizer.TT_WORD) ||
+ (type == '\'') || (type == '"'))
+ {
+ substr += tokenizer.sval.toLowerCase();
+ }
+ }
+
+ boolean found = false;
+ BundleRecord[] records = m_repo.getBundleRecords();
+ for (int recIdx = 0; recIdx < records.length; recIdx++)
+ {
+ String name = (String)
+ records[recIdx].getAttribute(BundleRecord.BUNDLE_NAME);
+ String symName = (String)
+ records[recIdx].getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+ if ((substr == null) ||
+ ((name != null) && (name.toLowerCase().indexOf(substr) >= 0)) ||
+ ((symName != null) && (symName.toLowerCase().indexOf(substr) >= 0)))
+ {
+ found = true;
+ String version =
+ (String) records[recIdx].getAttribute(BundleRecord.BUNDLE_VERSION);
+ if (version != null)
+ {
+ out.println(name + " (" + version + ")");
+ }
+ else
+ {
+ out.println(name);
+ }
+ }
+ }
+
+ if (!found)
+ {
+ out.println("No matching bundles.");
+ }
+ }
+
+ private void info(
+ String commandLine, String command, PrintStream out, PrintStream err)
+ throws IOException, InvalidSyntaxException
+ {
+ ParsedCommand pc = parseInfo(commandLine);
+ for (int i = 0; (pc != null) && (i < pc.getTargetCount()); i++)
+ {
+ BundleRecord[] records = searchRepository(
+ pc.getTargetId(i), pc.getTargetVersion(i));
+ if (records == null)
+ {
+ err.println("Unknown bundle and/or version: "
+ + pc.getTargetId(i));
+ }
+ else if (records.length > 1)
+ {
+ err.println("More than one version exists: "
+ + pc.getTargetId(i));
+ }
+ else
+ {
+ records[0].printAttributes(out);
+ }
+ }
+ }
+
+ private void deploy(
+ String commandLine, String command, PrintStream out, PrintStream err)
+ throws IOException, InvalidSyntaxException
+ {
+ ParsedCommand pc = parseInstallStart(commandLine);
+ _deploy(pc, command, out, err);
+ }
+
+ private void _deploy(
+ ParsedCommand pc, String command, PrintStream out, PrintStream err)
+ throws IOException, InvalidSyntaxException
+ {
+ for (int i = 0; (pc != null) && (i < pc.getTargetCount()); i++)
+ {
+ // Find the target's bundle record.
+ BundleRecord record = selectNewestVersion(
+ searchRepository(pc.getTargetId(i), pc.getTargetVersion(i)));
+ if (record != null)
+ {
+ m_repo.deployBundle(
+ out, // Output stream.
+ err, // Error stream.
+ (String) record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME),
+ Util.parseVersionString((String)record.getAttribute(BundleRecord.BUNDLE_VERSION)),
+ pc.isResolve(), // Resolve dependencies.
+ command.equals(START_CMD)); // Start.
+ }
+ else
+ {
+ err.println("Unknown bundle or amiguous version: "
+ + pc.getTargetId(i));
+ }
+ }
+ }
+/*
+ private void install(
+ String commandLine, String command, PrintStream out, PrintStream err)
+ throws IOException, InvalidSyntaxException
+ {
+ // Parse the command line to get all local targets to install.
+ ParsedCommand pc = parseInstallStart(commandLine);
+
+ // Loop through each local target and try to find
+ // the corresponding bundle record from the repository.
+ for (int targetIdx = 0;
+ (pc != null) && (targetIdx < pc.getTargetCount());
+ targetIdx++)
+ {
+ // Get the current target's name and version.
+ String targetName = pc.getTargetId(targetIdx);
+ String targetVersionString = pc.getTargetVersion(targetIdx);
+
+ // Make sure the bundle is not already installed.
+ Bundle bundle = findLocalBundle(targetName, targetVersionString);
+ if (bundle == null)
+ {
+ _deploy(pc, command, out, err);
+ }
+ else
+ {
+ err.println("Already installed: " + targetName);
+ }
+ }
+ }
+
+ private void update(
+ String commandLine, String command, PrintStream out, PrintStream err)
+ throws IOException, InvalidSyntaxException
+ {
+ // Parse the command line to get all local targets to update.
+ ParsedCommand pc = parseUpdate(commandLine);
+
+ if (pc.isCheck())
+ {
+ updateCheck(out, err);
+ }
+ else
+ {
+ // Loop through each local target and try to find
+ // the corresponding bundle record from the repository.
+ for (int targetIdx = 0;
+ (pc != null) && (targetIdx < pc.getTargetCount());
+ targetIdx++)
+ {
+ // Get the current target's name and version.
+ String targetName = pc.getTargetId(targetIdx);
+ String targetVersionString = pc.getTargetVersion(targetIdx);
+
+ // Make sure the bundle is not already installed.
+ Bundle bundle = findLocalBundle(targetName, targetVersionString);
+ if (bundle != null)
+ {
+ _deploy(pc, command, out, err);
+ }
+ else
+ {
+ err.println("Not installed: " + targetName);
+ }
+ }
+ }
+ }
+
+ private void updateCheck(PrintStream out, PrintStream err)
+ throws IOException
+ {
+ Bundle[] bundles = m_context.getBundles();
+
+ // Loop through each local target and try to find
+ // the corresponding locally installed bundle.
+ for (int bundleIdx = 0;
+ (bundles != null) && (bundleIdx < bundles.length);
+ bundleIdx++)
+ {
+ // Ignore the system bundle.
+ if (bundles[bundleIdx].getBundleId() == 0)
+ {
+ continue;
+ }
+
+ // Get the local bundle's update location.
+ String localLoc = (String)
+ bundles[bundleIdx].getHeaders().get(Constants.BUNDLE_UPDATELOCATION);
+ if (localLoc == null)
+ {
+ // Without an update location, there is no way to
+ // check for an update, so ignore the bundle.
+ continue;
+ }
+
+ // Get the local bundle's version.
+ String localVersion = (String)
+ bundles[bundleIdx].getHeaders().get(Constants.BUNDLE_VERSION);
+ localVersion = (localVersion == null) ? "0.0.0" : localVersion;
+
+ // Get the matching repository bundle records.
+ BundleRecord[] records = m_repo.getBundleRecords(
+ (String) bundles[bundleIdx].getHeaders().get(Constants.BUNDLE_NAME));
+
+ // Loop through all records to see if there is an update.
+ for (int recordIdx = 0;
+ (records != null) && (recordIdx < records.length);
+ recordIdx++)
+ {
+ String remoteLoc = (String)
+ records[recordIdx].getAttribute(BundleRecord.BUNDLE_UPDATELOCATION);
+ if (remoteLoc == null)
+ {
+ continue;
+ }
+
+ // If the update locations are equal, then compare versions.
+ if (remoteLoc.equals(localLoc))
+ {
+ String remoteVersion = (String)
+ records[recordIdx].getAttribute(BundleRecord.BUNDLE_VERSION);
+ if (remoteVersion != null)
+ {
+ int result = Util.compareVersion(
+ Util.parseVersionString(remoteVersion),
+ Util.parseVersionString(localVersion));
+ if (result > 0)
+ {
+ out.println(
+ records[recordIdx].getAttribute(BundleRecord.BUNDLE_NAME)
+ + " update available.");
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+*/
+ private void source(
+ String commandLine, String command, PrintStream out, PrintStream err)
+ throws IOException, InvalidSyntaxException
+ {
+ // Parse the command line to get all local targets to update.
+ ParsedCommand pc = parseSource(commandLine);
+
+ for (int i = 0; i < pc.getTargetCount(); i++)
+ {
+ BundleRecord[] records =
+ searchRepository(pc.getTargetId(i), pc.getTargetVersion(i));
+ if (records == null)
+ {
+ err.println("Unknown bundle and/or version: "
+ + pc.getTargetId(i));
+ }
+ else if (records.length > 1)
+ {
+ err.println("More than one version exists: "
+ + pc.getTargetId(i));
+ }
+ else
+ {
+ String srcURL = (String)
+ records[0].getAttribute(BundleRecord.BUNDLE_SOURCEURL);
+ if (srcURL != null)
+ {
+ FileUtil.downloadSource(
+ out, err, srcURL, pc.getDirectory(), pc.isExtract());
+ }
+ else
+ {
+ err.println("Missing source URL: " + pc.getTargetId(i));
+ }
+ }
+ }
+ }
+
+ private BundleRecord[] searchRepository(String targetId, String targetVersion)
+ {
+ // The targetId may be a bundle name or a bundle symbolic name.
+ // Query for symbolic name first, since it is more specific. If
+ // that can't be found, then compare bundle names.
+ BundleRecord[] records = null;
+ if (targetVersion != null)
+ {
+ BundleRecord record = m_repo.getBundleRecord(
+ targetId, Util.parseVersionString(targetVersion));
+ if (record != null)
+ {
+ records = new BundleRecord[] { record };
+ }
+ }
+ else
+ {
+ records = m_repo.getBundleRecords(targetId);
+ }
+
+ if (records == null)
+ {
+ List recordList = new ArrayList();
+ records = m_repo.getBundleRecords();
+ for (int i = 0; (records != null) && (i < records.length); i++)
+ {
+ if (targetId.compareToIgnoreCase((String)
+ records[i].getAttribute(BundleRecord.BUNDLE_NAME)) == 0)
+ {
+ int[] v1 = Util.parseVersionString(targetVersion);
+ int[] v2 = Util.parseVersionString((String)
+ records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+ if ((targetVersion == null) ||
+ ((targetVersion != null) && (Util.compareVersion(v1, v2) == 0)))
+ {
+ recordList.add(records[i]);
+ }
+ }
+ }
+ records = (recordList.size() == 0)
+ ? null
+ : (BundleRecord[]) recordList.toArray(new BundleRecord[recordList.size()]);
+ }
+
+ return records;
+ }
+
+ public BundleRecord selectNewestVersion(BundleRecord[] records)
+ {
+ int idx = -1;
+ int[] v = null;
+ for (int i = 0; (records != null) && (i < records.length); i++)
+ {
+ if (i == 0)
+ {
+ idx = 0;
+ v = Util.parseVersionString((String)
+ records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+ }
+ else
+ {
+ int[] vtmp = Util.parseVersionString((String)
+ records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+ if (Util.compareVersion(vtmp, v) > 0)
+ {
+ idx = i;
+ v = vtmp;
+ }
+ }
+ }
+
+ return (idx < 0) ? null : records[idx];
+ }
+
+ private Bundle findLocalBundle(String name, String versionString)
+ {
+ Bundle bundle = null;
+
+ // Get the name only if there is no version, but error
+ // if there are multiple matches for the same name.
+ if (versionString == null)
+ {
+ // Perhaps the target name is a bundle ID and
+ // not a name, so try to interpret as a long.
+ try
+ {
+ bundle = m_context.getBundle(Long.parseLong(name));
+ }
+ catch (NumberFormatException ex)
+ {
+ // The bundle is not a number, so look for a local
+ // bundle with the same name.
+ Bundle[] matchingBundles = findLocalBundlesBySymbolicName(name);
+
+ // If only one matches, then select is.
+ if (matchingBundles.length == 1)
+ {
+ bundle = matchingBundles[0];
+ }
+ }
+ }
+ else
+ {
+ // Find the local bundle by name and version.
+ bundle = findLocalBundleByVersion(
+ name, Util.parseVersionString(versionString));
+ }
+
+ return bundle;
+ }
+
+ private Bundle findLocalBundleByVersion(String symName, int[] version)
+ {
+ // Get bundles with matching name.
+ Bundle[] targets = findLocalBundlesBySymbolicName(symName);
+
+ // Find bundle with matching version.
+ if (targets.length > 0)
+ {
+ for (int i = 0; i < targets.length; i++)
+ {
+ String targetName = (String)
+ targets[i].getHeaders().get(BundleRecord.BUNDLE_SYMBOLICNAME);
+ int[] targetVersion = Util.parseVersionString((String)
+ targets[i].getHeaders().get(BundleRecord.BUNDLE_VERSION));
+
+ if ((targetName != null) &&
+ targetName.equalsIgnoreCase(symName) &&
+ (Util.compareVersion(targetVersion, version) == 0))
+ {
+ return targets[i];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private Bundle[] findLocalBundlesBySymbolicName(String symName)
+ {
+ // Get local bundles.
+ Bundle[] bundles = m_context.getBundles();
+
+ // Find bundles with matching name.
+ Bundle[] targets = new Bundle[0];
+ for (int i = 0; i < bundles.length; i++)
+ {
+ String targetName = (String)
+ bundles[i].getHeaders().get(BundleRecord.BUNDLE_SYMBOLICNAME);
+ if (targetName == null)
+ {
+ targetName = bundles[i].getLocation();
+ }
+ if ((targetName != null) && targetName.equalsIgnoreCase(symName))
+ {
+ Bundle[] newTargets = new Bundle[targets.length + 1];
+ System.arraycopy(targets, 0, newTargets, 0, targets.length);
+ newTargets[targets.length] = bundles[i];
+ targets = newTargets;
+ }
+ }
+
+ return targets;
+ }
+
+ private ParsedCommand parseInfo(String commandLine)
+ throws IOException, InvalidSyntaxException
+ {
+ // Create a stream tokenizer for the command line string,
+ // since the syntax for install/start is more sophisticated.
+ StringReader sr = new StringReader(commandLine);
+ StreamTokenizer tokenizer = new StreamTokenizer(sr);
+ tokenizer.resetSyntax();
+ tokenizer.quoteChar('\'');
+ tokenizer.quoteChar('\"');
+ tokenizer.whitespaceChars('\u0000', '\u0020');
+ tokenizer.wordChars('A', 'Z');
+ tokenizer.wordChars('a', 'z');
+ tokenizer.wordChars('0', '9');
+ tokenizer.wordChars('\u00A0', '\u00FF');
+ tokenizer.wordChars('.', '.');
+ tokenizer.wordChars('-', '-');
+ tokenizer.wordChars('_', '_');
+
+ // Ignore the invoking command name and the OBR command.
+ int type = tokenizer.nextToken();
+ type = tokenizer.nextToken();
+
+ int EOF = 1;
+ int SWITCH = 2;
+ int TARGET = 4;
+ int VERSION = 8;
+ int VERSION_VALUE = 16;
+
+ // Construct an install record.
+ ParsedCommand pc = new ParsedCommand();
+ String currentTargetName = null;
+
+ // The state machine starts by expecting either a
+ // SWITCH or a TARGET.
+ int expecting = (TARGET);
+ while (true)
+ {
+ // Get the next token type.
+ type = tokenizer.nextToken();
+ switch (type)
+ {
+ // EOF received.
+ case StreamTokenizer.TT_EOF:
+ // Error if we weren't expecting EOF.
+ if ((expecting & EOF) == 0)
+ {
+ throw new InvalidSyntaxException(
+ "Expecting more arguments.", null);
+ }
+ // Add current target if there is one.
+ if (currentTargetName != null)
+ {
+ pc.addTarget(currentTargetName, null);
+ }
+ // Return cleanly.
+ return pc;
+
+ // WORD or quoted WORD received.
+ case StreamTokenizer.TT_WORD:
+ case '\'':
+ case '\"':
+ // If we are expecting a target, the record it.
+ if ((expecting & TARGET) > 0)
+ {
+ // Add current target if there is one.
+ if (currentTargetName != null)
+ {
+ pc.addTarget(currentTargetName, null);
+ }
+ // Set the new target as the current target.
+ currentTargetName = tokenizer.sval;
+ expecting = (EOF | TARGET | VERSION);
+ }
+ else if ((expecting & VERSION_VALUE) > 0)
+ {
+ pc.addTarget(currentTargetName, tokenizer.sval);
+ currentTargetName = null;
+ expecting = (EOF | TARGET);
+ }
+ else
+ {
+ throw new InvalidSyntaxException(
+ "Not expecting '" + tokenizer.sval + "'.", null);
+ }
+ break;
+
+ // Version separator character received.
+ case ';':
+ // Error if we weren't expecting the version separator.
+ if ((expecting & VERSION) == 0)
+ {
+ throw new InvalidSyntaxException(
+ "Not expecting version.", null);
+ }
+ // Otherwise, we will only expect a version value next.
+ expecting = (VERSION_VALUE);
+ break;
+ }
+ }
+ }
+
+ private ParsedCommand parseInstallStart(String commandLine)
+ throws IOException, InvalidSyntaxException
+ {
+ // Create a stream tokenizer for the command line string,
+ // since the syntax for install/start is more sophisticated.
+ StringReader sr = new StringReader(commandLine);
+ StreamTokenizer tokenizer = new StreamTokenizer(sr);
+ tokenizer.resetSyntax();
+ tokenizer.quoteChar('\'');
+ tokenizer.quoteChar('\"');
+ tokenizer.whitespaceChars('\u0000', '\u0020');
+ tokenizer.wordChars('A', 'Z');
+ tokenizer.wordChars('a', 'z');
+ tokenizer.wordChars('0', '9');
+ tokenizer.wordChars('\u00A0', '\u00FF');
+ tokenizer.wordChars('.', '.');
+ tokenizer.wordChars('-', '-');
+ tokenizer.wordChars('_', '_');
+
+ // Ignore the invoking command name and the OBR command.
+ int type = tokenizer.nextToken();
+ type = tokenizer.nextToken();
+
+ int EOF = 1;
+ int SWITCH = 2;
+ int TARGET = 4;
+ int VERSION = 8;
+ int VERSION_VALUE = 16;
+
+ // Construct an install record.
+ ParsedCommand pc = new ParsedCommand();
+ String currentTargetName = null;
+
+ // The state machine starts by expecting either a
+ // SWITCH or a TARGET.
+ int expecting = (SWITCH | TARGET);
+ while (true)
+ {
+ // Get the next token type.
+ type = tokenizer.nextToken();
+ switch (type)
+ {
+ // EOF received.
+ case StreamTokenizer.TT_EOF:
+ // Error if we weren't expecting EOF.
+ if ((expecting & EOF) == 0)
+ {
+ throw new InvalidSyntaxException(
+ "Expecting more arguments.", null);
+ }
+ // Add current target if there is one.
+ if (currentTargetName != null)
+ {
+ pc.addTarget(currentTargetName, null);
+ }
+ // Return cleanly.
+ return pc;
+
+ // WORD or quoted WORD received.
+ case StreamTokenizer.TT_WORD:
+ case '\'':
+ case '\"':
+ // If we are expecting a command SWITCH and the token
+ // equals a command SWITCH, then record it.
+ if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(NODEPS_SWITCH))
+ {
+ pc.setResolve(false);
+ expecting = (EOF | TARGET);
+ }
+ // If we are expecting a target, the record it.
+ else if ((expecting & TARGET) > 0)
+ {
+ // Add current target if there is one.
+ if (currentTargetName != null)
+ {
+ pc.addTarget(currentTargetName, null);
+ }
+ // Set the new target as the current target.
+ currentTargetName = tokenizer.sval;
+ expecting = (EOF | TARGET | VERSION);
+ }
+ else if ((expecting & VERSION_VALUE) > 0)
+ {
+ pc.addTarget(currentTargetName, tokenizer.sval);
+ currentTargetName = null;
+ expecting = (EOF | TARGET);
+ }
+ else
+ {
+ throw new InvalidSyntaxException(
+ "Not expecting '" + tokenizer.sval + "'.", null);
+ }
+ break;
+
+ // Version separator character received.
+ case ';':
+ // Error if we weren't expecting the version separator.
+ if ((expecting & VERSION) == 0)
+ {
+ throw new InvalidSyntaxException(
+ "Not expecting version.", null);
+ }
+ // Otherwise, we will only expect a version value next.
+ expecting = (VERSION_VALUE);
+ break;
+ }
+ }
+ }
+
+ private ParsedCommand parseUpdate(String commandLine)
+ throws IOException, InvalidSyntaxException
+ {
+ // Create a stream tokenizer for the command line string,
+ // since the syntax for install/start is more sophisticated.
+ StringReader sr = new StringReader(commandLine);
+ StreamTokenizer tokenizer = new StreamTokenizer(sr);
+ tokenizer.resetSyntax();
+ tokenizer.quoteChar('\'');
+ tokenizer.quoteChar('\"');
+ tokenizer.whitespaceChars('\u0000', '\u0020');
+ tokenizer.wordChars('A', 'Z');
+ tokenizer.wordChars('a', 'z');
+ tokenizer.wordChars('0', '9');
+ tokenizer.wordChars('\u00A0', '\u00FF');
+ tokenizer.wordChars('.', '.');
+ tokenizer.wordChars('-', '-');
+ tokenizer.wordChars('_', '_');
+
+ // Ignore the invoking command name and the OBR command.
+ int type = tokenizer.nextToken();
+ type = tokenizer.nextToken();
+
+ int EOF = 1;
+ int SWITCH = 2;
+ int TARGET = 4;
+ int VERSION = 8;
+ int VERSION_VALUE = 16;
+
+ // Construct an install record.
+ ParsedCommand pc = new ParsedCommand();
+ String currentTargetName = null;
+
+ // The state machine starts by expecting either a
+ // SWITCH or a TARGET.
+ int expecting = (SWITCH | TARGET);
+ while (true)
+ {
+ // Get the next token type.
+ type = tokenizer.nextToken();
+ switch (type)
+ {
+ // EOF received.
+ case StreamTokenizer.TT_EOF:
+ // Error if we weren't expecting EOF.
+ if ((expecting & EOF) == 0)
+ {
+ throw new InvalidSyntaxException(
+ "Expecting more arguments.", null);
+ }
+ // Add current target if there is one.
+ if (currentTargetName != null)
+ {
+ pc.addTarget(currentTargetName, null);
+ }
+ // Return cleanly.
+ return pc;
+
+ // WORD or quoted WORD received.
+ case StreamTokenizer.TT_WORD:
+ case '\'':
+ case '\"':
+ // If we are expecting a command SWITCH and the token
+ // equals a NODEPS switch, then record it.
+ if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(NODEPS_SWITCH))
+ {
+ pc.setResolve(false);
+ expecting = (EOF | TARGET);
+ }
+ // If we are expecting a command SWITCH and the token
+ // equals a CHECK swithc, then record it.
+ else if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(CHECK_SWITCH))
+ {
+ pc.setCheck(true);
+ expecting = (EOF);
+ }
+ // If we are expecting a target, the record it.
+ else if ((expecting & TARGET) > 0)
+ {
+ // Add current target if there is one.
+ if (currentTargetName != null)
+ {
+ pc.addTarget(currentTargetName, null);
+ }
+ // Set the new target as the current target.
+ currentTargetName = tokenizer.sval;
+ expecting = (EOF | TARGET | VERSION);
+ }
+ else if ((expecting & VERSION_VALUE) > 0)
+ {
+ pc.addTarget(currentTargetName, tokenizer.sval);
+ currentTargetName = null;
+ expecting = (EOF | TARGET);
+ }
+ else
+ {
+ throw new InvalidSyntaxException(
+ "Not expecting '" + tokenizer.sval + "'.", null);
+ }
+ break;
+
+ // Version separator character received.
+ case ';':
+ // Error if we weren't expecting the version separator.
+ if ((expecting & VERSION) == 0)
+ {
+ throw new InvalidSyntaxException(
+ "Not expecting version.", null);
+ }
+ // Otherwise, we will only expect a version value next.
+ expecting = (VERSION_VALUE);
+ break;
+ }
+ }
+ }
+
+ private ParsedCommand parseSource(String commandLine)
+ throws IOException, InvalidSyntaxException
+ {
+ // Create a stream tokenizer for the command line string,
+ // since the syntax for install/start is more sophisticated.
+ StringReader sr = new StringReader(commandLine);
+ StreamTokenizer tokenizer = new StreamTokenizer(sr);
+ tokenizer.resetSyntax();
+ tokenizer.quoteChar('\'');
+ tokenizer.quoteChar('\"');
+ tokenizer.whitespaceChars('\u0000', '\u0020');
+ tokenizer.wordChars('A', 'Z');
+ tokenizer.wordChars('a', 'z');
+ tokenizer.wordChars('0', '9');
+ tokenizer.wordChars('\u00A0', '\u00FF');
+ tokenizer.wordChars('.', '.');
+ tokenizer.wordChars('-', '-');
+ tokenizer.wordChars('_', '_');
+ tokenizer.wordChars('/', '/');
+
+ // Ignore the invoking command name and the OBR command.
+ int type = tokenizer.nextToken();
+ type = tokenizer.nextToken();
+
+ int EOF = 1;
+ int SWITCH = 2;
+ int DIRECTORY = 4;
+ int TARGET = 8;
+ int VERSION = 16;
+ int VERSION_VALUE = 32;
+
+ // Construct an install record.
+ ParsedCommand pc = new ParsedCommand();
+ String currentTargetName = null;
+
+ // The state machine starts by expecting either a
+ // SWITCH or a DIRECTORY.
+ int expecting = (SWITCH | DIRECTORY);
+ while (true)
+ {
+ // Get the next token type.
+ type = tokenizer.nextToken();
+ switch (type)
+ {
+ // EOF received.
+ case StreamTokenizer.TT_EOF:
+ // Error if we weren't expecting EOF.
+ if ((expecting & EOF) == 0)
+ {
+ throw new InvalidSyntaxException(
+ "Expecting more arguments.", null);
+ }
+ // Add current target if there is one.
+ if (currentTargetName != null)
+ {
+ pc.addTarget(currentTargetName, null);
+ }
+ // Return cleanly.
+ return pc;
+
+ // WORD or quoted WORD received.
+ case StreamTokenizer.TT_WORD:
+ case '\'':
+ case '\"':
+ // If we are expecting a command SWITCH and the token
+ // equals a command SWITCH, then record it.
+ if (((expecting & SWITCH) > 0) && tokenizer.sval.equals(EXTRACT_SWITCH))
+ {
+ pc.setExtract(true);
+ expecting = (DIRECTORY);
+ }
+ // If we are expecting a directory, the record it.
+ else if ((expecting & DIRECTORY) > 0)
+ {
+ // Set the directory for the command.
+ pc.setDirectory(tokenizer.sval);
+ expecting = (TARGET);
+ }
+ // If we are expecting a target, the record it.
+ else if ((expecting & TARGET) > 0)
+ {
+ // Add current target if there is one.
+ if (currentTargetName != null)
+ {
+ pc.addTarget(currentTargetName, null);
+ }
+ // Set the new target as the current target.
+ currentTargetName = tokenizer.sval;
+ expecting = (EOF | TARGET | VERSION);
+ }
+ else if ((expecting & VERSION_VALUE) > 0)
+ {
+ pc.addTarget(currentTargetName, tokenizer.sval);
+ currentTargetName = null;
+ expecting = (EOF | TARGET);
+ }
+ else
+ {
+ throw new InvalidSyntaxException(
+ "Not expecting '" + tokenizer.sval + "'.", null);
+ }
+ break;
+
+ // Version separator character received.
+ case ';':
+ // Error if we weren't expecting the version separator.
+ if ((expecting & VERSION) == 0)
+ {
+ throw new InvalidSyntaxException(
+ "Not expecting version.", null);
+ }
+ // Otherwise, we will only expect a version value next.
+ expecting = (VERSION_VALUE);
+ break;
+ }
+ }
+ }
+
+ private void help(PrintStream out, StringTokenizer st)
+ {
+ String command = HELP_CMD;
+ if (st.hasMoreTokens())
+ {
+ command = st.nextToken();
+ }
+ if (command.equals(URLS_CMD))
+ {
+ out.println("");
+ out.println("obr " + URLS_CMD + " [<repository-file-url> ...]");
+ out.println("");
+ out.println(
+ "This command gets or sets the URLs to the repository files\n" + "used by OBR. Specify no arguments to get the current repository\n" +
+ "URLs or specify a space-delimited list of URLs to change the\n" +
+ "URLs. Each URL should point to a file containing meta-data about\n" + "available bundles in XML format.");
+ out.println("");
+ }
+ else if (command.equals(LIST_CMD))
+ {
+ out.println("");
+ out.println("obr " + LIST_CMD + " [<string> ...]");
+ out.println("");
+ out.println(
+ "This command lists bundles available in the bundle repository.\n" +
+ "If no arguments are specified, then all available bundles are\n" +
+ "listed, otherwise any arguments are concatenated with spaces\n" +
+ "and used as a substring filter on the bundle names.");
+ out.println("");
+ }
+ else if (command.equals(INFO_CMD))
+ {
+ out.println("");
+ out.println("obr " + INFO_CMD
+ + " <bundle-name>[;<version>] ...");
+ out.println("");
+ out.println(
+ "This command displays the meta-data for the specified bundles.\n" +
+ "If a bundle's name contains spaces, then it must be surrounded\n" +
+ "by quotes. It is also possible to specify a precise version\n" +
+ "if more than one version exists, such as:\n" +
+ "\n" +
+ " obr info \"Bundle Repository\";1.0.0\n" +
+ "\n" +
+ "The above example retrieves the meta-data for version \"1.0.0\"\n" +
+ "of the bundle named \"Bundle Repository\".");
+ out.println("");
+ }
+ else if (command.equals(DEPLOY_CMD))
+ {
+ out.println("");
+ out.println("obr " + DEPLOY_CMD
+ + " [" + NODEPS_SWITCH
+ + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+ out.println("");
+ out.println(
+ "This command tries to install or update the specified bundles\n" +
+ "and all of their dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch\n" +
+ "to ignore dependencies. You can specify either the bundle name or\n" +
+ "the bundle identifier. If a bundle's name contains spaces, then\n" +
+ "it must be surrounded by quotes. It is also possible to specify a\n" + "precise version if more than one version exists, such as:\n" +
+ "\n" +
+ " obr deploy \"Bundle Repository\";1.0.0\n" +
+ "\n" +
+ "For the above example, if version \"1.0.0\" of \"Bundle Repository\" is\n" +
+ "already installed locally, then the command will attempt to update it\n" +
+ "and all of its dependencies; otherwise, the command will install it\n" +
+ "and all of its dependencies.");
+ out.println("");
+ }
+/*
+ else if (command.equals(INSTALL_CMD))
+ {
+ out.println("");
+ out.println("obr " + INSTALL_CMD
+ + " [" + NODEPS_SWITCH
+ + "] <bundle-name>[;<version>] ...");
+ out.println("");
+ out.println(
+ "This command installs the specified bundles and all of their\n" +
+ "dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch to ignore\n" +
+ "dependencies. If a bundle's name contains spaces, then it\n" +
+ "must be surrounded by quotes. If a specified bundle is already\n" + "installed, then this command has no effect. It is also possible\n" + "to specify a precise version if more than one version exists,\n" + "such as:\n" +
+ "\n" +
+ " obr install \"Bundle Repository\";1.0.0\n" +
+ "\n" +
+ "The above example installs version \"1.0.0\" of the bundle\n" +
+ "named \"Bundle Repository\" and its dependencies. ");
+ out.println("");
+ }
+*/
+ else if (command.equals(START_CMD))
+ {
+ out.println("");
+ out.println("obr " + START_CMD
+ + " [" + NODEPS_SWITCH
+ + "] <bundle-name>[;<version>] ...");
+ out.println("");
+ out.println(
+ "This command installs and starts the specified bundles and all\n" +
+ "of their dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch to\n" +
+ "ignore dependencies. If a bundle's name contains spaces, then\n" +
+ "it must be surrounded by quotes. If a specified bundle is already\n" + "installed, then this command has no effect. It is also possible\n" + "to specify a precise version if more than one version exists,\n" + "such as:\n" +
+ "\n" +
+ " obr start \"Bundle Repository\";1.0.0\n" +
+ "\n" +
+ "The above example installs and starts version \"1.0.0\" of the\n" +
+ "bundle named \"Bundle Repository\" and its dependencies.");
+ out.println("");
+ }
+/*
+ else if (command.equals(UPDATE_CMD))
+ {
+ out.println("");
+ out.println("obr " + UPDATE_CMD + " " + CHECK_SWITCH);
+ out.println("");
+ out.println("obr " + UPDATE_CMD
+ + " [" + NODEPS_SWITCH
+ + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+ out.println("");
+ out.println(
+ "The first form of the command above checks for available updates\n" + "and the second updates the specified locally installed bundles\n" +
+ "and all of their dependencies by default; use the \"" + NODEPS_SWITCH + "\" switch\n" +
+ "to ignore dependencies. You can specify either the bundle name or\n" +
+ "the bundle identifier. If a bundle's name contains spaces, then\n" +
+ "it must be surrounded by quotes. If a specified bundle is not\n" + "already installed, then this command has no effect. It is also\n" + "possible to specify a precise version if more than one version\n" + "exists, such as:\n" +
+ "\n" +
+ " obr update \"Bundle Repository\";1.0.0\n" +
+ "\n" +
+ "The above example updates version \"1.0.0\" of the bundle named\n" +
+ "\"Bundle Repository\" and its dependencies. The update command may\n" +
+ "install new bundles if the updated bundles have new dependencies.");
+ out.println("");
+ }
+*/
+ else if (command.equals(SOURCE_CMD))
+ {
+ out.println("");
+ out.println("obr " + SOURCE_CMD
+ + " [" + EXTRACT_SWITCH
+ + "] <local-dir> <bundle-name>[;<version>] ...");
+ out.println("");
+ out.println(
+ "This command retrieves the source archives of the specified\n" +
+ "bundles and saves them to the specified local directory; use\n" +
+ "the \"" + EXTRACT_SWITCH + "\" switch to automatically extract the source archives.\n" +
+ "If a bundle name contains spaces, then it must be surrounded\n" +
+ "by quotes. It is also possible to specify a precise version if\n" + "more than one version exists, such as:\n" +
+ "\n" +
+ " obr source /home/rickhall/tmp \"Bundle Repository\";1.0.0\n" +
+ "\n" +
+ "The above example retrieves the source archive of version \"1.0.0\"\n" +
+ "of the bundle named \"Bundle Repository\" and saves it to the\n" +
+ "specified local directory.");
+ out.println("");
+ }
+ else
+ {
+ out.println("obr " + HELP_CMD
+ + " [" + URLS_CMD + " | " + LIST_CMD
+// + " | " + INFO_CMD + " | " + INSTALL_CMD
+ + " | " + INFO_CMD
+ + " | " + DEPLOY_CMD + " | " + START_CMD
+// + " | " + UPDATE_CMD + " | " + SOURCE_CMD + "]");
+ + " | " + SOURCE_CMD + "]");
+ out.println("obr " + URLS_CMD + " [<repository-file-url> ...]");
+ out.println("obr " + LIST_CMD + " [<string> ...]");
+ out.println("obr " + INFO_CMD
+ + " <bundle-name>[;<version>] ...");
+ out.println("obr " + DEPLOY_CMD
+ + " [" + NODEPS_SWITCH
+ + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+// out.println("obr " + INSTALL_CMD
+// + " [" + NODEPS_SWITCH
+// + "] <bundle-name>[;<version>] ...");
+ out.println("obr " + START_CMD
+ + " [" + NODEPS_SWITCH
+ + "] <bundle-name>[;<version>] ...");
+// out.println("obr " + UPDATE_CMD + " " + CHECK_SWITCH);
+// out.println("obr " + UPDATE_CMD
+// + " [" + NODEPS_SWITCH
+// + "] <bundle-name>[;<version>] ... | <bundle-id> ...");
+ out.println("obr " + SOURCE_CMD
+ + " [" + EXTRACT_SWITCH
+ + "] <local-dir> <bundle-name>[;<version>] ...");
+ }
+ }
+
+ private static class ParsedCommand
+ {
+ private static final int NAME_IDX = 0;
+ private static final int VERSION_IDX = 1;
+
+ private boolean m_isResolve = true;
+ private boolean m_isCheck = false;
+ private boolean m_isExtract = false;
+ private String m_dir = null;
+ private String[][] m_targets = new String[0][];
+
+ public boolean isResolve()
+ {
+ return m_isResolve;
+ }
+
+ public void setResolve(boolean b)
+ {
+ m_isResolve = b;
+ }
+
+ public boolean isCheck()
+ {
+ return m_isCheck;
+ }
+
+ public void setCheck(boolean b)
+ {
+ m_isCheck = b;
+ }
+
+ public boolean isExtract()
+ {
+ return m_isExtract;
+ }
+
+ public void setExtract(boolean b)
+ {
+ m_isExtract = b;
+ }
+
+ public String getDirectory()
+ {
+ return m_dir;
+ }
+
+ public void setDirectory(String s)
+ {
+ m_dir = s;
+ }
+
+ public int getTargetCount()
+ {
+ return m_targets.length;
+ }
+
+ public String getTargetId(int i)
+ {
+ if ((i < 0) || (i >= getTargetCount()))
+ {
+ return null;
+ }
+ return m_targets[i][NAME_IDX];
+ }
+
+ public String getTargetVersion(int i)
+ {
+ if ((i < 0) || (i >= getTargetCount()))
+ {
+ return null;
+ }
+ return m_targets[i][VERSION_IDX];
+ }
+
+ public void addTarget(String name, String version)
+ {
+ String[][] newTargets = new String[m_targets.length + 1][];
+ System.arraycopy(m_targets, 0, newTargets, 0, m_targets.length);
+ newTargets[m_targets.length] = new String[] { name, version };
+ m_targets = newTargets;
+ }
+ }
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java b/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java
new file mode 100644
index 0000000..d100711
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Attribute.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import org.apache.osgi.service.bundlerepository.IAttribute;
+
+public class R4Attribute implements IAttribute
+{
+ private String m_name = "";
+ private String m_value = "";
+ private boolean m_isMandatory = false;
+
+ public R4Attribute(String name, String value, boolean isMandatory)
+ {
+ m_name = name;
+ m_value = value;
+ m_isMandatory = isMandatory;
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Attribute#getName()
+ **/
+ public String getName()
+ {
+ return m_name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Attribute#getValue()
+ **/
+ public String getValue()
+ {
+ return m_value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Attribute#isMandatory()
+ **/
+ public boolean isMandatory()
+ {
+ return m_isMandatory;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java b/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java
new file mode 100644
index 0000000..719dd89
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Directive.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import org.apache.osgi.service.bundlerepository.IDirective;
+
+public class R4Directive implements IDirective
+{
+ private String m_name = "";
+ private String m_value = "";
+
+ public R4Directive(String name, String value)
+ {
+ m_name = name;
+ m_value = value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Directive#getName()
+ **/
+ public String getName()
+ {
+ return m_name;
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Directive#getValue()
+ **/
+ public String getValue()
+ {
+ return m_value;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Package.java b/src/org/apache/osgi/bundle/bundlerepository/R4Package.java
new file mode 100644
index 0000000..a055133
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Package.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import java.util.*;
+
+import org.apache.osgi.service.bundlerepository.*;
+import org.osgi.framework.Constants;
+
+//
+// This class is essentially the same as the R4Package class in Felix,
+// except that I had to add the parseDelimitedString() method. These
+// two classes should be unified.
+//
+
+/**
+ * This is a simple class to encapsulate a package declaration for
+ * bundle imports and exports for the bundle repository.
+**/
+public class R4Package implements IPackage
+{
+ private String m_id = "";
+ private IDirective[] m_directives = null;
+ private IAttribute[] m_attrs = null;
+ private IVersion m_versionLow = null;
+ private IVersion m_versionHigh = null;
+ private boolean m_isOptional = false;
+
+ protected R4Package(R4Package pkg)
+ {
+ m_id = pkg.m_id;
+ m_directives = pkg.m_directives;
+ m_attrs = pkg.m_attrs;
+ m_versionLow = pkg.m_versionLow;
+ m_versionHigh = pkg.m_versionHigh;
+ m_isOptional = pkg.m_isOptional;
+ }
+
+ public R4Package(String id, IDirective[] directives, IAttribute[] attrs)
+ {
+ m_id = id;
+ m_directives = (directives == null) ? new IDirective[0] : directives;
+ m_attrs = (attrs == null) ? new IAttribute[0] : attrs;
+
+ // Find mandatory and resolution directives, if present.
+ String mandatory = "";
+ for (int i = 0; i < m_directives.length; i++)
+ {
+ if (m_directives[i].getName().equals(Constants.MANDATORY_DIRECTIVE))
+ {
+ mandatory = m_directives[i].getValue();
+ }
+ else if (m_directives[i].getName().equals(Constants.RESOLUTION_DIRECTIVE))
+ {
+ m_isOptional = m_directives[i].getValue().equals(Constants.RESOLUTION_OPTIONAL);
+ }
+ }
+
+ // Parse mandatory directive and mark specified
+ // attributes as mandatory.
+ StringTokenizer tok = new StringTokenizer(mandatory, "");
+ while (tok.hasMoreTokens())
+ {
+ // Get attribute name.
+ String attrName = tok.nextToken().trim();
+ // Find attribute and mark it as mandatory.
+ boolean found = false;
+ for (int i = 0; (!found) && (i < m_attrs.length); i++)
+ {
+ if (m_attrs[i].getName().equals(attrName))
+ {
+ m_attrs[i] = new R4Attribute(
+ m_attrs[i].getName(), m_attrs[i].getValue(), true);
+ found = true;
+ }
+ }
+ // If a specified mandatory attribute was not found,
+ // then error.
+ if (!found)
+ {
+ throw new IllegalArgumentException(
+ "Mandatory attribute '" + attrName + "' does not exist.");
+ }
+ }
+
+ // Find and parse version attribute, if present.
+ String versionInterval = "0.0.0";
+ for (int i = 0; i < m_attrs.length; i++)
+ {
+ if (m_attrs[i].getName().equals(Constants.VERSION_ATTRIBUTE) ||
+ m_attrs[i].getName().equals(Constants.PACKAGE_SPECIFICATION_VERSION))
+ {
+ // Normalize version attribute name.
+ m_attrs[i] = new R4Attribute(
+ Constants.VERSION_ATTRIBUTE, m_attrs[i].getValue(),
+ m_attrs[i].isMandatory());
+ versionInterval = m_attrs[i].getValue();
+ break;
+ }
+ }
+
+ IVersion[] versions = parseVersionInterval(versionInterval);
+ m_versionLow = versions[0];
+ if (versions.length == 2)
+ {
+ m_versionHigh = versions[1];
+ }
+ }
+
+ public String getId()
+ {
+ return m_id;
+ }
+
+ public IDirective[] getDirectives()
+ {
+ return m_directives;
+ }
+
+ public IAttribute[] getAttributes()
+ {
+ return m_attrs;
+ }
+
+ public IVersion getVersionLow()
+ {
+ return m_versionLow;
+ }
+
+ public IVersion getVersionHigh()
+ {
+ return m_versionHigh;
+ }
+
+ public boolean isOptional()
+ {
+ return m_isOptional;
+ }
+
+ // PREVIOUSLY PART OF COMPATIBILITY POLICY.
+ public boolean doesSatisfy(IPackage pkg)
+ {
+ // For packages to be compatible, they must have the
+ // same name.
+ if (!m_id.equals(pkg.getId()))
+ {
+ return false;
+ }
+
+ return isVersionInRange(m_versionLow, pkg.getVersionLow(), pkg.getVersionHigh())
+ && doAttributesMatch(pkg);
+ }
+
+ // PREVIOUSLY PART OF COMPATIBILITY POLICY.
+ public static boolean isVersionInRange(IVersion version, IVersion low, IVersion high)
+ {
+ // We might not have an upper end to the range.
+ if (high == null)
+ {
+ return (version.compareTo(low) >= 0);
+ }
+ else if (low.isInclusive() && high.isInclusive())
+ {
+ return (version.compareTo(low) >= 0) && (version.compareTo(high) <= 0);
+ }
+ else if (high.isInclusive())
+ {
+ return (version.compareTo(low) > 0) && (version.compareTo(high) <= 0);
+ }
+ else if (low.isInclusive())
+ {
+ return (version.compareTo(low) >= 0) && (version.compareTo(high) < 0);
+ }
+
+ return (version.compareTo(low) > 0) && (version.compareTo(high) < 0);
+ }
+
+ private boolean doAttributesMatch(IPackage pkg)
+ {
+ // Cycle through all attributes of the specified package
+ // and make sure their values match the attribute values
+ // of this package.
+ for (int attrIdx = 0; attrIdx < pkg.getAttributes().length; attrIdx++)
+ {
+ // Get current attribute from specified package.
+ IAttribute attr = pkg.getAttributes()[attrIdx];
+
+ // Ignore version attribute, since it is a special case that
+ // has already been compared using isVersionInRange() before
+ // the call to this method was made.
+ if (attr.getName().equals(Constants.VERSION_ATTRIBUTE))
+ {
+ continue;
+ }
+
+ // Check if this package has the same attribute.
+ boolean found = false;
+ for (int thisAttrIdx = 0;
+ (!found) && (thisAttrIdx < m_attrs.length);
+ thisAttrIdx++)
+ {
+ // Get current attribute for this package.
+ IAttribute thisAttr = m_attrs[thisAttrIdx];
+ // Check if the attribute names are equal.
+ if (attr.getName().equals(thisAttr.getName()))
+ {
+ // If the values are not equal, then return false immediately.
+ // We should not compare version values here, since they are
+ // a special case and have already been compared by a call to
+ // isVersionInRange() before getting here; however, it is
+ // possible for version to be mandatory, so make sure it is
+ // present below.
+ if (!attr.getValue().equals(thisAttr.getValue()))
+ {
+ return false;
+ }
+ found = true;
+ }
+ }
+ // If the attribute was not found, then return false.
+ if (!found)
+ {
+ return false;
+ }
+ }
+
+ // Now, cycle through all attributes of this package and verify that
+ // all mandatory attributes are present in the speceified package.
+ for (int thisAttrIdx = 0; thisAttrIdx < m_attrs.length; thisAttrIdx++)
+ {
+ // Get current attribute for this package.
+ IAttribute thisAttr = m_attrs[thisAttrIdx];
+
+ // If the attribute is mandatory, then make sure
+ // the specified package has the attribute.
+ if (thisAttr.isMandatory())
+ {
+ boolean found = false;
+ for (int attrIdx = 0;
+ (!found) && (attrIdx < pkg.getAttributes().length);
+ attrIdx++)
+ {
+ // Get current attribute from specified package.
+ IAttribute attr = pkg.getAttributes()[attrIdx];
+
+ // Check if the attribute names are equal
+ // and set found flag.
+ if (thisAttr.getName().equals(attr.getName()))
+ {
+ found = true;
+ }
+ }
+ // If not found, then return false.
+ if (!found)
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public String toString()
+ {
+ String msg = getId();
+ for (int i = 0; (m_directives != null) && (i < m_directives.length); i++)
+ {
+ msg = msg + " [" + m_directives[i].getName() + ":="+ m_directives[i].getName() + "]";
+ }
+ for (int i = 0; (m_attrs != null) && (i < m_attrs.length); i++)
+ {
+ msg = msg + " [" + m_attrs[i].getValue() + "="+ m_attrs[i].getValue() + "]";
+ }
+ return msg;
+ }
+
+ // Like this: pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2,
+ // pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+ public static IPackage[] parseImportOrExportHeader(String s)
+ {
+ IPackage[] pkgs = null;
+ if (s != null)
+ {
+ if (s.length() == 0)
+ {
+ throw new IllegalArgumentException(
+ "The import and export headers cannot be an empty string.");
+ }
+ String[] ss = parseDelimitedString(s, ","); // FelixConstants.CLASS_PATH_SEPARATOR
+ pkgs = parsePackageStrings(ss);
+ }
+ return (pkgs == null) ? new IPackage[0] : pkgs;
+ }
+
+ // Like this: pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+ public static IPackage[] parsePackageStrings(String[] ss)
+ throws IllegalArgumentException
+ {
+ if (ss == null)
+ {
+ return null;
+ }
+
+ List completeList = new ArrayList();
+ for (int ssIdx = 0; ssIdx < ss.length; ssIdx++)
+ {
+ // Break string into semi-colon delimited pieces.
+ String[] pieces = parseDelimitedString(
+ ss[ssIdx], ";"); // FelixConstants.PACKAGE_SEPARATOR
+
+ // Count the number of different packages; packages
+ // will not have an '=' in their string. This assumes
+ // that packages come first, before directives and
+ // attributes.
+ int pkgCount = 0;
+ for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++)
+ {
+ if (pieces[pieceIdx].indexOf('=') >= 0)
+ {
+ break;
+ }
+ pkgCount++;
+ }
+
+ // Error if no packages were specified.
+ if (pkgCount == 0)
+ {
+ throw new IllegalArgumentException(
+ "No packages specified on import: " + ss[ssIdx]);
+ }
+
+ // Parse the directives/attributes.
+ IDirective[] dirs = new IDirective[pieces.length - pkgCount];
+ IAttribute[] attrs = new IAttribute[pieces.length - pkgCount];
+ int dirCount = 0, attrCount = 0;
+ int idx = -1;
+ String sep = null;
+ for (int pieceIdx = pkgCount; pieceIdx < pieces.length; pieceIdx++)
+ {
+ // Check if it is a directive.
+ if ((idx = pieces[pieceIdx].indexOf(":=")) >= 0) // FelixConstants.DIRECTIVE_SEPARATOR
+ {
+ sep = ":="; // FelixConstants.DIRECTIVE_SEPARATOR
+ }
+ // Check if it is an attribute.
+ else if ((idx = pieces[pieceIdx].indexOf("=")) >= 0) // FelixConstants.ATTRIBUTE_SEPARATOR
+ {
+ sep = "="; // FelixConstants.ATTRIBUTE_SEPARATOR
+ }
+ // It is an error.
+ else
+ {
+ throw new IllegalArgumentException(
+ "Not a directive/attribute: " + ss[ssIdx]);
+ }
+
+ String key = pieces[pieceIdx].substring(0, idx).trim();
+ String value = pieces[pieceIdx].substring(idx + sep.length()).trim();
+
+ // Remove quotes, if value is quoted.
+ if (value.startsWith("\"") && value.endsWith("\""))
+ {
+ value = value.substring(1, value.length() - 1);
+ }
+
+ // Save the directive/attribute in the appropriate array.
+ if (sep.equals(":=")) // FelixConstants.DIRECTIVE_SEPARATOR
+ {
+ dirs[dirCount++] = new R4Directive(key, value);
+ }
+ else
+ {
+ attrs[attrCount++] = new R4Attribute(key, value, false);
+ }
+ }
+
+ // Shrink directive array.
+ IDirective[] dirsFinal = new IDirective[dirCount];
+ System.arraycopy(dirs, 0, dirsFinal, 0, dirCount);
+ // Shrink attribute array.
+ IAttribute[] attrsFinal = new IAttribute[attrCount];
+ System.arraycopy(attrs, 0, attrsFinal, 0, attrCount);
+
+ // Create package attributes for each package and
+ // set directives/attributes. Add each package to
+ // completel list of packages.
+ IPackage[] pkgs = new IPackage[pkgCount];
+ for (int pkgIdx = 0; pkgIdx < pkgCount; pkgIdx++)
+ {
+ pkgs[pkgIdx] = new R4Package(pieces[pkgIdx], dirsFinal, attrsFinal);
+ completeList.add(pkgs[pkgIdx]);
+ }
+ }
+
+ IPackage[] ips = (IPackage[])
+ completeList.toArray(new IPackage[completeList.size()]);
+ return ips;
+ }
+
+ public static IVersion[] parseVersionInterval(String interval)
+ {
+ // Check if the version is an interval.
+ if (interval.indexOf(',') >= 0)
+ {
+ String s = interval.substring(1, interval.length() - 1);
+ String vlo = s.substring(0, s.indexOf(','));
+ String vhi = s.substring(s.indexOf(',') + 1, s.length());
+ return new IVersion[] {
+ new R4Version(vlo, (interval.charAt(0) == '[')),
+ new R4Version(vhi, (interval.charAt(interval.length() - 1) == ']'))
+ };
+ }
+ else
+ {
+ return new IVersion[] { new R4Version(interval, true) };
+ }
+ }
+
+ /**
+ * Parses delimited string and returns an array containing the tokens. This
+ * parser obeys quotes, so the delimiter character will be ignored if it is
+ * inside of a quote. This method assumes that the quote character is not
+ * included in the set of delimiter characters.
+ * @param value the delimited string to parse.
+ * @param delim the characters delimiting the tokens.
+ * @return an array of string tokens or null if there were no tokens.
+ **/
+ public static String[] parseDelimitedString(String value, String delim)
+ {
+ if (value == null)
+ {
+ value = "";
+ }
+
+ List list = new ArrayList();
+
+ int CHAR = 1;
+ int DELIMITER = 2;
+ int STARTQUOTE = 4;
+ int ENDQUOTE = 8;
+
+ StringBuffer sb = new StringBuffer();
+
+ int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+ for (int i = 0; i < value.length(); i++)
+ {
+ char c = value.charAt(i);
+
+ boolean isDelimiter = (delim.indexOf(c) >= 0);
+ boolean isQuote = (c == '"');
+
+ if (isDelimiter && ((expecting & DELIMITER) > 0))
+ {
+ list.add(sb.toString().trim());
+ sb.delete(0, sb.length());
+ expecting = (CHAR | DELIMITER | STARTQUOTE);
+ }
+ else if (isQuote && ((expecting & STARTQUOTE) > 0))
+ {
+ sb.append(c);
+ expecting = CHAR | ENDQUOTE;
+ }
+ else if (isQuote && ((expecting & ENDQUOTE) > 0))
+ {
+ sb.append(c);
+ expecting = (CHAR | STARTQUOTE | DELIMITER);
+ }
+ else if ((expecting & CHAR) > 0)
+ {
+ sb.append(c);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid delimited string: " + value);
+ }
+ }
+
+ if (sb.length() > 0)
+ {
+ list.add(sb.toString().trim());
+ }
+
+ return (String[]) list.toArray(new String[list.size()]);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/R4Version.java b/src/org/apache/osgi/bundle/bundlerepository/R4Version.java
new file mode 100644
index 0000000..eb44a9a
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/R4Version.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.bundlerepository.IVersion;
+
+public class R4Version implements Comparable, IVersion
+{
+ private int m_major = 0;
+ private int m_minor = 0;
+ private int m_micro = 0;
+ private String m_qualifier = "";
+ private boolean m_isInclusive = true;
+
+ private static final String SEPARATOR = ".";
+
+ public R4Version(String versionString)
+ {
+ this(versionString, true);
+ }
+
+ public R4Version(String versionString, boolean isInclusive)
+ {
+ if (versionString == null)
+ {
+ versionString = "0.0.0";
+ }
+ Object[] objs = parseVersion(versionString);
+ m_major = ((Integer) objs[0]).intValue();
+ m_minor = ((Integer) objs[1]).intValue();
+ m_micro = ((Integer) objs[2]).intValue();
+ m_qualifier = (String) objs[3];
+ m_isInclusive = isInclusive;
+ }
+
+ private static Object[] parseVersion(String versionString)
+ {
+ String s = versionString.trim();
+ Object[] objs = new Object[4];
+ objs[0] = objs[1] = objs[2] = new Integer(0);
+ objs[3] = "";
+ StringTokenizer tok = new StringTokenizer(s, SEPARATOR);
+ try
+ {
+ objs[0] = Integer.valueOf(tok.nextToken());
+ if (tok.hasMoreTokens())
+ {
+ objs[1] = Integer.valueOf(tok.nextToken());
+ if (tok.hasMoreTokens())
+ {
+ objs[2] = Integer.valueOf(tok.nextToken());
+ if (tok.hasMoreTokens())
+ {
+ objs[3] = tok.nextToken();
+ }
+ }
+ }
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new IllegalArgumentException("Invalid version: " + versionString);
+ }
+
+ if ((((Integer) objs[0]).intValue() < 0) ||
+ (((Integer) objs[0]).intValue() < 0) ||
+ (((Integer) objs[0]).intValue() < 0))
+ {
+ throw new IllegalArgumentException("Invalid version: " + versionString);
+ }
+
+ return objs;
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Version#equals(java.lang.Object)
+ **/
+ public boolean equals(Object object)
+ {
+ if (!(object instanceof R4Version))
+ {
+ return false;
+ }
+ IVersion v = (IVersion) object;
+ return
+ (v.getMajorComponent() == m_major) &&
+ (v.getMinorComponent() == m_minor) &&
+ (v.getMicroComponent() == m_micro) &&
+ (v.getQualifierComponent().equals(m_qualifier));
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Version#getMajorComponent()
+ **/
+ public int getMajorComponent()
+ {
+ return m_major;
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Version#getMinorComponent()
+ **/
+ public int getMinorComponent()
+ {
+ return m_minor;
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Version#getMicroComponent()
+ **/
+ public int getMicroComponent()
+ {
+ return m_micro;
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Version#getQualifierComponent()
+ **/
+ public String getQualifierComponent()
+ {
+ return m_qualifier;
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Version#isInclusive()
+ **/
+ public boolean isInclusive()
+ {
+ return m_isInclusive;
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Version#compareTo(java.lang.Object)
+ **/
+ public int compareTo(Object o)
+ {
+ if (!(o instanceof R4Version))
+ throw new ClassCastException();
+
+ if (equals(o))
+ return 0;
+
+ if (isGreaterThan((IVersion) o))
+ return 1;
+
+ return -1;
+ }
+
+ private boolean isGreaterThan(IVersion v)
+ {
+ if (v == null)
+ {
+ return false;
+ }
+
+ if (m_major > v.getMajorComponent())
+ {
+ return true;
+ }
+ if (m_major < v.getMajorComponent())
+ {
+ return false;
+ }
+ if (m_minor > v.getMinorComponent())
+ {
+ return true;
+ }
+ if (m_minor < v.getMinorComponent())
+ {
+ return false;
+ }
+ if (m_micro > v.getMicroComponent())
+ {
+ return true;
+ }
+ if (m_micro < v.getMicroComponent())
+ {
+ return false;
+ }
+ if (m_qualifier.compareTo(v.getQualifierComponent()) > 0)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.ungoverned.osgi.service.bundlerepository.Version#toString()
+ **/
+ public String toString()
+ {
+ if (m_qualifier.length() == 0)
+ {
+ return m_major + "." + m_minor + "." + m_micro;
+ }
+ return m_major + "." + m_minor + "." + m_micro + "." + m_qualifier;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/RepositoryState.java b/src/org/apache/osgi/bundle/bundlerepository/RepositoryState.java
new file mode 100644
index 0000000..b8b52d8
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/RepositoryState.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import org.apache.osgi.bundle.bundlerepository.kxmlsax.KXmlSAXParser;
+import org.apache.osgi.bundle.bundlerepository.metadataparser.MultivalueMap;
+import org.apache.osgi.bundle.bundlerepository.metadataparser.XmlCommonHandler;
+import org.apache.osgi.service.bundlerepository.BundleRecord;
+import org.apache.osgi.service.bundlerepository.ResolveException;
+import org.osgi.framework.*;
+
+public class RepositoryState
+{
+ private BundleContext m_context = null;
+ private String[] m_urls = null;
+ private Map m_recordMap = new HashMap();
+ private BundleRecord[] m_recordArray = null;
+ private boolean m_initialized = false;
+
+ private int m_hopCount = 1;
+
+ private static final String[] DEFAULT_REPOSITORY_URL = {
+ "http://oscar-osgi.sf.net/alpha/repository.xml"
+ };
+ public static final String REPOSITORY_URL_PROP = "osgi.repository.url";
+ public static final String EXTERN_REPOSITORY_TAG = "extern-repositories";
+
+ public RepositoryState(BundleContext context)
+ {
+ m_context = context;
+
+ String urlStr = m_context.getProperty(REPOSITORY_URL_PROP);
+ if (urlStr != null)
+ {
+ StringTokenizer st = new StringTokenizer(urlStr);
+ if (st.countTokens() > 0)
+ {
+ m_urls = new String[st.countTokens()];
+ for (int i = 0; i < m_urls.length; i++)
+ {
+ m_urls[i] = st.nextToken();
+ }
+ }
+ }
+
+ // Use the default URL if none were specified.
+ if (m_urls == null)
+ {
+ m_urls = DEFAULT_REPOSITORY_URL;
+ }
+ }
+
+ public String[] getURLs()
+ {
+ // Return a copy because the array is mutable.
+ return (m_urls == null) ? null : (String[]) m_urls.clone();
+ }
+
+ public void setURLs(String[] urls)
+ {
+ if (urls != null)
+ {
+ m_urls = urls;
+ initialize();
+ }
+ }
+
+ public BundleRecord[] getRecords()
+ {
+ if (!m_initialized)
+ {
+ initialize();
+ }
+
+ // Returned cached array of bundle records.
+ return m_recordArray;
+ }
+
+ public BundleRecord[] getRecords(String symName)
+ {
+ if (!m_initialized)
+ {
+ initialize();
+ }
+
+ // Return a copy of the array, since it would be mutable
+ // otherwise.
+ BundleRecord[] records = (BundleRecord[]) m_recordMap.get(symName);
+ // Return a copy because the array is mutable.
+ return (records == null) ? null : (BundleRecord[]) records.clone();
+ }
+
+ public BundleRecord getRecord(String symName, int[] version)
+ {
+ if (!m_initialized)
+ {
+ initialize();
+ }
+
+ BundleRecord[] records = (BundleRecord[]) m_recordMap.get(symName);
+ if ((records != null) && (records.length > 0))
+ {
+ for (int i = 0; i < records.length; i++)
+ {
+ int[] targetVersion = Util.parseVersionString(
+ (String) records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+
+ if (Util.compareVersion(targetVersion, version) == 0)
+ {
+ return records[i];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public BundleRecord[] resolvePackages(LocalState localState, Filter[] reqFilters)
+ throws ResolveException
+ {
+ // Create a list that will contain the transitive closure of
+ // all import dependencies; use a list because this will keep
+ // everything in order.
+ List deployList = new ArrayList();
+ // Add the target bundle
+ resolvePackages(localState, reqFilters, deployList);
+
+ // Convert list of symbolic names to an array of bundle
+ // records and return it.
+ BundleRecord[] records = new BundleRecord[deployList.size()];
+ return (BundleRecord[]) deployList.toArray(records);
+ }
+
+ private void resolvePackages(
+ LocalState localState, Filter[] reqFilters, List deployList)
+ throws ResolveException
+ {
+ for (int reqIdx = 0;
+ (reqFilters != null) && (reqIdx < reqFilters.length);
+ reqIdx++)
+ {
+ // If the package can be locally resolved, then
+ // it can be completely ignored; otherwise, try
+ // to find a resolving bundle.
+ if (!localState.isResolvable(reqFilters[reqIdx]))
+ {
+ // Select resolving bundle for current package.
+ BundleRecord source = selectResolvingBundle(
+ deployList, localState, reqFilters[reqIdx]);
+ // If there is no resolving bundle, then throw a
+ // resolve exception.
+ if (source == null)
+ {
+throw new IllegalArgumentException("HACK: SHOULD THROW RESOLVE EXCEPTION: " + reqFilters[reqIdx]);
+// throw new ResolveException(reqFilters[reqIdx]);
+ }
+ // If the resolving bundle is already in the deploy list,
+ // then just ignore it; otherwise, add it to the deploy
+ // list and resolve its packages.
+ if (!deployList.contains(source))
+ {
+ deployList.add(source);
+ Filter[] filters = (Filter[])
+ source.getAttribute("requirements");
+ resolvePackages(localState, filters, deployList);
+ }
+ }
+ }
+ }
+
+ /**
+ * Selects a single source bundle record for the target package from
+ * the repository. The algorithm tries to select a source bundle record
+ * if it is already installed locally in the framework; this approach
+ * favors updating already installed bundles rather than installing
+ * new ones. If no matching bundles are installed locally, then the
+ * first bundle record providing the target package is returned.
+ * @param targetPkg the target package for which to select a source
+ * bundle record.
+ * @return the selected bundle record or <tt>null</tt> if no sources
+ * could be found.
+ **/
+ private BundleRecord selectResolvingBundle(
+ List deployList, LocalState localState, Filter targetFilter)
+ {
+ BundleRecord[] exporters = findExporters(targetFilter);
+ if (exporters == null)
+ {
+ return null;
+ }
+
+ // Try to select a source bundle record that is already
+ // in the deployed list to minimize the number of bundles
+ // that need to be deployed. If this is not possible, then
+ // try to select a bundle that is already installed locally,
+ // since it might be possible to update this bundle to
+ // minimize the number of bundles installed in the framework.
+ for (int i = 0; i < exporters.length; i++)
+ {
+ if (deployList.contains(exporters[i]))
+ {
+ return exporters[i];
+ }
+ else
+ {
+ String symName = (String)
+ exporters[i].getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME);
+ if (symName != null)
+ {
+ BundleRecord[] records = localState.findBundles(symName);
+ if (records != null)
+ {
+ return exporters[i];
+ }
+ }
+ }
+ }
+
+ // If none of the sources are installed locally, then
+ // just pick the first one.
+ return exporters[0];
+ }
+
+ /**
+ * Returns an array of bundle records that resolve the supplied
+ * package declaration.
+ * @param target the package declaration to resolve.
+ * @return an array of bundle records that resolve the package
+ * declaration or <tt>null</tt> if none are found.
+ **/
+ private BundleRecord[] findExporters(Filter targetFilter)
+ {
+ MapToDictionary mapDict = new MapToDictionary(null);
+
+ // Create a list for storing bundles that can resolve package.
+ List resolveList = new ArrayList();
+ for (int recIdx = 0; recIdx < m_recordArray.length; recIdx++)
+ {
+ Map[] capMaps = (Map[]) m_recordArray[recIdx].getAttribute("capability");
+ for (int capIdx = 0; capIdx < capMaps.length; capIdx++)
+ {
+ mapDict.setSourceMap(capMaps[capIdx]);
+ if (targetFilter.match(mapDict))
+ {
+ resolveList.add(m_recordArray[recIdx]);
+ }
+ }
+ }
+
+ // If no resolving bundles were found, return null.
+ if (resolveList.size() == 0)
+ {
+ return null;
+ }
+
+ // Otherwise, return an array containing resolving bundles.
+ return (BundleRecord[]) resolveList.toArray(new BundleRecord[resolveList.size()]);
+ }
+
+ private boolean isUpdateAvailable(
+ PrintStream out, PrintStream err, Bundle bundle)
+ {
+ // Get the bundle's update location.
+ String symname =
+ (String) bundle.getHeaders().get(BundleRecord.BUNDLE_SYMBOLICNAME);
+
+ // Get associated repository bundle recorded for the
+ // local bundle and see if an update is necessary.
+ BundleRecord[] records = getRecords(symname);
+ if (records == null)
+ {
+ err.println(Util.getBundleName(bundle) + " not in repository.");
+ return false;
+ }
+
+ // Check bundle version againts bundle record version.
+ for (int i = 0; i < records.length; i++)
+ {
+ int[] bundleVersion = Util.parseVersionString(
+ (String) bundle.getHeaders().get(BundleRecord.BUNDLE_VERSION));
+ int[] recordVersion = Util.parseVersionString(
+ (String) records[i].getAttribute(BundleRecord.BUNDLE_VERSION));
+ if (Util.compareVersion(recordVersion, bundleVersion) > 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void initialize()
+ {
+ m_initialized = true;
+ m_recordMap.clear();
+
+ for (int urlIdx = 0; (m_urls != null) && (urlIdx < m_urls.length); urlIdx++)
+ {
+ parseRepositoryFile(m_hopCount, m_urls[urlIdx]);
+ }
+
+ // Cache a sorted array of all bundle records.
+ List list = new ArrayList();
+ for (Iterator i = m_recordMap.entrySet().iterator(); i.hasNext(); )
+ {
+ BundleRecord[] records = (BundleRecord[]) ((Map.Entry) i.next()).getValue();
+ for (int recIdx = 0; recIdx < records.length; recIdx++)
+ {
+ list.add(records[recIdx]);
+ }
+ }
+ m_recordArray = (BundleRecord[]) list.toArray(new BundleRecord[list.size()]);
+ Arrays.sort(m_recordArray, new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ BundleRecord r1 = (BundleRecord) o1;
+ BundleRecord r2 = (BundleRecord) o2;
+ String name1 = (String) r1.getAttribute(BundleRecord.BUNDLE_NAME);
+ String name2 = (String) r2.getAttribute(BundleRecord.BUNDLE_NAME);
+ return name1.compareToIgnoreCase(name2);
+ }
+ });
+ }
+
+ private void parseRepositoryFile(int hopCount, String urlStr)
+ {
+ InputStream is = null;
+ BufferedReader br = null;
+
+ try
+ {
+ // Do it the manual way to have a chance to
+ // set request properties as proxy auth (EW).
+ URL url = new URL(urlStr);
+ URLConnection conn = url.openConnection();
+
+ // Support for http proxy authentication
+ String auth = System.getProperty("http.proxyAuth");
+ if ((auth != null) && (auth.length() > 0))
+ {
+ if ("http".equals(url.getProtocol()) ||
+ "https".equals(url.getProtocol()))
+ {
+ String base64 = Util.base64Encode(auth);
+ conn.setRequestProperty(
+ "Proxy-Authorization", "Basic " + base64);
+ }
+ }
+ is = conn.getInputStream();
+
+ // Create the parser Kxml
+ XmlCommonHandler handler = new XmlCommonHandler();
+ handler.addType("bundles", ArrayList.class);
+ handler.addType("repository", HashMap.class);
+ handler.addType("extern-repositories", ArrayList.class);
+ handler.addType("bundle", MultivalueMap.class);
+ handler.addType("requirement", String.class);
+ handler.addType("capability", ArrayList.class);
+ handler.addType("property", HashMap.class);
+ handler.setDefaultType(String.class);
+
+ br = new BufferedReader(new InputStreamReader(is));
+ KXmlSAXParser parser;
+ parser = new KXmlSAXParser(br);
+ try
+ {
+ parser.parseXML(handler);
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ return;
+ }
+
+ List root = (List) handler.getRoot();
+ for (int bundleIdx = 0; bundleIdx < root.size(); bundleIdx++)
+ {
+ Object obj = root.get(bundleIdx);
+
+ // The elements of the root will either be a HashMap for
+ // the repository tag or a MultivalueMap for the bundle
+ // tag, as indicated above when we parsed the file.
+
+ // If HashMap, then read repository information.
+ if (obj instanceof HashMap)
+ {
+ // Create a case-insensitive map.
+ Map repoMap = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+ repoMap.putAll((Map) obj);
+
+ // Process external repositories if hop count is
+ // greater than zero.
+ if (hopCount > 0)
+ {
+ // Get the external repository list.
+ List externList = (List) repoMap.get(EXTERN_REPOSITORY_TAG);
+ for (int i = 0; (externList != null) && (i < externList.size()); i++)
+ {
+ parseRepositoryFile(hopCount - 1, (String) externList.get(i));
+ }
+ }
+ }
+ // Else if mulitvalue map, then create a bundle record
+ // for the associated bundle meta-data.
+ else if (obj instanceof MultivalueMap)
+ {
+ // Create a case-insensitive map.
+ Map bundleMap = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+ bundleMap.putAll((Map) obj);
+
+ // Convert capabilities into case-insensitive maps.
+ List list = (List) bundleMap.get("capability");
+ Map[] capabilityMaps = convertCapabilities(list);
+ bundleMap.put("capability", capabilityMaps);
+
+ // Convert requirements info filters.
+ list = (List) bundleMap.get("requirement");
+ Filter[] filters = convertRequirements(list);
+ bundleMap.put("requirement", filters);
+
+ // Convert any remaining single-element lists into
+ // the element itself.
+ for (Iterator i = bundleMap.keySet().iterator(); i.hasNext(); )
+ {
+ Object key = i.next();
+ Object value = bundleMap.get(key);
+ if ((value instanceof List) &&
+ (((List) value).size() == 1))
+ {
+ bundleMap.put(key, ((List) value).get(0));
+ }
+ }
+
+ // Create a bundle record using the map.
+ BundleRecord record = new BundleRecord(bundleMap);
+ // TODO: Filter duplicates.
+ BundleRecord[] records =
+ (BundleRecord[]) m_recordMap.get(
+ record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME));
+ if (records == null)
+ {
+ records = new BundleRecord[] { record };
+ }
+ else
+ {
+ BundleRecord[] newRecords = new BundleRecord[records.length + 1];
+ System.arraycopy(records, 0, newRecords, 0, records.length);
+ newRecords[records.length] = record;
+ records = newRecords;
+ }
+ m_recordMap.put(
+ record.getAttribute(BundleRecord.BUNDLE_SYMBOLICNAME), records);
+ }
+ }
+ }
+ catch (MalformedURLException ex)
+ {
+ ex.printStackTrace(System.err);
+// System.err.println("Error: " + ex);
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace(System.err);
+// System.err.println("Error: " + ex);
+ }
+ finally
+ {
+ try
+ {
+ if (is != null) is.close();
+ }
+ catch (IOException ex)
+ {
+ // Not much we can do.
+ }
+ }
+ }
+
+ private Map[] convertCapabilities(List capLists)
+ {
+ Map[] capabilityMaps = new Map[(capLists == null) ? 0 : capLists.size()];
+ for (int capIdx = 0; (capLists != null) && (capIdx < capLists.size()); capIdx++)
+ {
+ // Create a case-insensitive map.
+ capabilityMaps[capIdx] = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+
+ List capList = (List) capLists.get(capIdx);
+
+ for (int propIdx = 0; propIdx < capList.size(); propIdx++)
+ {
+ Map propMap = (Map) capList.get(propIdx);
+ String name = (String) propMap.get("name");
+ String type = (String) propMap.get("type");
+ String value = (String) propMap.get("value");
+ try
+ {
+ Class clazz = this.getClass().getClassLoader().loadClass(type);
+ Object o = clazz
+ .getConstructor(new Class[] { String.class })
+ .newInstance(new Object[] { value });
+ capabilityMaps[capIdx].put(name, o);
+ }
+ catch (Exception ex)
+ {
+// TODO: DETERMINE WHAT TO DO HERE.
+ // Two options here, we can either ignore the
+ // entire capability or we can just ignore the
+ // property. For now, just ignore the property.
+ continue;
+ }
+ }
+ }
+ return capabilityMaps;
+ }
+
+ private Filter[] convertRequirements(List reqsList)
+ {
+ Filter[] filters = new Filter[(reqsList == null) ? 0 : reqsList.size()];
+ for (int i = 0; (reqsList != null) && (i < reqsList.size()); i++)
+ {
+ try
+ {
+ filters[i] = m_context.createFilter((String) reqsList.get(i));
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ }
+ }
+ return filters;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/Util.java b/src/org/apache/osgi/bundle/bundlerepository/Util.java
new file mode 100644
index 0000000..7c40551
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/Util.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository;
+
+import java.io.*;
+import java.util.StringTokenizer;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+public class Util
+{
+ public static String getBundleName(Bundle bundle)
+ {
+ String name = (String) bundle.getHeaders().get(Constants.BUNDLE_NAME);
+ return (name == null)
+ ? "Bundle " + Long.toString(bundle.getBundleId())
+ : name;
+ }
+
+ public static int compareVersion(int[] v1, int[] v2)
+ {
+ if (v1[0] > v2[0])
+ {
+ return 1;
+ }
+ else if (v1[0] < v2[0])
+ {
+ return -1;
+ }
+ else if (v1[1] > v2[1])
+ {
+ return 1;
+ }
+ else if (v1[1] < v2[1])
+ {
+ return -1;
+ }
+ else if (v1[2] > v2[2])
+ {
+ return 1;
+ }
+ else if (v1[2] < v2[2])
+ {
+ return -1;
+ }
+ return 0;
+ }
+
+ public static int[] parseVersionString(String s)
+ {
+ int[] version = new int[] { 0, 0, 0 };
+
+ if (s != null)
+ {
+ StringTokenizer st = new StringTokenizer(s, ".");
+ if (st.hasMoreTokens())
+ {
+ try
+ {
+ version[0] = Integer.parseInt(st.nextToken());
+ if (st.hasMoreTokens())
+ {
+ version[1] = Integer.parseInt(st.nextToken());
+ if (st.hasMoreTokens())
+ {
+ version[2] = Integer.parseInt(st.nextToken());
+ }
+ }
+ return version;
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new IllegalArgumentException(
+ "Improper version number.");
+ }
+ }
+ }
+
+ return version;
+ }
+
+ private static final byte encTab[] = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
+ 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52,
+ 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64,
+ 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70,
+ 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31,
+ 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f };
+
+ private static final byte decTab[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1,
+ -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1,
+ -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, -1, -1, -1, -1, -1 };
+
+ public static String base64Encode(String s) throws IOException
+ {
+ return encode(s.getBytes(), 0);
+ }
+
+ /**
+ * Encode a raw byte array to a Base64 String.
+ *
+ * @param in Byte array to encode.
+ * @param len Length of Base64 lines. 0 means no line breaks.
+ **/
+ public static String encode(byte[] in, int len) throws IOException
+ {
+ ByteArrayOutputStream baos = null;
+ ByteArrayInputStream bais = null;
+ try
+ {
+ baos = new ByteArrayOutputStream();
+ bais = new ByteArrayInputStream(in);
+ encode(bais, baos, len);
+ // ASCII byte array to String
+ return (new String(baos.toByteArray()));
+ }
+ finally
+ {
+ if (baos != null)
+ {
+ baos.close();
+ }
+ if (bais != null)
+ {
+ bais.close();
+ }
+ }
+ }
+
+ public static void encode(InputStream in, OutputStream out, int len)
+ throws IOException
+ {
+
+ // Check that length is a multiple of 4 bytes
+ if (len % 4 != 0)
+ {
+ throw new IllegalArgumentException("Length must be a multiple of 4");
+ }
+
+ // Read input stream until end of file
+ int bits = 0;
+ int nbits = 0;
+ int nbytes = 0;
+ int b;
+
+ while ((b = in.read()) != -1)
+ {
+ bits = (bits << 8) | b;
+ nbits += 8;
+ while (nbits >= 6)
+ {
+ nbits -= 6;
+ out.write(encTab[0x3f & (bits >> nbits)]);
+ nbytes++;
+ // New line
+ if (len != 0 && nbytes >= len)
+ {
+ out.write(0x0d);
+ out.write(0x0a);
+ nbytes -= len;
+ }
+ }
+ }
+
+ switch (nbits)
+ {
+ case 2:
+ out.write(encTab[0x3f & (bits << 4)]);
+ out.write(0x3d); // 0x3d = '='
+ out.write(0x3d);
+ break;
+ case 4:
+ out.write(encTab[0x3f & (bits << 2)]);
+ out.write(0x3d);
+ break;
+ }
+
+ if (len != 0)
+ {
+ if (nbytes != 0)
+ {
+ out.write(0x0d);
+ out.write(0x0a);
+ }
+ out.write(0x0d);
+ out.write(0x0a);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXHandler.java b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXHandler.java
new file mode 100644
index 0000000..b6a8e1d
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository.kxmlsax;
+
+import java.util.Properties;
+
+import org.xml.sax.SAXException;
+
+/**
+ * Interface for SAX handler with kXML
+ *
+ * @author Didier Donsez (didier.donsez@imag.fr)
+ */
+public interface KXmlSAXHandler {
+
+ /**
+ * Method called when parsing text
+ *
+ * @param ch
+ * @param offset
+ * @param length
+ * @exception SAXException
+ */
+ public void characters(char[] ch, int offset, int length) throws Exception;
+
+ /**
+ * Method called when a tag opens
+ *
+ * @param uri
+ * @param localName
+ * @param qName
+ * @param attrib
+ * @exception SAXException
+ **/
+ public void startElement(
+ String uri,
+ String localName,
+ String qName,
+ Properties attrib)
+ throws Exception;
+ /**
+ * Method called when a tag closes
+ *
+ * @param uri
+ * @param localName
+ * @param qName
+ * @exception SAXException
+ */
+ public void endElement(
+ java.lang.String uri,
+ java.lang.String localName,
+ java.lang.String qName)
+ throws Exception;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXParser.java b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXParser.java
new file mode 100644
index 0000000..1bb9b25
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/kxmlsax/KXmlSAXParser.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository.kxmlsax;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Properties;
+
+import org.kxml.Attribute;
+import org.kxml.Xml;
+import org.kxml.parser.ParseEvent;
+import org.kxml.parser.XmlParser;
+
+/**
+ * The KXmlSAXParser extends the XmlParser from kxml. This is a very
+ * simple parser that does not take into account the DTD
+ *
+ * @version 1.0 08 Nov 2002
+ * @version 1.1 24 Apr 2004
+ * @author Humberto Cervantes, Didier Donsez
+ */
+public class KXmlSAXParser extends XmlParser {
+ /**
+ * The constructor for a parser, it receives a java.io.Reader.
+ *
+ * @param reader The reader
+ * @exception IOException thrown by the superclass
+ */
+ public KXmlSAXParser(Reader r) throws IOException {
+ super(r);
+ }
+
+ /**
+ * Parser from the reader provided in the constructor, and call
+ * the startElement and endElement in a KxmlHandler
+ *
+ * @param reader The reader
+ * @exception Exception thrown by the superclass
+ */
+ public void parseXML(KXmlSAXHandler handler) throws Exception {
+ ParseEvent evt = null;
+ do {
+ evt = read();
+ if (evt.getType() == Xml.START_TAG) {
+ Properties props = new Properties();
+ for (int i = 0; i < evt.getAttributeCount(); i++) {
+ Attribute attr = evt.getAttribute(i);
+ props.put(attr.getName(), attr.getValue());
+ }
+ handler.startElement(
+ "uri",
+ evt.getName(),
+ evt.getName(),
+ props);
+ } else if (evt.getType() == Xml.END_TAG) {
+ handler.endElement("uri", evt.getName(), evt.getName());
+ } else if (evt.getType() == Xml.TEXT) {
+ String text = evt.getText();
+ handler.characters(text.toCharArray(),0,text.length());
+ } else {
+ // do nothing
+ }
+ } while (evt.getType() != Xml.END_DOCUMENT);
+ }
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/manifest.mf b/src/org/apache/osgi/bundle/bundlerepository/manifest.mf
new file mode 100644
index 0000000..ad49efe
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/manifest.mf
@@ -0,0 +1,10 @@
+Bundle-Name: Bundle Repository
+Bundle-SymbolicName: org.apache.osgi.bundle.bundlerepository
+Bundle-Description: A simple bundle repository for Felix.
+Bundle-Activator: org.apache.osgi.bundle.bundlerepository.Activator
+Bundle-ClassPath: .,org/apache/osgi/bundle/bundlerepository/kxml.jar
+Bundle-Version: 2.0.0.alpha2
+Import-Package: org.osgi.framework
+DynamicImport-Package: org.apache.osgi.service.shell
+Export-Package:
+ org.apache.osgi.service.bundlerepository; specification-version="1.1.0"
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/ClassUtility.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/ClassUtility.java
new file mode 100644
index 0000000..fd9b6e9
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/ClassUtility.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository.metadataparser;
+
+/**
+ * This class provides methods to process class name
+ */
+
+public class ClassUtility {
+
+ /**
+ * This method capitalizes the first character in the provided string.
+ * @return resulted string
+ */
+ public static String capitalize(String name) {
+
+ int len=name.length();
+ StringBuffer sb=new StringBuffer(len);
+ boolean setCap=true;
+ for(int i=0; i<len; i++){
+ char c=name.charAt(i);
+ if(c=='-' || c=='_') {
+ setCap=true;
+ } else {
+ if(setCap){
+ sb.append(Character.toUpperCase(c));
+ setCap=false;
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * This method capitalizes all characters in the provided string.
+ * @return resulted string
+ */
+ public static String finalstaticOf(String membername) {
+ int len=membername.length();
+ StringBuffer sb=new StringBuffer(len+2);
+ for(int i=0; i<len; i++){
+ char c=membername.charAt(i);
+ if(Character.isLowerCase(c) ) {
+ sb.append(Character.toUpperCase(c));
+ } else if(Character.isUpperCase(c) ) {
+ sb.append('_').append(c);
+ } else {
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * This method returns the package name in a full class name
+ * @return resulted string
+ */
+ public static String packageOf(String fullclassname) {
+ int index=fullclassname.lastIndexOf(".");
+ if(index>0) {
+ return fullclassname.substring(0,index);
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * This method returns the package name in a full class name
+ * @return resulted string
+ */
+ public static String classOf(String fullclassname) {
+ int index=fullclassname.lastIndexOf(".");
+ if(index>0) {
+ return fullclassname.substring(index+1);
+ } else {
+ return fullclassname;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/KXmlMetadataHandler.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/KXmlMetadataHandler.java
new file mode 100644
index 0000000..077a551
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/KXmlMetadataHandler.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository.metadataparser;
+
+import java.io.*;
+
+import org.apache.osgi.bundle.bundlerepository.kxmlsax.KXmlSAXParser;
+
+
+/**
+ * handles the metadata in XML format
+ * (use kXML (http://kxml.enhydra.org/) a open-source very light weight XML parser
+ * @version 1.00 11 Nov 2003
+ * @author Didier Donsez
+ */
+public class KXmlMetadataHandler /*implements MetadataHandler*/ {
+
+ private XmlCommonHandler handler;
+
+ public KXmlMetadataHandler() {
+ handler = new XmlCommonHandler();
+ }
+
+ /**
+ * Called to parse the InputStream and set bundle list and package hash map
+ */
+ public void parse(InputStream is) throws Exception {
+ BufferedReader br = new BufferedReader(new InputStreamReader(is));
+ KXmlSAXParser parser;
+ parser = new KXmlSAXParser(br);
+ parser.parseXML(handler);
+ }
+
+ /**
+ * return the metadata
+ * @return a Objet
+ */
+ public Object getMetadata() {
+ return handler.getRoot();
+ }
+
+ public void addType(String qname, Class clazz) {
+ handler.addType(qname, clazz);
+ }
+
+ public void setDefaultType(Class clazz) {
+ handler.setDefaultType(clazz);
+ }
+}
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/MultivalueMap.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/MultivalueMap.java
new file mode 100644
index 0000000..ba29423
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/MultivalueMap.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository.metadataparser;
+
+import java.util.*;
+
+public class MultivalueMap implements Map {
+ private Map m_map = null;
+
+ public MultivalueMap() {
+ m_map = new HashMap();
+ }
+
+ public MultivalueMap(Map map) {
+ m_map = map;
+ }
+
+ /**
+ * @see java.util.Map#size()
+ */
+ public int size() {
+ return m_map.size();
+ }
+
+ /**
+ * @see java.util.Map#clear()
+ */
+ public void clear() {
+ m_map.clear();
+ }
+
+ /**
+ * @see java.util.Map#isEmpty()
+ */
+ public boolean isEmpty() {
+ return m_map.isEmpty();
+ }
+
+ /**
+ * @see java.util.Map#containsKey(java.lang.Object)
+ */
+ public boolean containsKey(Object arg0) {
+ return m_map.containsKey(arg0);
+ }
+
+ /**
+ * @see java.util.Map#containsValue(java.lang.Object)
+ */
+ public boolean containsValue(Object arg0) {
+ return false;
+ }
+
+ /**
+ * @see java.util.Map#values()
+ */
+ public Collection values() {
+ return null;
+ }
+
+ /**
+ * @see java.util.Map#putAll(java.util.Map)
+ */
+ public void putAll(Map arg0) {
+ }
+
+ /**
+ * @see java.util.Map#entrySet()
+ */
+ public Set entrySet() {
+ return m_map.entrySet();
+ }
+
+ /**
+ * @see java.util.Map#keySet()
+ */
+ public Set keySet() {
+ return m_map.keySet();
+ }
+
+ /**
+ * @see java.util.Map#get(java.lang.Object)
+ */
+ public Object get(Object key) {
+ return m_map.get(key);
+ }
+
+ /**
+ * @see java.util.Map#remove(java.lang.Object)
+ */
+ public Object remove(Object arg0) {
+ return m_map.remove(arg0);
+ }
+
+ /**
+ * @see java.util.Map#put(java.lang.Object, java.lang.Object)
+ */
+ public Object put(Object key, Object value) {
+ Object prev = m_map.get(key);
+ if (prev == null) {
+ List list = new ArrayList();
+ list.add(value);
+ m_map.put(key, list);
+ return list;
+ } else {
+ ((List) prev).add(value);
+ return prev;
+ }
+ }
+
+ public String toString() {
+ StringBuffer sb=new StringBuffer();
+ sb.append("[MultivalueMap:");
+ if(m_map.isEmpty()) {
+ sb.append("empty");
+ } else {
+ Set keys=m_map.keySet();
+ Iterator iter=keys.iterator();
+ while(iter.hasNext()){
+ String key=(String)iter.next();
+ sb.append("\n\"").append(key).append("\":");
+ sb.append(m_map.get(key).toString());
+ }
+ sb.append('\n');
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/bundlerepository/metadataparser/XmlCommonHandler.java b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/XmlCommonHandler.java
new file mode 100644
index 0000000..628b4c6
--- /dev/null
+++ b/src/org/apache/osgi/bundle/bundlerepository/metadataparser/XmlCommonHandler.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.bundlerepository.metadataparser;
+
+import java.lang.reflect.Method;
+import java.util.*;
+
+import org.apache.osgi.bundle.bundlerepository.kxmlsax.KXmlSAXHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * SAX handler for the XML OBR file
+ *
+ * @author Didier Donsez (didier.donsez@imag.fr)
+ */
+public class XmlCommonHandler implements KXmlSAXHandler {
+
+ private static final String PI_MAPPING="mapping";
+
+ private int columnNumber;
+
+ private int lineNumber;
+
+ //
+ // Data
+ //
+
+ private Object root;
+
+ private Stack objectStack;
+ private Stack qnameStack;
+
+ private Map types;
+ private Class defaultType;
+
+ private StringBuffer currentText;
+
+ public XmlCommonHandler() {
+ objectStack = new Stack();
+ qnameStack = new Stack();
+ types = new HashMap();
+ }
+
+ public void addType(String qname, Class clazz) {
+ types.put(qname, clazz);
+ }
+
+ public void setDefaultType(Class clazz) {
+ defaultType=clazz;
+ }
+
+ public Object getRoot() {
+ return root;
+ }
+
+ /* for PCDATA */
+ public void characters(char[] ch, int offset, int length)
+ throws Exception {
+ if (currentText != null)
+ currentText.append(ch, offset, length);
+ }
+
+ private String adderOf(Class clazz) {
+ return "add"
+ + ClassUtility.capitalize(ClassUtility.classOf(clazz.getName()));
+ }
+
+ private String adderOf(String key) {
+ return "add" + ClassUtility.capitalize(key);
+ }
+
+ private String setterOf(Class clazz) {
+ return "set"
+ + ClassUtility.capitalize(ClassUtility.classOf(clazz.getName()));
+ }
+
+ private String setterOf(String key) {
+ return "set" + ClassUtility.capitalize(key);
+ }
+
+ /**
+ * Method called when a tag opens
+ *
+ * @param uri
+ * @param localName
+ * @param qName
+ * @param attrib
+ * @exception SAXException
+ **/
+ public void startElement(
+ String uri,
+ String localName,
+ String qName,
+ Properties attrib)
+ throws Exception {
+
+ trace("START ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+
+ Class clazz = (Class) types.get(qName);
+ // TODO: should add uri in the future
+
+ if(clazz==null && defaultType!=null)
+ clazz=defaultType;
+
+ Object obj;
+ if (clazz != null) {
+
+ try {
+ obj = clazz.newInstance();
+ } catch (InstantiationException e) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "class "+clazz.getName()+" for element " + qName + " should have an empty constructor");
+ } catch (IllegalAccessException e) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "illegal access on the empty constructor of class "+clazz.getName()+" for element " + qName);
+ }
+
+ Set keyset = attrib.keySet();
+ Iterator iter = keyset.iterator();
+ while (iter.hasNext()) {
+ String key = (String) iter.next();
+
+ if (obj instanceof Map) {
+ ((Map) obj).put(key, attrib.get(key));
+ } else if (obj instanceof List) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "List element " + qName + " cannot have any attribute");
+ } else if (obj instanceof String) {
+ if(key.equals("value")){
+ obj=(String)attrib.get(key);
+ } else {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "String element " + qName + " cannot have other attribute than value");
+ }
+ } else {
+ Method method = null;
+ try {
+ method =
+ clazz.getMethod(
+ setterOf(key),
+ new Class[] { String.class });
+ } catch (NoSuchMethodException e) {
+ // do nothing
+ }
+ if (method == null)
+ try {
+ method =
+ clazz.getMethod(
+ adderOf(key),
+ new Class[] { String.class });
+
+ } catch (NoSuchMethodException e) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "element "
+ + qName
+ + " does not support the attribute "
+ + key);
+ }
+ if (method != null)
+ method.invoke(
+ obj,
+ new String[] {(String) attrib.get(key)});
+ }
+
+ }
+
+ } else {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "this element " + qName + " has not corresponding class");
+ }
+
+ if (root == null)
+ root = obj;
+ objectStack.push(obj);
+ qnameStack.push(qName);
+ currentText = new StringBuffer();
+
+ trace("START/ ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+ }
+
+ /**
+ * Method called when a tag closes
+ *
+ * @param uri
+ * @param localName
+ * @param qName
+ * @exception SAXException
+ */
+ public void endElement(
+ java.lang.String uri,
+ java.lang.String localName,
+ java.lang.String qName)
+ throws Exception {
+
+ trace("END ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+
+ Object obj = objectStack.pop();
+
+ if (currentText != null && currentText.length() != 0) {
+ if (obj instanceof Map) {
+ ((Map) obj).put(qName, currentText.toString().trim());
+ } else if (obj instanceof List) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "List element " + qName + " cannot have PCDATAs");
+ } else if (obj instanceof String) {
+ String str=(String)obj;
+ if(str.length()!=0){
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ "String element " + qName + " cannot have both PCDATA and an attribute value");
+ } else {
+ obj=currentText.toString().trim();
+ }
+ } else {
+ Method method = null;
+ try {
+ method =
+ obj.getClass().getMethod(
+ "addText",
+ new Class[] { String.class });
+ } catch (NoSuchMethodException e) {
+ // do nothing
+ }
+ if (method != null) {
+ method.invoke(obj, new String[] { currentText.toString().trim()});
+ }
+ }
+ }
+
+ currentText = null;
+
+ if (!objectStack.isEmpty()) {
+
+ Object parent = objectStack.peek();
+ String parentName = (String) qnameStack.peek();
+
+ if (parent instanceof Map) {
+ ((Map) parent).put(qName, obj);
+ } else if (parent instanceof List) {
+ ((List) parent).add(obj);
+ } else {
+ Method method = null;
+ try {
+ method =
+ parent.getClass().getMethod(
+ adderOf(ClassUtility.capitalize(qName)),
+ new Class[] { obj.getClass()});
+ } catch (NoSuchMethodException e) {
+ trace(
+ "NoSuchMethodException: "
+ + adderOf(ClassUtility.capitalize(qName)));
+ // do nothing
+ }
+ if (method == null)
+ try {
+ method =
+ parent.getClass().getMethod(
+ setterOf(ClassUtility.capitalize(qName)),
+ new Class[] { obj.getClass()});
+ } catch (NoSuchMethodException e) {
+ trace(
+ "NoSuchMethodException: "
+ + setterOf(ClassUtility.capitalize(qName)));
+ // do nothing
+ }
+ if (method == null)
+ try {
+ method =
+ parent.getClass().getMethod(
+ adderOf(obj.getClass()),
+ new Class[] { obj.getClass()});
+ } catch (NoSuchMethodException e) {
+ trace(
+ "NoSuchMethodException: "
+ + adderOf(obj.getClass()));
+ // do nothing
+ }
+ if (method == null)
+ try {
+ method =
+ parent.getClass().getMethod(
+ setterOf(obj.getClass()),
+ new Class[] { obj.getClass()});
+ } catch (NoSuchMethodException e) {
+ trace(
+ "NoSuchMethodException: "
+ + setterOf(obj.getClass()));
+ // do nothing
+ }
+
+ if (method != null) {
+ trace(method.getName());
+ method.invoke(parent, new Object[] { obj });
+ } else {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " element " + parentName + " cannot have an attribute " + qName + " of type " + obj.getClass());
+ }
+ }
+
+ }
+
+ trace("END/ ("+lineNumber+","+columnNumber+"):" + uri + ":" + qName);
+
+ }
+
+ private void trace(String msg) {
+ if (false)
+ System.err.println(msg);
+ }
+
+ /**
+ * @see kxml.sax.KXmlSAXHandler#setLineNumber(int)
+ */
+ public void setLineNumber(int lineNumber) {
+ this.lineNumber=lineNumber;
+ }
+
+ /**
+ * @see kxml.sax.KXmlSAXHandler#setColumnNumber(int)
+ */
+ public void setColumnNumber(int columnNumber) {
+ this.columnNumber=columnNumber;
+
+ }
+
+ /**
+ * @see kxml.sax.KXmlSAXHandler#processingInstruction(java.lang.String, java.lang.String)
+ */
+ public void processingInstruction(String target, String data) throws Exception {
+ trace("pi:"+target+";"+data);
+ if(target==null){ // TODO kXML
+ if(!data.startsWith(PI_MAPPING)) return;
+ } else if(!target.equals(PI_MAPPING))return;
+
+
+ // defaultclass attribute
+ String datt="defaultclass=\"";
+ int dstart=data.indexOf(datt);
+ if(dstart!=-1) {
+ int dend=data.indexOf("\"",dstart+datt.length());
+ if(dend==-1)
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " \"defaultclass\" attribute in \"mapping\" PI is not quoted");
+
+ String classname=data.substring(dstart+datt.length(),dend);
+ Class clazz=null;
+ try {
+ clazz=getClass().getClassLoader().loadClass(classname);
+ } catch (ClassNotFoundException e) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " cannot found class "+ classname+" for \"mapping\" PI");
+ }
+ setDefaultType(clazz);
+ return;
+ }
+
+ // element attribute
+ String eatt="element=\"";
+ int estart=data.indexOf(eatt);
+ if(estart==-1)
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " missing \"element\" attribute in \"mapping\" PI");
+ int eend=data.indexOf("\"",estart+eatt.length());
+ if(eend==-1)
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " \"element\" attribute in \"mapping\" PI is not quoted");
+
+ String element=data.substring(estart+eatt.length(),eend);
+
+ // element class
+ String catt="class=\"";
+ int cstart=data.indexOf(catt);
+ if(cstart==-1)
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " missing \"class\" attribute in \"mapping\" PI");
+ int cend=data.indexOf("\"",cstart+catt.length());
+ if(cend==-1)
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " \"class\" attribute in \"mapping\" PI is not quoted");
+
+ String classname=data.substring(cstart+catt.length(),cend);
+
+ Class clazz=null;
+ try {
+ clazz=getClass().getClassLoader().loadClass(classname);
+ } catch (ClassNotFoundException e) {
+ throw new Exception(lineNumber+","+columnNumber+":"+
+ " cannot found class "+ classname+" for \"mapping\" PI");
+ }
+ addType(element,clazz);
+ }
+}
diff --git a/src/org/apache/osgi/bundle/shell/Activator.java b/src/org/apache/osgi/bundle/shell/Activator.java
new file mode 100644
index 0000000..348d586
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/Activator.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.security.*;
+import java.util.*;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class Activator implements BundleActivator
+{
+ private transient BundleContext m_context = null;
+ private transient ShellServiceImpl m_shell = null;
+
+ public void start(BundleContext context)
+ {
+ m_context = context;
+
+ // Register shell service implementation.
+ String[] classes = {
+ org.apache.osgi.service.shell.ShellService.class.getName(),
+ org.ungoverned.osgi.service.shell.ShellService.class.getName()
+ };
+ context.registerService(classes, m_shell = new ShellServiceImpl(), null);
+
+ // Listen for registering/unregistering of shell command
+ // services so that we can automatically add/remove them
+ // from our list of available commands.
+ ServiceListener sl = new ServiceListener() {
+ public void serviceChanged(ServiceEvent event)
+ {
+ if (event.getType() == ServiceEvent.REGISTERED)
+ {
+ m_shell.addCommand(event.getServiceReference());
+ }
+ else if (event.getType() == ServiceEvent.UNREGISTERING)
+ {
+ m_shell.removeCommand(event.getServiceReference());
+ }
+ else
+ {
+ }
+ }
+ };
+
+ try
+ {
+ m_context.addServiceListener(sl,
+ "(|(objectClass="
+ + org.apache.osgi.service.shell.Command.class.getName()
+ + ")(objectClass="
+ + org.ungoverned.osgi.service.shell.Command.class.getName()
+ + "))");
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ System.err.println("Activator: Cannot register service listener.");
+ System.err.println("Activator: " + ex);
+ }
+
+ // Now manually try to find any commands that have already
+ // been registered (i.e., we didn't see their service events).
+ initializeCommands();
+
+ // Register "exports" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new BundleLevelCommandImpl(m_context), null);
+
+ // Register "cd" command service.
+ classes = new String[2];
+ classes[0] = org.apache.osgi.service.shell.Command.class.getName();
+ classes[1] = org.apache.osgi.service.shell.CdCommand.class.getName();
+ context.registerService(
+ classes, new CdCommandImpl(m_context), null);
+
+ // Register "exports" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new PackagesCommandImpl(m_context), null);
+
+ // Register "headers" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new HeadersCommandImpl(m_context), null);
+
+ // Register "help" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new HelpCommandImpl(m_context), null);
+
+ // Register "install" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new InstallCommandImpl(m_context), null);
+
+ // Register "ps" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new PsCommandImpl(m_context), null);
+
+ // Register "refresh" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new RefreshCommandImpl(m_context), null);
+
+ // Register "services" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new ServicesCommandImpl(m_context), null);
+
+ // Register "startlevel" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new StartLevelCommandImpl(m_context), null);
+
+ // Register "shutdown" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new ShutdownCommandImpl(m_context), null);
+
+ // Register "start" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new StartCommandImpl(m_context), null);
+
+ // Register "stop" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new StopCommandImpl(m_context), null);
+
+ // Register "uninstall" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new UninstallCommandImpl(m_context), null);
+
+ // Register "update" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new UpdateCommandImpl(m_context), null);
+
+ // Register "version" command service.
+ context.registerService(
+ org.apache.osgi.service.shell.Command.class.getName(),
+ new VersionCommandImpl(m_context), null);
+ }
+
+ public void stop(BundleContext context)
+ {
+ m_shell.clearCommands();
+ }
+
+ private void initializeCommands()
+ {
+ synchronized (m_shell)
+ {
+ try
+ {
+ ServiceReference[] refs = m_context.getServiceReferences(
+ org.apache.osgi.service.shell.Command.class.getName(), null);
+ if (refs != null)
+ {
+ for (int i = 0; i < refs.length; i++)
+ {
+ m_shell.addCommand(refs[i]);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ System.err.println("Activator: " + ex);
+ }
+ }
+ }
+
+ private class ShellServiceImpl implements
+ org.apache.osgi.service.shell.ShellService,
+ org.ungoverned.osgi.service.shell.ShellService
+ {
+ private HashMap m_commandRefMap = new HashMap();
+ private TreeMap m_commandNameMap = new TreeMap();
+
+ public synchronized String[] getCommands()
+ {
+ Set ks = m_commandNameMap.keySet();
+ String[] cmds = (ks == null)
+ ? new String[0] : (String[]) ks.toArray(new String[ks.size()]);
+ return cmds;
+ }
+
+ public synchronized String getCommandUsage(String name)
+ {
+ Command command = (Command) m_commandNameMap.get(name);
+ return (command == null) ? null : command.getUsage();
+ }
+
+ public synchronized String getCommandDescription(String name)
+ {
+ Command command = (Command) m_commandNameMap.get(name);
+ return (command == null) ? null : command.getShortDescription();
+ }
+
+ public synchronized ServiceReference getCommandReference(String name)
+ {
+ return (ServiceReference) m_commandNameMap.get(name);
+ }
+
+ public synchronized void removeCommand(ServiceReference ref)
+ {
+ Command command = (Command) m_commandRefMap.remove(ref);
+ if (command != null)
+ {
+ m_commandNameMap.remove(command.getName());
+ }
+ }
+
+ public synchronized void executeCommand(
+ String commandLine, PrintStream out, PrintStream err) throws Exception
+ {
+ commandLine = commandLine.trim();
+ String commandName = (commandLine.indexOf(' ') >= 0)
+ ? commandLine.substring(0, commandLine.indexOf(' ')) : commandLine;
+ Command command = getCommand(commandName);
+ if (command != null)
+ {
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ AccessController.doPrivileged(
+ new ExecutePrivileged(command, commandLine, out, err));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ex.getException();
+ }
+ }
+ else
+ {
+ try
+ {
+ command.execute(commandLine, out, err);
+ }
+ catch (Throwable ex)
+ {
+ err.println("Unable to execute command: " + ex);
+ ex.printStackTrace(err);
+ }
+ }
+ }
+ else
+ {
+ err.println("Command not found.");
+ }
+ }
+
+ protected synchronized Command getCommand(String name)
+ {
+ Command command = (Command) m_commandNameMap.get(name);
+ return (command == null) ? null : command;
+ }
+
+ protected synchronized void addCommand(ServiceReference ref)
+ {
+ Object cmdObj = m_context.getService(ref);
+ Command command =
+ (cmdObj instanceof org.ungoverned.osgi.service.shell.Command)
+ ? new OldCommandWrapper((org.ungoverned.osgi.service.shell.Command) cmdObj)
+ : (Command) cmdObj;
+ m_commandRefMap.put(ref, command);
+ m_commandNameMap.put(command.getName(), command);
+ }
+
+ protected synchronized void clearCommands()
+ {
+ m_commandRefMap.clear();
+ m_commandNameMap.clear();
+ }
+ }
+
+ private static class OldCommandWrapper implements Command
+ {
+ private org.ungoverned.osgi.service.shell.Command m_oldCommand = null;
+
+ public OldCommandWrapper(org.ungoverned.osgi.service.shell.Command oldCommand)
+ {
+ m_oldCommand = oldCommand;
+ }
+
+ public String getName()
+ {
+ return m_oldCommand.getName();
+ }
+
+ public String getUsage()
+ {
+ return m_oldCommand.getUsage();
+ }
+
+ public String getShortDescription()
+ {
+ return m_oldCommand.getShortDescription();
+ }
+
+ public void execute(String line, PrintStream out, PrintStream err)
+ {
+ m_oldCommand.execute(line, out, err);
+ }
+ }
+
+ public static class ExecutePrivileged implements PrivilegedExceptionAction
+ {
+ private Command m_command = null;
+ private String m_commandLine = null;
+ private PrintStream m_out = null;
+ private PrintStream m_err = null;
+
+ public ExecutePrivileged(
+ Command command, String commandLine,
+ PrintStream out, PrintStream err)
+ throws Exception
+ {
+ m_command = command;
+ m_commandLine = commandLine;
+ m_out = out;
+ m_err = err;
+ }
+
+ public Object run() throws Exception
+ {
+ m_command.execute(m_commandLine, m_out, m_err);
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/BundleLevelCommandImpl.java b/src/org/apache/osgi/bundle/shell/BundleLevelCommandImpl.java
new file mode 100644
index 0000000..06fc95c
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/BundleLevelCommandImpl.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+import org.osgi.service.startlevel.StartLevel;
+
+public class BundleLevelCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public BundleLevelCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "bundlelevel";
+ }
+
+ public String getUsage()
+ {
+ return "bundlelevel <level> <id> ... | <id>";
+ }
+
+ public String getShortDescription()
+ {
+ return "set or get bundle start level.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ // Get start level service.
+ ServiceReference ref = m_context.getServiceReference(
+ org.osgi.service.startlevel.StartLevel.class.getName());
+ if (ref == null)
+ {
+ out.println("StartLevel service is unavailable.");
+ return;
+ }
+
+ StartLevel sl = (StartLevel) m_context.getService(ref);
+ if (sl == null)
+ {
+ out.println("StartLevel service is unavailable.");
+ return;
+ }
+
+ // Parse command line.
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ // If there is only one token, then assume it is
+ // a bundle ID for which we must retrieve the bundle
+ // level.
+ if (st.countTokens() == 1)
+ {
+ // Get the bundle and display start level.
+ Bundle bundle = null;
+ String token = null;
+ try
+ {
+ token = st.nextToken();
+ long id = Long.parseLong(token);
+ bundle = m_context.getBundle(id);
+ if (bundle != null)
+ {
+ out.println("Bundle " + token + " is level "
+ + sl.getBundleStartLevel(bundle));
+ }
+ else
+ {
+ err.println("Bundle ID " + token + " is invalid.");
+ }
+ }
+ catch (NumberFormatException ex)
+ {
+ err.println("Unable to parse integer '" + token + "'.");
+ }
+ catch (Exception ex)
+ {
+ err.println(ex.toString());
+ }
+ }
+ // If there is more than one token, assume the first
+ // token is the new start level and the remaining
+ // tokens are the bundle IDs whose start levels should
+ // be changed.
+ else if (st.countTokens() > 1)
+ {
+ // Get the bundle.
+ Bundle bundle = null;
+ String token = null;
+ int startLevel = -1;
+
+ try
+ {
+ token = st.nextToken();
+ startLevel = Integer.parseInt(token);
+ }
+ catch (NumberFormatException ex)
+ {
+ err.println("Unable to parse start level '" + token + "'.");
+ }
+
+ // Ignore invalid start levels.
+ if (startLevel > 0)
+ {
+ // Set the start level for each specified bundle.
+ while (st.hasMoreTokens())
+ {
+ try
+ {
+ token = st.nextToken();
+ long id = Long.parseLong(token);
+ bundle = m_context.getBundle(id);
+ if (bundle != null)
+ {
+ sl.setBundleStartLevel(bundle, startLevel);
+ }
+ else
+ {
+ err.println("Bundle ID '" + token + "' is invalid.");
+ }
+ }
+ catch (NumberFormatException ex)
+ {
+ err.println("Unable to parse bundle ID '" + token + "'.");
+ }
+ catch (Exception ex)
+ {
+ err.println(ex.toString());
+ }
+ }
+ }
+ else
+ {
+ err.println("Invalid start level.");
+ }
+ }
+ else
+ {
+ err.println("Incorrect number of arguments.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/CdCommandImpl.java b/src/org/apache/osgi/bundle/shell/CdCommandImpl.java
new file mode 100644
index 0000000..c876550
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/CdCommandImpl.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.CdCommand;
+import org.osgi.framework.BundleContext;
+
+public class CdCommandImpl implements CdCommand
+{
+ private BundleContext m_context = null;
+ private String m_baseURL = "";
+
+ public CdCommandImpl(BundleContext context)
+ {
+ m_context = context;
+
+ // See if the initial base URL is specified.
+ String baseURL = m_context.getProperty(BASE_URL_PROPERTY);
+ setBaseURL(baseURL);
+ }
+
+ public String getName()
+ {
+ return "cd";
+ }
+
+ public String getUsage()
+ {
+ return "cd [<base-URL>]";
+ }
+
+ public String getShortDescription()
+ {
+ return "change or display base URL.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ // No more tokens means to display the base URL,
+ // otherwise set the base URL.
+ if (st.countTokens() == 0)
+ {
+ out.println(m_baseURL);
+ }
+ else if (st.countTokens() == 1)
+ {
+ setBaseURL(st.nextToken());
+ }
+ else
+ {
+ err.println("Incorrect number of arguments");
+ }
+ }
+
+ public String getBaseURL()
+ {
+ return m_baseURL;
+ }
+
+ public void setBaseURL(String s)
+ {
+ m_baseURL = (s == null) ? "" : s;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/HeadersCommandImpl.java b/src/org/apache/osgi/bundle/shell/HeadersCommandImpl.java
new file mode 100644
index 0000000..bb341d8
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/HeadersCommandImpl.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.*;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+public class HeadersCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public HeadersCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "headers";
+ }
+
+ public String getUsage()
+ {
+ return "headers [<id> ...]";
+ }
+
+ public String getShortDescription()
+ {
+ return "display bundle header properties.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ // Print the specified bundles or all if none are specified.
+ if (st.hasMoreTokens())
+ {
+ while (st.hasMoreTokens())
+ {
+ String id = st.nextToken().trim();
+
+ try
+ {
+ long l = Long.parseLong(id);
+ Bundle bundle = m_context.getBundle(l);
+ if (bundle != null)
+ {
+ printHeaders(out, bundle);
+ }
+ else
+ {
+ err.println("Bundle ID " + id + " is invalid.");
+ }
+ }
+ catch (NumberFormatException ex)
+ {
+ err.println("Unable to parse id '" + id + "'.");
+ }
+ catch (Exception ex)
+ {
+ err.println(ex.toString());
+ }
+ }
+ }
+ else
+ {
+ Bundle[] bundles = m_context.getBundles();
+ for (int i = 0; i < bundles.length; i++)
+ {
+ printHeaders(out, bundles[i]);
+ }
+ }
+ }
+
+ private void printHeaders(PrintStream out, Bundle bundle)
+ {
+ String title = Util.getBundleName(bundle);
+ out.println("\n" + title);
+ out.println(Util.getUnderlineString(title));
+ Dictionary dict = bundle.getHeaders();
+ Enumeration keys = dict.keys();
+ while (keys.hasMoreElements())
+ {
+ Object k = (String) keys.nextElement();
+ Object v = dict.get(k);
+ out.println(k + " = " + Util.getValueString(v));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/HelpCommandImpl.java b/src/org/apache/osgi/bundle/shell/HelpCommandImpl.java
new file mode 100644
index 0000000..b2d969f
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/HelpCommandImpl.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+
+import org.apache.osgi.service.shell.Command;
+import org.apache.osgi.service.shell.ShellService;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+public class HelpCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public HelpCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "help";
+ }
+
+ public String getUsage()
+ {
+ return "help";
+ }
+
+ public String getShortDescription()
+ {
+ return "display shell commands.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ try {
+ // Get a reference to the shell service.
+ ServiceReference ref = m_context.getServiceReference(
+ org.apache.osgi.service.shell.ShellService.class.getName());
+
+ if (ref != null)
+ {
+ ShellService ss = (ShellService) m_context.getService(ref);
+ String[] cmds = ss.getCommands();
+ String[] usage = new String[cmds.length];
+ String[] desc = new String[cmds.length];
+ int maxUsage = 0;
+ for (int i = 0; i < cmds.length; i++)
+ {
+ usage[i] = ss.getCommandUsage(cmds[i]);
+ desc[i] = ss.getCommandDescription(cmds[i]);
+ // Just in case the command has gone away.
+ if ((usage[i] != null) && (desc[i] != null))
+ {
+ maxUsage = Math.max(maxUsage, usage[i].length());
+ }
+ }
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < cmds.length; i++)
+ {
+ // Just in case the command has gone away.
+ if ((usage[i] != null) && (desc[i] != null))
+ {
+ sb.delete(0, sb.length());
+ for (int j = 0; j < (maxUsage - usage[i].length()); j++)
+ {
+ sb.append(' ');
+ }
+ out.println(usage[i] + sb + " - " + desc[i]);
+ }
+ }
+ }
+ else
+ {
+ err.println("No ShellService is unavailable.");
+ }
+ } catch (Exception ex) {
+ err.println(ex.toString());
+ }
+ }
+}
diff --git a/src/org/apache/osgi/bundle/shell/InstallCommandImpl.java b/src/org/apache/osgi/bundle/shell/InstallCommandImpl.java
new file mode 100644
index 0000000..4603cc7
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/InstallCommandImpl.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.CdCommand;
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class InstallCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public InstallCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "install";
+ }
+
+ public String getUsage()
+ {
+ return "install <URL> [<URL> ...]";
+ }
+
+ public String getShortDescription()
+ {
+ return "install bundle(s).";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ // There should be at least one URL.
+ if (st.countTokens() >= 1)
+ {
+ while (st.hasMoreTokens())
+ {
+ String location = st.nextToken().trim();
+ install(location, out, err);
+ }
+ }
+ else
+ {
+ err.println("Incorrect number of arguments");
+ }
+ }
+
+ protected Bundle install(String location, PrintStream out, PrintStream err)
+ {
+ String abs = absoluteLocation(location);
+ if (abs == null)
+ {
+ err.println("Malformed location: " + location);
+ }
+ else
+ {
+ try
+ {
+ return m_context.installBundle(abs, null);
+ }
+ catch (IllegalStateException ex)
+ {
+ err.println(ex.toString());
+ }
+ catch (BundleException ex)
+ {
+ if (ex.getNestedException() != null)
+ {
+ err.println(ex.getNestedException().toString());
+ }
+ else
+ {
+ err.println(ex.toString());
+ }
+ }
+ catch (Exception ex)
+ {
+ err.println(ex.toString());
+ }
+ }
+ return null;
+ }
+
+ private String absoluteLocation(String location)
+ {
+ if (!location.endsWith(".jar"))
+ {
+ location = location + ".jar";
+ }
+ try
+ {
+ new URL(location);
+ }
+ catch (MalformedURLException ex)
+ {
+ // Try to create a valid URL using the base URL
+ // contained in the "cd" command service.
+ String baseURL = "";
+
+ try
+ {
+ // Get a reference to the "cd" command service.
+ ServiceReference ref = m_context.getServiceReference(
+ org.apache.osgi.service.shell.CdCommand.class.getName());
+
+ if (ref != null)
+ {
+ CdCommand cd = (CdCommand) m_context.getService(ref);
+ baseURL = cd.getBaseURL();
+ baseURL = (baseURL == null) ? "" : baseURL;
+ m_context.ungetService(ref);
+ }
+
+ String theURL = baseURL + location;
+ new URL(theURL);
+
+ }
+ catch (Exception ex2)
+ {
+ return null;
+ }
+ location = baseURL + location;
+ }
+ return location;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/PackagesCommandImpl.java b/src/org/apache/osgi/bundle/shell/PackagesCommandImpl.java
new file mode 100644
index 0000000..2722cdd
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/PackagesCommandImpl.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+public class PackagesCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public PackagesCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "packages";
+ }
+
+ public String getUsage()
+ {
+ return "packages [<id> ...]";
+ }
+
+ public String getShortDescription()
+ {
+ return "list exported packages.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ // Get package admin service.
+ ServiceReference ref = m_context.getServiceReference(
+ org.osgi.service.packageadmin.PackageAdmin.class.getName());
+ if (ref == null)
+ {
+ out.println("PackageAdmin service is unavailable.");
+ return;
+ }
+
+ PackageAdmin pa = (PackageAdmin) m_context.getService(ref);
+ if (pa == null)
+ {
+ out.println("PackageAdmin service is unavailable.");
+ return;
+ }
+
+ // Parse command line.
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ if (st.hasMoreTokens())
+ {
+ while (st.hasMoreTokens())
+ {
+ String id = st.nextToken();
+ try
+ {
+ long l = Long.parseLong(id);
+ Bundle bundle = m_context.getBundle(l);
+ ExportedPackage[] exports = pa.getExportedPackages(bundle);
+ printExports(out, bundle, exports);
+ }
+ catch (NumberFormatException ex)
+ {
+ err.println("Unable to parse id '" + id + "'.");
+ }
+ catch (Exception ex)
+ {
+ err.println(ex.toString());
+ }
+ }
+ }
+ else
+ {
+ ExportedPackage[] exports = pa.getExportedPackages((Bundle) null);
+ printExports(out, null, exports);
+ }
+ }
+
+ private void printExports(PrintStream out, Bundle target, ExportedPackage[] exports)
+ {
+ if ((exports != null) && (exports.length > 0))
+ {
+ for (int i = 0; i < exports.length; i++)
+ {
+ Bundle bundle = exports[i].getExportingBundle();
+ out.print(Util.getBundleName(bundle));
+ out.println(": " + exports[i]);
+ }
+ }
+ else
+ {
+ out.println(Util.getBundleName(target)
+ + ": No active exported packages.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/PsCommandImpl.java b/src/org/apache/osgi/bundle/shell/PsCommandImpl.java
new file mode 100644
index 0000000..5099815
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/PsCommandImpl.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.Dictionary;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+import org.osgi.service.startlevel.StartLevel;
+
+public class PsCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public PsCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "ps";
+ }
+
+ public String getUsage()
+ {
+ return "ps [-l | -u]";
+ }
+
+ public String getShortDescription()
+ {
+ return "list installed bundles.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ // Get start level service.
+ ServiceReference ref = m_context.getServiceReference(
+ org.osgi.service.startlevel.StartLevel.class.getName());
+ StartLevel sl = null;
+ if (ref != null)
+ {
+ sl = (StartLevel) m_context.getService(ref);
+ }
+
+ if (sl == null)
+ {
+ out.println("StartLevel service is unavailable.");
+ }
+
+ // Parse command line.
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ // Check for optional argument.
+ boolean showLoc = false;
+ boolean showUpdate = false;
+ if (st.countTokens() >= 1)
+ {
+ while (st.hasMoreTokens())
+ {
+ String token = st.nextToken().trim();
+ if (token.equals("-l"))
+ {
+ showLoc = true;
+ }
+ else if (token.equals("-u"))
+ {
+ showUpdate = true;
+ }
+ }
+ }
+ Bundle[] bundles = m_context.getBundles();
+ if (bundles != null)
+ {
+ // Display active start level.
+ if (sl != null)
+ {
+ out.println("START LEVEL " + sl.getStartLevel());
+ }
+
+ // Print column headers.
+ String msg = " Name";
+ if (showLoc)
+ {
+ msg = " Location";
+ }
+ else if (showUpdate)
+ {
+ msg = " Update location";
+ }
+ String level = (sl == null) ? "" : " Level ";
+ out.println(" ID " + " State " + level + msg);
+ for (int i = 0; i < bundles.length; i++)
+ {
+ // Get the bundle name or location.
+ String name = (String)
+ bundles[i].getHeaders().get(Constants.BUNDLE_NAME);
+ if (showLoc)
+ {
+ name = bundles[i].getLocation();
+ }
+ else if (showUpdate)
+ {
+ Dictionary dict = bundles[i].getHeaders();
+ name = (String) dict.get(Constants.BUNDLE_UPDATELOCATION);
+ if (name == null)
+ {
+ name = bundles[i].getLocation();
+ }
+ }
+ // Show bundle version if not showing location.
+ String version = (String)
+ bundles[i].getHeaders().get(Constants.BUNDLE_VERSION);
+ name = (!showLoc && !showUpdate && (version != null))
+ ? name + " (" + version + ")" : name;
+ long l = bundles[i].getBundleId();
+ String id = String.valueOf(l);
+ if (sl == null)
+ {
+ level = "1";
+ }
+ else
+ {
+ level = String.valueOf(sl.getBundleStartLevel(bundles[i]));
+ }
+ while (level.length() < 5)
+ {
+ level = " " + level;
+ }
+ while (id.length() < 4)
+ {
+ id = " " + id;
+ }
+ out.println("[" + id + "] ["
+ + getStateString(bundles[i].getState())
+ + "] [" + level + "] " + name);
+ }
+ }
+ else
+ {
+ out.println("There are no installed bundles.");
+ }
+ }
+
+ public String getStateString(int i)
+ {
+ if (i == Bundle.ACTIVE)
+ return "Active ";
+ else if (i == Bundle.INSTALLED)
+ return "Installed ";
+ else if (i == Bundle.RESOLVED)
+ return "Resolved ";
+ else if (i == Bundle.STARTING)
+ return "Starting ";
+ else if (i == Bundle.STOPPING)
+ return "Stopping ";
+ return "Unknown ";
+ }
+}
diff --git a/src/org/apache/osgi/bundle/shell/RefreshCommandImpl.java b/src/org/apache/osgi/bundle/shell/RefreshCommandImpl.java
new file mode 100644
index 0000000..25d063b
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/RefreshCommandImpl.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+public class RefreshCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public RefreshCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "refresh";
+ }
+
+ public String getUsage()
+ {
+ return "refresh";
+ }
+
+ public String getShortDescription()
+ {
+ return "refresh packages.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ // Get package admin service.
+ ServiceReference ref = m_context.getServiceReference(
+ org.osgi.service.packageadmin.PackageAdmin.class.getName());
+ if (ref == null)
+ {
+ out.println("PackageAdmin service is unavailable.");
+ return;
+ }
+
+ PackageAdmin pa = (PackageAdmin) m_context.getService(ref);
+ if (pa == null)
+ {
+ out.println("PackageAdmin service is unavailable.");
+ return;
+ }
+
+ pa.refreshPackages(null);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/ServicesCommandImpl.java b/src/org/apache/osgi/bundle/shell/ServicesCommandImpl.java
new file mode 100644
index 0000000..af6bb3f
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/ServicesCommandImpl.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.*;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class ServicesCommandImpl implements Command
+{
+ private static final String IN_USE_SWITCH = "-u";
+ private static final String SHOW_ALL_SWITCH = "-a";
+
+ private BundleContext m_context = null;
+
+ public ServicesCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "services";
+ }
+
+ public String getUsage()
+ {
+ return "services [-u] [-a] [<id> ...]";
+ }
+
+ public String getShortDescription()
+ {
+ return "list registered or used services.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ // Put the remaining tokens into a list.
+ List tokens = new ArrayList();
+ for (int i = 0; st.hasMoreTokens(); i++)
+ {
+ tokens.add(st.nextToken());
+ }
+
+ // Default switch values.
+ boolean inUse = false;
+ boolean showAll = false;
+
+ // Check for "in use" switch.
+ if (tokens.contains(IN_USE_SWITCH))
+ {
+ // Remove the switch and set boolean flag.
+ tokens.remove(IN_USE_SWITCH);
+ inUse = true;
+ }
+
+ // Check for "show all" switch.
+ if (tokens.contains(SHOW_ALL_SWITCH))
+ {
+ // Remove the switch and set boolean flag.
+ tokens.remove(SHOW_ALL_SWITCH);
+ showAll = true;
+ }
+
+ // If there are bundle IDs specified then print their
+ // services and associated service properties, otherwise
+ // list all bundles and services.
+ if (tokens.size() >= 1)
+ {
+ while (tokens.size() > 0)
+ {
+ String id = (String) tokens.remove(0);
+
+ boolean headerPrinted = false;
+ boolean needSeparator = false;
+
+ try
+ {
+ long l = Long.parseLong(id);
+ Bundle bundle = m_context.getBundle(l);
+ if (bundle != null)
+ {
+ ServiceReference[] refs = null;
+
+ // Get registered or in-use services.
+ if (inUse)
+ {
+ refs = bundle.getServicesInUse();
+ }
+ else
+ {
+ refs = bundle.getRegisteredServices();
+ }
+
+ // Print properties for each service.
+ for (int refIdx = 0;
+ (refs != null) && (refIdx < refs.length);
+ refIdx++)
+ {
+ String[] objectClass = (String[])
+ refs[refIdx].getProperty("objectClass");
+
+ // Determine if we need to print the service, depending
+ // on whether it is a command service or not.
+ boolean print = true;
+ for (int ocIdx = 0;
+ !showAll && (ocIdx < objectClass.length);
+ ocIdx++)
+ {
+ if (objectClass[ocIdx].equals(
+ org.apache.osgi.service.shell.Command.class.getName()))
+ {
+ print = false;
+ }
+ }
+
+ // Print header if we have not already done so.
+ if (!headerPrinted)
+ {
+ headerPrinted = true;
+ String title = Util.getBundleName(bundle);
+ title = (inUse)
+ ? title + " uses:"
+ : title + " provides:";
+ out.println("");
+ out.println(title);
+ out.println(Util.getUnderlineString(title));
+ }
+
+ if (showAll || print)
+ {
+ // Print service separator if necessary.
+ if (needSeparator)
+ {
+ out.println("----");
+ }
+
+ // Print service properties.
+ String[] keys = refs[refIdx].getPropertyKeys();
+ for (int keyIdx = 0;
+ (keys != null) && (keyIdx < keys.length);
+ keyIdx++)
+ {
+ Object v = refs[refIdx].getProperty(keys[keyIdx]);
+ out.println(
+ keys[keyIdx] + " = " + Util.getValueString(v));
+ }
+
+ needSeparator = true;
+ }
+ }
+ }
+ else
+ {
+ err.println("Bundle ID " + id + " is invalid.");
+ }
+ }
+ catch (NumberFormatException ex)
+ {
+ err.println("Unable to parse id '" + id + "'.");
+ }
+ catch (Exception ex)
+ {
+ err.println(ex.toString());
+ }
+ }
+ }
+ else
+ {
+ Bundle[] bundles = m_context.getBundles();
+ if (bundles != null)
+ {
+ // TODO: Sort list.
+ for (int bundleIdx = 0; bundleIdx < bundles.length; bundleIdx++)
+ {
+ boolean headerPrinted = false;
+ ServiceReference[] refs = null;
+
+ // Get registered or in-use services.
+ if (inUse)
+ {
+ refs = bundles[bundleIdx].getServicesInUse();
+ }
+ else
+ {
+ refs = bundles[bundleIdx].getRegisteredServices();
+ }
+
+ for (int refIdx = 0; (refs != null) && (refIdx < refs.length); refIdx++)
+ {
+ String[] objectClass = (String[])
+ refs[refIdx].getProperty("objectClass");
+
+ // Determine if we need to print the service, depending
+ // on whether it is a command service or not.
+ boolean print = true;
+ for (int ocIdx = 0;
+ !showAll && (ocIdx < objectClass.length);
+ ocIdx++)
+ {
+ if (objectClass[ocIdx].equals(
+ org.apache.osgi.service.shell.Command.class.getName()))
+ {
+ print = false;
+ }
+ }
+
+ // Print the service if necessary.
+ if (showAll || print)
+ {
+ if (!headerPrinted)
+ {
+ headerPrinted = true;
+ String title = Util.getBundleName(bundles[bundleIdx]);
+ title = (inUse)
+ ? title + " uses:"
+ : title + " provides:";
+ out.println("\n" + title);
+ out.println(Util.getUnderlineString(title));
+ }
+ out.println(Util.getValueString(objectClass));
+ }
+ }
+ }
+ }
+ else
+ {
+ out.println("There are no registered services.");
+ }
+ }
+ }
+}
diff --git a/src/org/apache/osgi/bundle/shell/ShutdownCommandImpl.java b/src/org/apache/osgi/bundle/shell/ShutdownCommandImpl.java
new file mode 100644
index 0000000..b92e7c5
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/ShutdownCommandImpl.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+public class ShutdownCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public ShutdownCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "shutdown";
+ }
+
+ public String getUsage()
+ {
+ return "shutdown";
+ }
+
+ public String getShortDescription()
+ {
+ return "shutdown framework.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ // Get system bundle and use it to shutdown Felix.
+ try
+ {
+ Bundle bundle = m_context.getBundle(0);
+ bundle.stop();
+ }
+ catch (Exception ex)
+ {
+ err.println(ex.toString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/StartCommandImpl.java b/src/org/apache/osgi/bundle/shell/StartCommandImpl.java
new file mode 100644
index 0000000..a03405d
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/StartCommandImpl.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class StartCommandImpl extends InstallCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public StartCommandImpl(BundleContext context)
+ {
+ super(context);
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "start";
+ }
+
+ public String getUsage()
+ {
+ return "start <id> [<id> <URL> ...]";
+ }
+
+ public String getShortDescription()
+ {
+ return "start bundle(s).";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ // There should be at least one bundle id.
+ if (st.countTokens() >= 1)
+ {
+ while (st.hasMoreTokens())
+ {
+ String id = st.nextToken().trim();
+
+ try {
+ Bundle bundle = null;
+
+ // The id may be a number or a URL, so check.
+ if (Character.isDigit(id.charAt(0)))
+ {
+ long l = Long.parseLong(id);
+ bundle = m_context.getBundle(l);
+ }
+ else
+ {
+ bundle = install(id, out, err);
+ }
+
+ if (bundle != null)
+ {
+ bundle.start();
+ }
+ else
+ {
+ err.println("Bundle ID " + id + " is invalid.");
+ }
+ } catch (NumberFormatException ex) {
+ err.println("Unable to parse id '" + id + "'.");
+ } catch (BundleException ex) {
+ if (ex.getNestedException() != null)
+ {
+ ex.printStackTrace();
+ err.println(ex.getNestedException().toString());
+ }
+ else
+ {
+ err.println(ex.toString());
+ }
+ } catch (Exception ex) {
+ err.println(ex.toString());
+ }
+ }
+ }
+ else
+ {
+ err.println("Incorrect number of arguments");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/StartLevelCommandImpl.java b/src/org/apache/osgi/bundle/shell/StartLevelCommandImpl.java
new file mode 100644
index 0000000..d2c0303
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/StartLevelCommandImpl.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.startlevel.StartLevel;
+
+public class StartLevelCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public StartLevelCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "startlevel";
+ }
+
+ public String getUsage()
+ {
+ return "startlevel [<level>]";
+ }
+
+ public String getShortDescription()
+ {
+ return "get or set framework start level.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ // Get start level service.
+ ServiceReference ref = m_context.getServiceReference(
+ org.osgi.service.startlevel.StartLevel.class.getName());
+ if (ref == null)
+ {
+ out.println("StartLevel service is unavailable.");
+ return;
+ }
+
+ StartLevel sl = (StartLevel) m_context.getService(ref);
+ if (sl == null)
+ {
+ out.println("StartLevel service is unavailable.");
+ return;
+ }
+
+ // Parse command line.
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ if (st.countTokens() == 0)
+ {
+ out.println("Level " + sl.getStartLevel());
+ }
+ else if (st.countTokens() >= 1)
+ {
+ String levelStr = st.nextToken().trim();
+
+ try {
+ int level = Integer.parseInt(levelStr);
+ sl.setStartLevel(level);
+ } catch (NumberFormatException ex) {
+ err.println("Unable to parse integer '" + levelStr + "'.");
+ } catch (Exception ex) {
+ err.println(ex.toString());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/StopCommandImpl.java b/src/org/apache/osgi/bundle/shell/StopCommandImpl.java
new file mode 100644
index 0000000..5ae2713
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/StopCommandImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class StopCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public StopCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "stop";
+ }
+
+ public String getUsage()
+ {
+ return "stop <id> [<id> ...]";
+ }
+
+ public String getShortDescription()
+ {
+ return "stop bundle(s).";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ // There should be at least one bundle id.
+ if (st.countTokens() >= 1)
+ {
+ while (st.hasMoreTokens())
+ {
+ String id = st.nextToken().trim();
+
+ try {
+ long l = Long.parseLong(id);
+ Bundle bundle = m_context.getBundle(l);
+ if (bundle != null)
+ {
+ bundle.stop();
+ }
+ else
+ {
+ err.println("Bundle ID " + id + " is invalid.");
+ }
+ } catch (NumberFormatException ex) {
+ err.println("Unable to parse id '" + id + "'.");
+ } catch (BundleException ex) {
+ if (ex.getNestedException() != null)
+ err.println(ex.getNestedException().toString());
+ else
+ err.println(ex.toString());
+ } catch (Exception ex) {
+ err.println(ex.toString());
+ }
+ }
+ }
+ else
+ {
+ err.println("Incorrect number of arguments");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/UninstallCommandImpl.java b/src/org/apache/osgi/bundle/shell/UninstallCommandImpl.java
new file mode 100644
index 0000000..2182d6b
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/UninstallCommandImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class UninstallCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public UninstallCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "uninstall";
+ }
+
+ public String getUsage()
+ {
+ return "uninstall <id> [<id> ...]";
+ }
+
+ public String getShortDescription()
+ {
+ return "uninstall bundle(s).";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ // There must be at least one bundle ID.
+ if (st.countTokens() >= 1)
+ {
+ while (st.hasMoreTokens())
+ {
+ String id = st.nextToken().trim();
+
+ try {
+ long l = Long.parseLong(id);
+ Bundle bundle = m_context.getBundle(l);
+ if (bundle != null)
+ {
+ bundle.uninstall();
+ }
+ else
+ {
+ err.println("Bundle ID " + id + " is invalid.");
+ }
+ } catch (NumberFormatException ex) {
+ err.println("Unable to parse id '" + id + "'.");
+ } catch (BundleException ex) {
+ if (ex.getNestedException() != null)
+ err.println(ex.getNestedException().toString());
+ else
+ err.println(ex.toString());
+ } catch (Exception ex) {
+ err.println(ex.toString());
+ }
+ }
+ }
+ else
+ {
+ err.println("Incorrect number of arguments");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/UpdateCommandImpl.java b/src/org/apache/osgi/bundle/shell/UpdateCommandImpl.java
new file mode 100644
index 0000000..45c895c
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/UpdateCommandImpl.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.StringTokenizer;
+
+import org.apache.osgi.service.shell.CdCommand;
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.*;
+
+public class UpdateCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public UpdateCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "update";
+ }
+
+ public String getUsage()
+ {
+ return "update <id> [<URL>]";
+ }
+
+ public String getShortDescription()
+ {
+ return "update bundle.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ StringTokenizer st = new StringTokenizer(s, " ");
+
+ // Ignore the command name.
+ st.nextToken();
+
+ // There should be at least a bundle ID, but there may
+ // also be a URL.
+ if ((st.countTokens() == 1) || (st.countTokens() == 2))
+ {
+ String id = st.nextToken().trim();
+ String location = st.countTokens() == 0 ? null : st.nextToken().trim();
+
+ if (location != null)
+ {
+ location = absoluteLocation(location);
+
+ if (location == null)
+ {
+ err.println("Malformed location: " + location);
+ }
+ }
+
+ try
+ {
+ // Get the bundle id.
+ long l = Long.parseLong(id);
+
+ // Get the bundle.
+ Bundle bundle = m_context.getBundle(l);
+ if (bundle != null)
+ {
+ // Create input stream from location if present
+ // and use it to update, otherwise just update.
+ if (location != null)
+ {
+ InputStream is = new URL(location).openStream();
+ bundle.update(is);
+ }
+ else
+ {
+ bundle.update();
+ }
+ }
+ else
+ {
+ err.println("Bundle ID " + id + " is invalid.");
+ }
+ }
+ catch (NumberFormatException ex)
+ {
+ err.println("Unable to parse id '" + id + "'.");
+ }
+ catch (MalformedURLException ex)
+ {
+ err.println("Unable to parse URL.");
+ }
+ catch (IOException ex)
+ {
+ err.println("Unable to open input stream: " + ex);
+ }
+ catch (BundleException ex)
+ {
+ if (ex.getNestedException() != null)
+ {
+ err.println(ex.getNestedException().toString());
+ }
+ else
+ {
+ err.println(ex.toString());
+ }
+ }
+ catch (Exception ex)
+ {
+ err.println(ex.toString());
+ }
+ }
+ else
+ {
+ err.println("Incorrect number of arguments");
+ }
+ }
+
+ private String absoluteLocation(String location)
+ {
+ if (!location.endsWith(".jar"))
+ {
+ location = location + ".jar";
+ }
+ try
+ {
+ new URL(location);
+ }
+ catch (MalformedURLException ex)
+ {
+ // Try to create a valid URL using the base URL
+ // contained in the "cd" command service.
+ String baseURL = "";
+
+ try
+ {
+ // Get a reference to the "cd" command service.
+ ServiceReference ref = m_context.getServiceReference(
+ org.apache.osgi.service.shell.CdCommand.class.getName());
+
+ if (ref != null)
+ {
+ CdCommand cd = (CdCommand) m_context.getService(ref);
+ baseURL = cd.getBaseURL();
+ baseURL = (baseURL == null) ? "" : baseURL;
+ m_context.ungetService(ref);
+ }
+
+ String theURL = baseURL + location;
+ new URL(theURL);
+
+ }
+ catch (Exception ex2)
+ {
+ return null;
+ }
+ location = baseURL + location;
+ }
+ return location;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/Util.java b/src/org/apache/osgi/bundle/shell/Util.java
new file mode 100644
index 0000000..5eb0149
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/Util.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+public class Util
+{
+ public static String getBundleName(Bundle bundle)
+ {
+ if (bundle != null)
+ {
+ String name = (String) bundle.getHeaders().get(Constants.BUNDLE_NAME);
+ return (name == null)
+ ? "Bundle " + Long.toString(bundle.getBundleId())
+ : name + " (" + Long.toString(bundle.getBundleId()) + ")";
+ }
+ return "[STALE BUNDLE]";
+ }
+
+ private static StringBuffer m_sb = new StringBuffer();
+
+ public static String getUnderlineString(String s)
+ {
+ synchronized (m_sb)
+ {
+ m_sb.delete(0, m_sb.length());
+ for (int i = 0; i < s.length(); i++)
+ {
+ m_sb.append('-');
+ }
+ return m_sb.toString();
+ }
+ }
+
+ public static String getValueString(Object obj)
+ {
+ synchronized (m_sb)
+ {
+ if (obj instanceof String)
+ {
+ return (String) obj;
+ }
+ else if (obj instanceof String[])
+ {
+ String[] array = (String[]) obj;
+ m_sb.delete(0, m_sb.length());
+ for (int i = 0; i < array.length; i++)
+ {
+ if (i != 0)
+ {
+ m_sb.append(", ");
+ }
+ m_sb.append(array[i].toString());
+ }
+ return m_sb.toString();
+ }
+ else if (obj instanceof Boolean)
+ {
+ return ((Boolean) obj).toString();
+ }
+ else if (obj instanceof Long)
+ {
+ return ((Long) obj).toString();
+ }
+ else if (obj instanceof Integer)
+ {
+ return ((Integer) obj).toString();
+ }
+ else if (obj instanceof Short)
+ {
+ return ((Short) obj).toString();
+ }
+ else if (obj instanceof Double)
+ {
+ return ((Double) obj).toString();
+ }
+ else if (obj instanceof Float)
+ {
+ return ((Float) obj).toString();
+ }
+
+ return "<unknown value type>";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/bundle/shell/VersionCommandImpl.java b/src/org/apache/osgi/bundle/shell/VersionCommandImpl.java
new file mode 100644
index 0000000..39133af
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/VersionCommandImpl.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shell;
+
+import java.io.PrintStream;
+
+import org.apache.osgi.service.shell.Command;
+import org.osgi.framework.BundleContext;
+
+public class VersionCommandImpl implements Command
+{
+ private BundleContext m_context = null;
+
+ public VersionCommandImpl(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public String getName()
+ {
+ return "version";
+ }
+
+ public String getUsage()
+ {
+ return "version";
+ }
+
+ public String getShortDescription()
+ {
+ return "display version of framework.";
+ }
+
+ public void execute(String s, PrintStream out, PrintStream err)
+ {
+ out.println(m_context.getProperty("felix.version"));
+ }
+}
diff --git a/src/org/apache/osgi/bundle/shell/manifest.mf b/src/org/apache/osgi/bundle/shell/manifest.mf
new file mode 100644
index 0000000..6406fdb
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shell/manifest.mf
@@ -0,0 +1,13 @@
+Bundle-Name: Shell Service
+Bundle-SymbolicName: org.apache.osgi.bundle.shell
+Bundle-Description: A simple command shell service for Felix.
+Bundle-Version: 1.0.2
+Bundle-Activator: org.apache.osgi.bundle.shell.Activator
+Bundle-ClassPath: .
+Import-Package:
+ org.osgi.framework,
+ org.osgi.service.startlevel,
+ org.osgi.service.packageadmin
+Export-Package:
+ org.apache.osgi.service.shell; specification-version="1.0.0",
+ org.ungoverned.osgi.service.shell; specification-version="1.0.0"
diff --git a/src/org/apache/osgi/bundle/shelltui/Activator.java b/src/org/apache/osgi/bundle/shelltui/Activator.java
new file mode 100644
index 0000000..39840c6
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shelltui/Activator.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.bundle.shelltui;
+
+import java.io.*;
+
+import org.apache.osgi.service.shell.ShellService;
+import org.osgi.framework.*;
+
+public class Activator implements BundleActivator
+{
+ private transient BundleContext m_context = null;
+ private transient ShellTuiRunnable m_runnable = null;
+ private transient ServiceReference m_shellRef = null;
+ private transient ShellService m_shell = null;
+
+ public void start(BundleContext context)
+ {
+ m_context = context;
+
+ // Listen for registering/unregistering shell service.
+ ServiceListener sl = new ServiceListener() {
+ public void serviceChanged(ServiceEvent event)
+ {
+ synchronized (Activator.this)
+ {
+ // Ignore additional services if we already have one.
+ if ((event.getType() == ServiceEvent.REGISTERED)
+ && (m_shellRef != null))
+ {
+ return;
+ }
+ // Initialize the service if we don't have one.
+ else if ((event.getType() == ServiceEvent.REGISTERED)
+ && (m_shellRef == null))
+ {
+ initializeService();
+ }
+ // Unget the service if it is unregistering.
+ else if ((event.getType() == ServiceEvent.UNREGISTERING)
+ && event.getServiceReference().equals(m_shellRef))
+ {
+ m_context.ungetService(m_shellRef);
+ m_shellRef = null;
+ m_shell = null;
+ // Try to get another service.
+ initializeService();
+ }
+ }
+ }
+ };
+ try {
+ m_context.addServiceListener(sl,
+ "(objectClass="
+ + org.apache.osgi.service.shell.ShellService.class.getName()
+ + ")");
+ } catch (InvalidSyntaxException ex) {
+ System.err.println("ShellTuiActivator: Cannot add service listener.");
+ System.err.println("ShellTuiActivator: " + ex);
+ }
+
+ // Now try to manually initialize the shell service
+ // since one might already be available.
+ initializeService();
+
+ // Start shell thread.
+ new Thread(
+ m_runnable = new ShellTuiRunnable(),
+ "Felix Shell TUI").start();
+ }
+
+ private synchronized void initializeService()
+ {
+ if (m_shell != null)
+ return;
+ m_shellRef = m_context.getServiceReference(
+ org.apache.osgi.service.shell.ShellService.class.getName());
+ if (m_shellRef == null)
+ return;
+ m_shell = (ShellService) m_context.getService(m_shellRef);
+ }
+
+ public void stop(BundleContext context)
+ {
+ if (m_runnable != null)
+ {
+ m_runnable.stop();
+ }
+ }
+
+ private class ShellTuiRunnable implements Runnable
+ {
+ private boolean stop = false;
+
+ public void stop()
+ {
+ stop = true;
+ }
+
+ public void run()
+ {
+ String line = null;
+ BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+
+ while (!stop)
+ {
+ System.out.print("-> ");
+
+ try {
+ line = in.readLine();
+ } catch (IOException ex) {
+ System.err.println("Could not read input, please try again.");
+ continue;
+ }
+
+ synchronized (Activator.this)
+ {
+ if (m_shell == null)
+ {
+ System.out.println("No shell service available.");
+ continue;
+ }
+
+ if (line == null)
+ {
+ continue;
+ }
+
+ line = line.trim();
+
+ if (line.length() == 0)
+ {
+ continue;
+ }
+
+ try {
+ m_shell.executeCommand(line, System.out, System.err);
+ } catch (Exception ex) {
+ System.err.println("ShellTuiActivator: " + ex);
+ ex.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/org/apache/osgi/bundle/shelltui/manifest.mf b/src/org/apache/osgi/bundle/shelltui/manifest.mf
new file mode 100644
index 0000000..f8f17d4
--- /dev/null
+++ b/src/org/apache/osgi/bundle/shelltui/manifest.mf
@@ -0,0 +1,7 @@
+Bundle-Name: Shell TUI
+Bundle-SymbolicName: org.apache.osgi.bundle.shelltui
+Bundle-Description: A simple textual user interface for Felix's the shell service.
+Bundle-Version: 1.0.0
+Bundle-Activator: org.apache.osgi.bundle.shelltui.Activator
+Bundle-ClassPath: .
+Import-Package: org.osgi.framework, org.apache.osgi.service.shell
diff --git a/src/org/apache/osgi/framework/BundleContextImpl.java b/src/org/apache/osgi/framework/BundleContextImpl.java
new file mode 100644
index 0000000..e0114d2
--- /dev/null
+++ b/src/org/apache/osgi/framework/BundleContextImpl.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Dictionary;
+
+import org.apache.osgi.framework.ext.FelixBundleContext;
+import org.osgi.framework.*;
+
+class BundleContextImpl implements FelixBundleContext
+{
+ private Felix m_felix = null;
+ private BundleImpl m_bundle = null;
+
+ protected BundleContextImpl(Felix felix, BundleImpl bundle)
+ {
+ m_felix = felix;
+ m_bundle = bundle;
+ }
+
+ public void addImportPackage() throws BundleException
+ {
+ throw new BundleException("Not implemented yet.");
+ }
+
+ public void removeImportPackage() throws BundleException
+ {
+ throw new BundleException("Not implemented yet.");
+ }
+
+ public void addExportPackage() throws BundleException
+ {
+ throw new BundleException("Not implemented yet.");
+ }
+
+ public void removeExportPackage() throws BundleException
+ {
+ throw new BundleException("Not implemented yet.");
+ }
+
+ public String getProperty(String name)
+ {
+ return m_felix.getProperty(name);
+ }
+
+ public Bundle getBundle()
+ {
+ return m_bundle;
+ }
+
+ public Filter createFilter(String expr)
+ throws InvalidSyntaxException
+ {
+ return new FilterImpl(m_felix.getLogger(), expr);
+ }
+
+ public Bundle installBundle(String location)
+ throws BundleException
+ {
+ return installBundle(location, null);
+ }
+
+ public Bundle installBundle(String location, InputStream is)
+ throws BundleException
+ {
+ return m_felix.installBundle(location, is);
+ }
+
+ public Bundle getBundle(long id)
+ {
+ return m_felix.getBundle(id);
+ }
+
+ public Bundle[] getBundles()
+ {
+ return m_felix.getBundles();
+ }
+
+ public void addBundleListener(BundleListener l)
+ {
+ m_felix.addBundleListener(m_bundle, l);
+ }
+
+ public void removeBundleListener(BundleListener l)
+ {
+ m_felix.removeBundleListener(l);
+ }
+
+ public void addServiceListener(ServiceListener l)
+ {
+ try
+ {
+ addServiceListener(l, null);
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ // This will not happen since the filter is null.
+ }
+ }
+
+ public void addServiceListener(ServiceListener l, String s)
+ throws InvalidSyntaxException
+ {
+ m_felix.addServiceListener(m_bundle, l, s);
+ }
+
+ public void removeServiceListener(ServiceListener l)
+ {
+ m_felix.removeServiceListener(l);
+ }
+
+ public void addFrameworkListener(FrameworkListener l)
+ {
+ m_felix.addFrameworkListener(m_bundle, l);
+ }
+
+ public void removeFrameworkListener(FrameworkListener l)
+ {
+ m_felix.removeFrameworkListener(l);
+ }
+
+ public ServiceRegistration registerService(
+ String clazz, Object svcObj, Dictionary dict)
+ {
+ return registerService(new String[] { clazz }, svcObj, dict);
+ }
+
+ public ServiceRegistration registerService(
+ String[] clazzes, Object svcObj, Dictionary dict)
+ {
+ return m_felix.registerService(m_bundle, clazzes, svcObj, dict);
+ }
+
+ public ServiceReference getServiceReference(String clazz)
+ {
+ try
+ {
+ ServiceReference[] refs = getServiceReferences(clazz, null);
+ return getBestServiceReference(refs);
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ m_felix.getLogger().log(LogWrapper.LOG_ERROR, "BundleContextImpl: " + ex);
+ }
+ return null;
+ }
+
+ private ServiceReference getBestServiceReference(ServiceReference[] refs)
+ {
+ if (refs == null)
+ {
+ return null;
+ }
+
+ if (refs.length == 1)
+ {
+ return refs[0];
+ }
+
+ // Loop through all service references and return
+ // the "best" one according to its rank and ID.
+ ServiceReference bestRef = null;
+ Integer bestRank = null;
+ Long bestId = null;
+ for (int i = 0; i < refs.length; i++)
+ {
+ ServiceReference ref = refs[i];
+
+ // The first time through the loop just
+ // assume that the first reference is best.
+ if (bestRef == null)
+ {
+ bestRef = ref;
+ bestRank = (Integer) bestRef.getProperty("service.ranking");
+ // The spec says no ranking defaults to zero.
+ if (bestRank == null)
+ {
+ bestRank = new Integer(0);
+ }
+ bestId = (Long) bestRef.getProperty("service.id");
+ }
+
+ // Compare current and best references to see if
+ // the current reference is a better choice.
+ Integer rank = (Integer) ref.getProperty("service.ranking");
+
+ // The spec says no ranking defaults to zero.
+ if (rank == null)
+ {
+ rank = new Integer(0);
+ }
+
+ // If the current reference ranking is greater than the
+ // best ranking, then keep the current reference.
+ if (bestRank.compareTo(rank) < 0)
+ {
+ bestRef = ref;
+ bestRank = rank;
+ bestId = (Long) bestRef.getProperty("service.id");
+ }
+ // If rankings are equal, then compare IDs and
+ // keep the smallest.
+ else if (bestRank.compareTo(rank) == 0)
+ {
+ Long id = (Long) ref.getProperty("service.id");
+ // If either reference has a null ID, then keep
+ // the one with a non-null ID.
+ if ((bestId == null) || (id == null))
+ {
+ bestRef = (bestId == null) ? ref : bestRef;
+ // bestRank = bestRank; // No need to update since they are equal.
+ bestId = (Long) bestRef.getProperty("service.id");
+ }
+ // Otherwise compare IDs.
+ else
+ {
+ // If the current reference ID is less than the
+ // best ID, then keep the current reference.
+ if (bestId.compareTo(id) > 0)
+ {
+ bestRef = ref;
+ // bestRank = bestRank; // No need to update since they are equal.
+ bestId = (Long) bestRef.getProperty("service.id");
+ }
+ }
+ }
+ }
+
+ return bestRef;
+ }
+
+ public ServiceReference[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException
+ {
+ // TODO: Implement BundleContext.getAllServiceReferences()
+ return null;
+ }
+
+ public ServiceReference[] getServiceReferences(String clazz, String filter)
+ throws InvalidSyntaxException
+ {
+ return m_felix.getServiceReferences(m_bundle, clazz, filter);
+ }
+
+ public Object getService(ServiceReference ref)
+ {
+ if (ref == null)
+ {
+ throw new NullPointerException("Specified service reference cannot be null.");
+ }
+ return m_felix.getService(m_bundle, ref);
+ }
+
+ public boolean ungetService(ServiceReference ref)
+ {
+ if (ref == null)
+ {
+ throw new NullPointerException("Specified service reference cannot be null.");
+ }
+
+ // Unget the specified service.
+ return m_felix.ungetService(m_bundle, ref);
+ }
+
+ public File getDataFile(String s)
+ {
+ return m_felix.getDataFile(m_bundle, s);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/BundleImpl.java b/src/org/apache/osgi/framework/BundleImpl.java
new file mode 100644
index 0000000..0b1d056
--- /dev/null
+++ b/src/org/apache/osgi/framework/BundleImpl.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+import org.osgi.framework.*;
+
+class BundleImpl implements Bundle
+{
+ private Felix m_felix = null;
+ private BundleInfo m_info = null;
+
+ protected BundleImpl(Felix felix, BundleInfo info)
+ {
+ m_felix = felix;
+ m_info = info;
+ }
+
+ Felix getFelix() // package protected
+ {
+ return m_felix;
+ }
+
+ BundleInfo getInfo() // package protected
+ {
+ return m_info;
+ }
+
+ void setInfo(BundleInfo info) // package protected
+ {
+ m_info = info;
+ }
+
+ public long getBundleId()
+ {
+ return m_info.getBundleId();
+ }
+
+ public Dictionary getHeaders()
+ {
+ return m_felix.getBundleHeaders(this);
+ }
+
+ public String getLocation()
+ {
+ return m_felix.getBundleLocation(this);
+ }
+
+ /**
+ * Returns a URL to a named resource in the bundle.
+ *
+ * @return a URL to named resource, or null if not found.
+ **/
+ public URL getResource(String name)
+ {
+ return m_felix.getBundleResource(this, name);
+ }
+
+ /**
+ * Returns an array of service references corresponding to
+ * the bundle's registered services.
+ *
+ * @return an array of service references or null.
+ **/
+ public ServiceReference[] getRegisteredServices()
+ {
+ return m_felix.getBundleRegisteredServices(this);
+ }
+
+ public ServiceReference[] getServicesInUse()
+ {
+ return m_felix.getBundleServicesInUse(this);
+ }
+
+ public int getState()
+ {
+ return m_info.getState();
+ }
+
+ public boolean hasPermission(Object obj)
+ {
+ return m_felix.bundleHasPermission(this, obj);
+ }
+
+ public void start() throws BundleException
+ {
+ m_felix.startBundle(this, true);
+ }
+
+ public void update() throws BundleException
+ {
+ update(null);
+ }
+
+ public void update(InputStream is) throws BundleException
+ {
+ m_felix.updateBundle(this, is);
+ }
+
+ public void stop() throws BundleException
+ {
+ m_felix.stopBundle(this, true);
+ }
+
+ public void uninstall() throws BundleException
+ {
+ m_felix.uninstallBundle(this);
+ }
+
+ public String toString()
+ {
+ return "[" + getBundleId() +"]";
+ }
+
+ //
+ // PLACE FOLLOWING METHODS INTO PROPER LOCATION ONCE IMPLEMENTED.
+ //
+
+ public Dictionary getHeaders(String locale)
+ {
+ // TODO: Implement Bundle.getHeaders()
+ return null;
+ }
+
+ public String getSymbolicName()
+ {
+ // TODO: Implement Bundle.getSymbolicName()
+ return null;
+ }
+
+ public Class loadClass(String name) throws ClassNotFoundException
+ {
+ // TODO: Implement Bundle.loadClass()
+ return null;
+ }
+
+ public Enumeration getResources(String name) throws IOException
+ {
+ // TODO: Implement Bundle.getResources()
+ return null;
+ }
+
+ public Enumeration getEntryPaths(String path)
+ {
+ // TODO: Implement Bundle.getEntryPaths()
+ return null;
+ }
+
+ public URL getEntry(String name)
+ {
+ // TODO: Implement Bundle.getEntry()
+ return null;
+ }
+
+ public long getLastModified()
+ {
+ // TODO: Implement Bundle.getLastModified()
+ return 0;
+ }
+
+ public Enumeration findEntries(String path, String filePattern, boolean recurse)
+ {
+ // TODO: Implement Bundle.findEntries()
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/BundleInfo.java b/src/org/apache/osgi/framework/BundleInfo.java
new file mode 100644
index 0000000..5d589bb
--- /dev/null
+++ b/src/org/apache/osgi/framework/BundleInfo.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.util.Map;
+
+import org.apache.osgi.framework.cache.BundleArchive;
+import org.apache.osgi.moduleloader.Module;
+import org.osgi.framework.*;
+
+class BundleInfo
+{
+ private LogWrapper m_logger = null;
+ private BundleArchive m_archive = null;
+ private Module[] m_modules = null;
+ private int m_state = 0;
+ private BundleActivator m_activator = null;
+ private BundleContext m_context = null;
+ // Indicates that the bundle was either updated
+ // or uninstalled and is waiting to be removed or refreshed.
+ private boolean m_removalPending = false;
+
+ // Used for bundle locking.
+ private int m_lockCount = 0;
+ private Thread m_lockThread = null;
+
+ protected BundleInfo(LogWrapper logger, BundleArchive archive, Module module)
+ throws Exception
+ {
+ m_logger = logger;
+ m_archive = archive;
+ m_modules = (module == null) ? new Module[0] : new Module[] { module };
+
+ m_state = Bundle.INSTALLED;
+ m_removalPending = false;
+ m_activator = null;
+ m_context = null;
+ }
+
+ /**
+ * Returns the bundle archive associated with this bundle.
+ * @return the bundle archive associated with this bundle.
+ **/
+ public BundleArchive getArchive()
+ {
+ return m_archive;
+ }
+
+ /**
+ * Returns an array of all modules associated with the bundle represented by
+ * this <tt>BundleInfo</tt> object. A module in the array corresponds to a
+ * revision of the bundle's JAR file and is ordered from oldest to newest.
+ * Multiple revisions of a bundle JAR file might exist if a bundle is
+ * updated, without refreshing the framework. In this case, exports from
+ * the prior revisions of the bundle JAR file are still offered; the
+ * current revision will be bound to packages from the prior revision,
+ * unless the packages were not offered by the prior revision. There is
+ * no limit on the potential number of bundle JAR file revisions.
+ * @return array of modules corresponding to the bundle JAR file revisions.
+ **/
+ public Module[] getModules()
+ {
+ return m_modules;
+ }
+
+ /**
+ * Determines if the specified module is associated with this bundle.
+ * @param module the module to determine if it is associate with this bundle.
+ * @return <tt>true</tt> if the specified module is in the array of modules
+ * associated with this bundle, <tt>false</tt> otherwise.
+ **/
+ public boolean hasModule(Module module)
+ {
+ for (int i = 0; i < m_modules.length; i++)
+ {
+ if (m_modules[i] == module)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the newest module, which corresponds to the last module
+ * in the module array.
+ * @return the newest module.
+ **/
+ public Module getCurrentModule()
+ {
+ return m_modules[m_modules.length - 1];
+ }
+
+ /**
+ * Add a module that corresponds to a new bundle JAR file revision for
+ * the bundle associated with this <tt>BundleInfo</tt> object.
+ * @param module the module to add.
+ **/
+ public void addModule(Module module)
+ {
+ Module[] dest = new Module[m_modules.length + 1];
+ System.arraycopy(m_modules, 0, dest, 0, m_modules.length);
+ dest[m_modules.length] = module;
+ m_modules = dest;
+ }
+
+ public long getBundleId()
+ {
+ return m_archive.getId();
+ }
+
+ public String getLocation()
+ {
+ try
+ {
+ return m_archive.getLocation();
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Error reading location from bundle archive.",
+ ex);
+ return null;
+ }
+ }
+
+ public int getStartLevel(int defaultLevel)
+ {
+ try
+ {
+ return m_archive.getStartLevel();
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Error reading start level from bundle archive.",
+ ex);
+ return defaultLevel;
+ }
+ }
+
+ public void setStartLevel(int i)
+ {
+ try
+ {
+ m_archive.setStartLevel(i);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Error writing start level to bundle archive.",
+ ex);
+ }
+ }
+
+ public Map getCurrentHeader()
+ {
+ try
+ {
+ // Return the header for the most recent bundle revision only,
+ // since we shouldn't ever need access to older revisions.
+ return m_archive.getManifestHeader(m_archive.getRevisionCount() - 1);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Error reading manifest from bundle archive.",
+ ex);
+ return null;
+ }
+ }
+
+ public int getState()
+ {
+ return m_state;
+ }
+
+ public void setState(int i)
+ {
+ m_state = i;
+ }
+
+ public int getPersistentState()
+ {
+ try
+ {
+ return m_archive.getPersistentState();
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Error reading persistent state from bundle archive.",
+ ex);
+ return Bundle.INSTALLED;
+ }
+ }
+
+ public void setPersistentStateInactive()
+ {
+ try
+ {
+ m_archive.setPersistentState(Bundle.INSTALLED);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR,
+ "Error writing persistent state to bundle archive.",
+ ex);
+ }
+ }
+
+ public void setPersistentStateActive()
+ {
+ try
+ {
+ m_archive.setPersistentState(Bundle.ACTIVE);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Error writing persistent state to bundle archive.",
+ ex);
+ }
+ }
+
+ public void setPersistentStateUninstalled()
+ {
+ try
+ {
+ m_archive.setPersistentState(Bundle.UNINSTALLED);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Error writing persistent state to bundle archive.",
+ ex);
+ }
+ }
+
+ public BundleContext getContext()
+ {
+ return m_context;
+ }
+
+ public void setContext(BundleContext context)
+ {
+ m_context = context;
+ }
+
+ public BundleActivator getActivator()
+ {
+ return m_activator;
+ }
+
+ public void setActivator(BundleActivator activator)
+ {
+ m_activator = activator;
+ }
+
+ public boolean isRemovalPending()
+ {
+ return m_removalPending;
+ }
+
+ public void setRemovalPending()
+ {
+ m_removalPending = true;
+ }
+
+ //
+ // Locking related methods.
+ // NOTE: These methods are not synchronized because it is assumed they
+ // will only ever be called when the caller is in a synchronized block.
+ //
+
+ public boolean isLockable()
+ {
+ return (m_lockCount == 0) || (m_lockThread == Thread.currentThread());
+ }
+
+ public void lock()
+ {
+ if ((m_lockCount > 0) && (m_lockThread != Thread.currentThread()))
+ {
+ throw new IllegalStateException("Bundle is locked by another thread.");
+ }
+ m_lockCount++;
+ m_lockThread = Thread.currentThread();
+ }
+
+ public void unlock()
+ {
+ if (m_lockCount == 0)
+ {
+ throw new IllegalStateException("Bundle is not locked.");
+ }
+ if ((m_lockCount > 0) && (m_lockThread != Thread.currentThread()))
+ {
+ throw new IllegalStateException("Bundle is locked by another thread.");
+ }
+ m_lockCount--;
+ if (m_lockCount == 0)
+ {
+ m_lockThread = null;
+ }
+ }
+
+ public void syncLock(BundleInfo info)
+ {
+ m_lockCount = info.m_lockCount;
+ m_lockThread = info.m_lockThread;
+ }
+
+ /**
+ * Converts a module identifier to a bundle identifier. Module IDs
+ * are typically <tt><bundle-id>.<revision></tt>; this
+ * method returns only the portion corresponding to the bundle ID.
+ **/
+ protected static long getBundleIdFromModuleId(String id)
+ {
+ try
+ {
+ String bundleId = (id.indexOf('.') >= 0)
+ ? id.substring(0, id.indexOf('.')) : id;
+ return Long.parseLong(bundleId);
+ }
+ catch (NumberFormatException ex)
+ {
+ return -1;
+ }
+ }
+
+ /**
+ * Converts a module identifier to a bundle identifier. Module IDs
+ * are typically <tt><bundle-id>.<revision></tt>; this
+ * method returns only the portion corresponding to the revision.
+ **/
+ protected static int getModuleRevisionFromModuleId(String id)
+ {
+ try
+ {
+ String rev = (id.indexOf('.') >= 0)
+ ? id.substring(id.indexOf('.') + 1) : id;
+ return Integer.parseInt(rev);
+ }
+ catch (NumberFormatException ex)
+ {
+ return -1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/BundleURLConnection.java b/src/org/apache/osgi/framework/BundleURLConnection.java
new file mode 100644
index 0000000..ca890b2
--- /dev/null
+++ b/src/org/apache/osgi/framework/BundleURLConnection.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.Permission;
+
+import org.apache.osgi.moduleloader.*;
+
+class BundleURLConnection extends URLConnection
+{
+ private ModuleManager m_mgr = null;
+ private int contentLength;
+ private long contentTime;
+ private String contentType;
+ private InputStream is;
+
+ public BundleURLConnection(ModuleManager mgr, URL url)
+ {
+ super(url);
+ m_mgr = mgr;
+ }
+
+ public void connect() throws IOException
+ {
+ if (!connected)
+ {
+ // The URL is constructed like this:
+ // bundle://<module-id>/<source-idx>/<resource-path>
+
+ Module module = m_mgr.getModule(url.getHost());
+ if (module == null)
+ {
+ throw new IOException("Unable to find bundle's module.");
+ }
+
+ String resource = url.getFile();
+ if (resource == null)
+ {
+ throw new IOException("Unable to find resource: " + url.toString());
+ }
+ if (resource.startsWith("/"))
+ {
+ resource = resource.substring(1);
+ }
+ int rsIdx = -1;
+ try
+ {
+ rsIdx = Integer.parseInt(resource.substring(0, resource.indexOf("/")));
+ }
+ catch (NumberFormatException ex)
+ {
+ new IOException("Error parsing resource index.");
+ }
+ resource = resource.substring(resource.indexOf("/") + 1);
+
+ // Get the resource bytes from the resource source.
+ byte[] bytes = null;
+ ResourceSource[] resSources = module.getResourceSources();
+ if ((resSources != null) && (rsIdx < resSources.length))
+ {
+ if (resSources[rsIdx].hasResource(resource))
+ {
+ bytes = resSources[rsIdx].getBytes(resource);
+ }
+ }
+
+ if (bytes == null)
+ {
+ throw new IOException("Unable to find resource: " + url.toString());
+ }
+
+ is = new ByteArrayInputStream(bytes);
+ contentLength = bytes.length;
+ contentTime = 0L; // TODO: Change this.
+ contentType = URLConnection.guessContentTypeFromName(resource);
+ connected = true;
+ }
+ }
+
+ public InputStream getInputStream()
+ throws IOException
+ {
+ if (!connected)
+ {
+ connect();
+ }
+ return is;
+ }
+
+ public int getContentLength()
+ {
+ if (!connected)
+ {
+ try {
+ connect();
+ } catch(IOException ex) {
+ return -1;
+ }
+ }
+ return contentLength;
+ }
+
+ public long getLastModified()
+ {
+ if (!connected)
+ {
+ try {
+ connect();
+ } catch(IOException ex) {
+ return 0;
+ }
+ }
+ if(contentTime != -1L)
+ return contentTime;
+ else
+ return 0L;
+ }
+
+ public String getContentType()
+ {
+ if (!connected)
+ {
+ try {
+ connect();
+ } catch(IOException ex) {
+ return null;
+ }
+ }
+ return contentType;
+ }
+
+ public Permission getPermission()
+ {
+ // TODO: This should probably return a FilePermission
+ // to access the bundle JAR file, but we don't have the
+ // necessary information here to construct the absolute
+ // path of the JAR file...so it would take some
+ // re-arranging to get this to work.
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/BundleURLStreamHandler.java b/src/org/apache/osgi/framework/BundleURLStreamHandler.java
new file mode 100644
index 0000000..3948c63
--- /dev/null
+++ b/src/org/apache/osgi/framework/BundleURLStreamHandler.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.io.IOException;
+import java.net.*;
+
+import org.apache.osgi.moduleloader.ModuleManager;
+
+class BundleURLStreamHandler extends URLStreamHandler
+{
+ private ModuleManager m_mgr = null;
+
+ public BundleURLStreamHandler(ModuleManager mgr)
+ {
+ m_mgr = mgr;
+ }
+
+ protected URLConnection openConnection(URL url) throws IOException
+ {
+ return new BundleURLConnection(m_mgr, url);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/ExportedPackageImpl.java b/src/org/apache/osgi/framework/ExportedPackageImpl.java
new file mode 100644
index 0000000..19cbecc
--- /dev/null
+++ b/src/org/apache/osgi/framework/ExportedPackageImpl.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import org.apache.osgi.framework.searchpolicy.R4Version;
+import org.osgi.framework.Bundle;
+import org.osgi.service.packageadmin.ExportedPackage;
+
+class ExportedPackageImpl implements ExportedPackage
+{
+ private Felix m_felix = null;
+ private BundleImpl m_exporter = null;
+ private String m_name = null;
+ private R4Version m_version = null;
+ private String m_toString = null;
+ private String m_versionString = null;
+
+ public ExportedPackageImpl(
+ Felix felix, BundleImpl exporter, String name, R4Version version)
+ {
+ m_felix = felix;
+ m_exporter = exporter;
+ m_name = name;
+ m_version = version;
+ }
+
+ public Bundle getExportingBundle()
+ {
+ // If remove is pending due to a bundle update, then
+ // return null per the spec.
+ if (m_exporter.getInfo().isRemovalPending())
+ {
+ return null;
+ }
+ return m_exporter;
+ }
+
+ /**
+ * Returns the exporting bundle whether the package is state or
+ * not. This is called internally to get access to the exporting
+ * bundle during a refresh operation, which is not possible using
+ * <tt>getExportingBundle</tt> since the specification says that
+ * method must return <tt>null</tt> for stale packages.
+ * @return the exporting bundle for the package.
+ **/
+ protected Bundle getExportingBundleInternal()
+ {
+ return m_exporter;
+ }
+
+ public Bundle[] getImportingBundles()
+ {
+ // If remove is pending due to a bundle update, then
+ // return null per the spec.
+ if (m_exporter.getInfo().isRemovalPending())
+ {
+ return null;
+ }
+ return m_felix.getImportingBundles(this);
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public String getSpecificationVersion()
+ {
+ if (m_versionString == null)
+ {
+ if (m_version == null)
+ {
+ m_versionString = "0.0.0";
+ }
+ else
+ {
+ m_versionString = m_version.toString();
+ }
+ }
+ return m_versionString;
+ }
+
+ public boolean isRemovalPending()
+ {
+ return m_exporter.getInfo().isRemovalPending();
+ }
+
+ public String toString()
+ {
+ if (m_toString == null)
+ {
+ m_toString = m_name
+ + "; version=" + getSpecificationVersion();
+ }
+ return m_toString;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/FakeURLStreamHandler.java b/src/org/apache/osgi/framework/FakeURLStreamHandler.java
new file mode 100644
index 0000000..363c10f
--- /dev/null
+++ b/src/org/apache/osgi/framework/FakeURLStreamHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.io.IOException;
+import java.net.*;
+
+/**
+ * This class implements a fake stream handler. This class is necessary in
+ * some cases when assigning <tt>CodeSource</tt>s to classes in
+ * <tt>BundleClassLoader</tt>. In general, the bundle location is an URL
+ * and this URL is used as the code source for the bundle's associated
+ * classes. The OSGi specification does not require that the bundle
+ * location be an URL, though, so in that case we try to generate a
+ * fake URL for the code source of the bundle, which is just the location
+ * string prepended with the "location:" protocol, by default. We need
+ * this fake handler to avoid an unknown protocol exception.
+**/
+class FakeURLStreamHandler extends URLStreamHandler
+{
+ protected URLConnection openConnection(URL url) throws IOException
+ {
+ return null;
+ }
+}
diff --git a/src/org/apache/osgi/framework/Felix.java b/src/org/apache/osgi/framework/Felix.java
new file mode 100644
index 0000000..5fa6092
--- /dev/null
+++ b/src/org/apache/osgi/framework/Felix.java
@@ -0,0 +1,3675 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.io.*;
+import java.net.*;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.util.*;
+
+import org.apache.osgi.framework.cache.*;
+import org.apache.osgi.framework.searchpolicy.*;
+import org.apache.osgi.framework.util.*;
+import org.apache.osgi.framework.util.Util;
+import org.apache.osgi.moduleloader.*;
+import org.apache.osgi.moduleloader.search.ResolveException;
+import org.apache.osgi.moduleloader.search.ResolveListener;
+import org.osgi.framework.*;
+import org.osgi.service.packageadmin.ExportedPackage;
+
+public class Felix
+{
+ // Logging related member variables.
+ private LogWrapper m_logger = new LogWrapper();
+ // Config properties.
+ private PropertyResolver m_config = new ConfigImpl();
+ // Configuration properties passed into constructor.
+ private MutablePropertyResolver m_configProps = null;
+
+ // MODULE MANAGER.
+ private ModuleManager m_mgr = null;
+
+ // Object used as a lock when calculating which bundles
+ // when performing an operation on one or more bundles.
+ private Object[] m_bundleLock = new Object[0];
+
+ // Maps a bundle location to a bundle location;
+ // used to reserve a location when installing a bundle.
+ private Map m_installRequestMap = null;
+ // This lock must be acquired to modify m_installRequestMap;
+ // to help avoid deadlock this lock as priority 1 and should
+ // be acquired before locks with lower priority.
+ private Object[] m_installRequestLock_Priority1 = new Object[0];
+
+ // Maps a bundle location to a bundle.
+ private HashMap m_installedBundleMap = null;
+ // This lock must be acquired to modify m_installedBundleMap;
+ // to help avoid deadlock this lock as priority 2 and should
+ // be acquired before locks with lower priority.
+ private Object[] m_installedBundleLock_Priority2 = new Object[0];
+
+ // An array of uninstalled bundles before a refresh occurs.
+ private BundleImpl[] m_uninstalledBundles = null;
+ // This lock must be acquired to modify m_uninstalledBundles;
+ // to help avoid deadlock this lock as priority 3 and should
+ // be acquired before locks with lower priority.
+ private Object[] m_uninstalledBundlesLock_Priority3 = new Object[0];
+
+ // Status flag for framework.
+ public static final int INITIAL_STATUS = -1;
+ public static final int RUNNING_STATUS = 0;
+ public static final int STARTING_STATUS = 1;
+ public static final int STOPPING_STATUS = 2;
+ private int m_frameworkStatus = INITIAL_STATUS;
+
+ // Framework's active start level.
+ private int m_activeStartLevel =
+ FelixConstants.FRAMEWORK_INACTIVE_STARTLEVEL;
+
+ // Local file system cache.
+ private BundleCache m_cache = null;
+
+ // Next available bundle identifier.
+ private long m_nextId = 1L;
+
+ // List of event listeners.
+ private FelixDispatchQueue m_dispatchQueue = null;
+ // Re-usable event dispatchers.
+ private Dispatcher m_frameworkDispatcher = null;
+ private Dispatcher m_bundleDispatcher = null;
+ private Dispatcher m_serviceDispatcher = null;
+
+ // Service registry.
+ private ServiceRegistry m_registry = null;
+
+ // Reusable admin permission object for all instances
+ // of the BundleImpl.
+ private static AdminPermission m_adminPerm = new AdminPermission();
+
+ /**
+ * <p>
+ * This method starts the framework instance; instances of the framework
+ * are dormant until this method is called. The caller may also provide
+ * <tt>MutablePropertyResolver</tt> implementations that the instance will
+ * use to obtain configuration or framework properties. Configuration
+ * properties are used internally by the framework and its extensions to alter
+ * its default behavior. Framework properties are used by bundles
+ * and are accessible from <tt>BundleContext.getProperty()</tt>.
+ * </p>
+ * <p>
+ * Configuration properties are the sole means to configure the framework's
+ * default behavior; the framework does not refer to any system properties for
+ * configuration information. If a <tt>MutablePropertyResolver</tt> is
+ * supplied to this method for configuration properties, then the framework will
+ * consult the <tt>MutablePropertyResolver</tt> instance for any and all
+ * configuration properties. It is possible to specify a <tt>null</tt>
+ * configuration property resolver, in which case the framework will use its
+ * default behavior in all cases. However, if the
+ * <a href="cache/DefaultBundleCache.html"><tt>DefaulBundleCache</tt></a>
+ * is used, then at a minimum a profile name or profile directory must
+ * be specified.
+ * </p>
+ * <p>
+ * The following configuration properties can be specified:
+ * </p>
+ * <ul>
+ * <li><tt>felix.cache.class</tt> - The class name to be used when
+ * creating an instance for the bundle cache; this class must
+ * implement the <tt>BundleCache</tt> interface and have a default
+ * constructor. By default, the framework will create an instance of
+ * <tt>DefaultBundleCache</tt> for the bundle cache.
+ * </li>
+ * <li><tt>felix.auto.install.<n></tt> - Space-delimited list of
+ * bundles to automatically install into start level <tt>n</tt> when
+ * the framework is started. Append a specific start level to this
+ * property name to assign the bundles' start level
+ * (e.g., <tt>felix.auto.install.2</tt>).
+ * </li>
+ * <li><tt>felix.auto.start.<n></tt> - Space-delimited list of
+ * bundles to automatically install and start into start level
+ * <tt>n</tt> when the framework is started. Append a
+ * specific start level to this property name to assign the
+ * bundles' start level(e.g., <tt>felix.auto.start.2</tt>).
+ * </li>
+ * <li><tt>felix.startlevel.framework</tt> - The initial start level
+ * of the framework once it starts execution; the default
+ * value is 1.
+ * </li>
+ * <li><tt>felix.startlevel.bundle</tt> - The default start level for
+ * newly installed bundles; the default value is 1.
+ * </li>
+ * <li><tt>felix.embedded.execution</tt> - Flag to indicate whether
+ * the framework is embedded into a host application; the default value is
+ * "<tt>false</tt>". If this flag is "<tt>true</tt>" then the framework
+ * will not called <tt>System.exit()</tt> upon termination.
+ * </li>
+ * <li><tt>felix.strict.osgi</tt> - Flag to indicate whether the framework is
+ * running in strict OSGi mode; the default value is "<tt>true</tt>".
+ * If this flag is "<tt>false</tt>" it enables a non-OSGi-compliant
+ * feature by persisting <tt>BundleActivator</tt>s that implement
+ * <tt>Serializable</tt>. This feature is not recommended since
+ * it is non-compliant.
+ * </li>
+ * </ul>
+ * <p>
+ * Besides the above framework configuration properties, it is also
+ * possible to specify properties for the bundle cache. The available
+ * bundle cache properties depend on the cache implementation
+ * being used. For the properties of the default bundle cache, refer to the
+ * <a href="cache/DefaultBundleCache.html"><tt>DefaulBundleCache</tt></a>
+ * API documentation.
+ * </p>
+ * <p>
+ * Framework properties are somewhat misnamed, since they are not used by
+ * the framework, but by bundles via <tt>BundleContext.getProperty()</tt>.
+ * Please refer to bundle documentation of your specific bundle for any
+ * available properties.
+ * </p>
+ * <p>
+ * The <a href="Main.html"><tt>Main</tt></a> class implements some
+ * functionality for default property file handling, which makes it
+ * possible to specify configuration properties and framework properties
+ * in files that are automatically loaded when starting the framework. If you
+ * plan to create your own framework instance, you may be
+ * able to take advantage of the features it provides; refer to its
+ * class documentation for more information.
+ * </p>
+ *
+ * @param configProps An object for obtaining configuration properties,
+ * may be <tt>null</tt>.
+ * @param frameworkProps An object for obtaining framework properties,
+ * may be <tt>null</tt>.
+ * @param activatorList A list of System Bundle activators.
+ **/
+ public synchronized void start(
+ MutablePropertyResolver configProps,
+ List activatorList)
+ {
+ if (m_frameworkStatus != INITIAL_STATUS)
+ {
+ throw new IllegalStateException("Invalid framework status: " + m_frameworkStatus);
+ }
+
+ // The framework is now in its startup sequence.
+ m_frameworkStatus = STARTING_STATUS;
+
+ // Initialize member variables.
+ m_mgr = null;
+ m_configProps = (configProps == null)
+ ? new MutablePropertyResolverImpl(new CaseInsensitiveMap()) : configProps;
+ m_activeStartLevel = FelixConstants.FRAMEWORK_INACTIVE_STARTLEVEL;
+ m_installRequestMap = new HashMap();
+ m_installedBundleMap = new HashMap();
+ m_uninstalledBundles = null;
+ m_cache = null;
+ m_nextId = 1L;
+ m_dispatchQueue = null;
+ m_registry = new ServiceRegistry(m_logger);
+
+ // Add a listener to the service registry; this is
+ // used to distribute service registry events to
+ // service listeners.
+ m_registry.addServiceListener(new ServiceListener() {
+ public void serviceChanged(ServiceEvent event)
+ {
+ fireServiceEvent(event);
+ }
+ });
+
+ // Create default storage system from the specified cache class
+ // or use the default cache if no custom cache was specified.
+ String className = m_config.get(FelixConstants.CACHE_CLASS_PROP);
+ if (className == null)
+ {
+ className = DefaultBundleCache.class.getName();
+ }
+
+ try
+ {
+ Class clazz = Class.forName(className);
+ m_cache = (BundleCache) clazz.newInstance();
+ m_cache.initialize(m_config, m_logger);
+ }
+ catch (Exception ex)
+ {
+ System.err.println("Error creating bundle cache:");
+ ex.printStackTrace();
+
+ // Only shutdown the JVM if the framework is running stand-alone.
+ String embedded = m_config.get(
+ FelixConstants.EMBEDDED_EXECUTION_PROP);
+ boolean isEmbedded = (embedded == null)
+ ? false : embedded.equals("true");
+ if (!isEmbedded)
+ {
+ System.exit(-1);
+ }
+ else
+ {
+ throw new RuntimeException(ex.toString());
+ }
+ }
+
+ // Create search policy for module loader.
+ R4SearchPolicy searchPolicy = new R4SearchPolicy(m_logger);
+
+ // Add a resolver listener to the search policy
+ // so that we will be notified when modules are resolved
+ // in order to update the bundle state.
+ searchPolicy.addResolverListener(new ResolveListener() {
+ public void moduleResolved(ModuleEvent event)
+ {
+ BundleImpl bundle = null;
+ try
+ {
+ long id = BundleInfo.getBundleIdFromModuleId(
+ event.getModule().getId());
+ if (id >= 0)
+ {
+ // Update the bundle's state to resolved when the
+ // current module is resolved; just ignore resolve
+ // events for older revisions since this only occurs
+ // when an update is done on an unresolved bundle
+ // and there was no refresh performed.
+ bundle = (BundleImpl) getBundle(id);
+
+ // Lock the bundle first.
+ try
+ {
+ acquireBundleLock(bundle);
+ if (bundle.getInfo().getCurrentModule() == event.getModule())
+ {
+ bundle.getInfo().setState(Bundle.RESOLVED);
+ }
+ }
+ catch (BundleException ex)
+ {
+ // This should not happen, but if it does
+ // there isn't much we can do.
+ }
+ finally
+ {
+ releaseBundleLock(bundle);
+ }
+ }
+ }
+ catch (NumberFormatException ex)
+ {
+ // Ignore.
+ }
+ }
+
+ public void moduleUnresolved(ModuleEvent event)
+ {
+ // We can ignore this, because the only time it
+ // should happen is when a refresh occurs. The
+ // refresh operation resets the bundle's state
+ // by calling BundleInfo.reset(), thus it is not
+ // necessary for us to reset the bundle's state
+ // here.
+ }
+ });
+
+ m_mgr = new ModuleManager(searchPolicy, new OSGiURLPolicy(this));
+
+ // Initialize dispatch queue.
+ m_dispatchQueue = new FelixDispatchQueue(m_logger);
+
+ // Initialize framework properties.
+ initializeFrameworkProperties();
+
+ // Before we reload any cached bundles, let's create a system
+ // bundle that is responsible for providing specific container
+ // related services.
+ SystemBundle systembundle = null;
+ try
+ {
+ // Create a simple bundle info for the system bundle.
+ BundleInfo info = new BundleInfo(
+ m_logger, new SystemBundleArchive(), null);
+ systembundle = new SystemBundle(this, info, activatorList);
+ systembundle.getInfo().addModule(
+ m_mgr.addModule(
+ "0", systembundle.getAttributes(),
+ systembundle.getResourceSources(),
+ systembundle.getLibrarySources(),
+ true)); // HACK ALERT! This flag indicates that we will
+ // use the parent class loader as a resource source.
+ m_installedBundleMap.put(
+ systembundle.getInfo().getLocation(), systembundle);
+
+ // Manually resolve the System Bundle, which will cause its
+ // state to be set to RESOLVED.
+ try
+ {
+ searchPolicy.resolve(systembundle.getInfo().getCurrentModule());
+ }
+ catch (ResolveException ex)
+ {
+ // This should never happen.
+ throw new BundleException(
+ "Unresolved package in System Bundle:"
+ + ex.getPackage());
+ }
+
+ // Start the system bundle; this will set its state
+ // to STARTING, we must set its state to ACTIVE after
+ // all bundles are restarted below according to the spec.
+ systembundle.start();
+ }
+ catch (Exception ex)
+ {
+ m_mgr = null;
+ DispatchQueue.shutdown();
+ m_logger.log(LogWrapper.LOG_ERROR, "Unable to start system bundle.", ex);
+ throw new RuntimeException("Unable to start system bundle.");
+ }
+
+ // Reload and cached bundles.
+ BundleArchive[] archives = null;
+
+ // First get cached bundle identifiers.
+ try
+ {
+ archives = m_cache.getArchives();
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Unable to list saved bundles: " + ex, ex);
+ archives = null;
+ }
+
+ BundleImpl bundle = null;
+
+ // Now install all cached bundles.
+ for (int i = 0; (archives != null) && (i < archives.length); i++)
+ {
+ // Make sure our id generator is not going to overlap.
+ // TODO: This is not correct since it may lead to re-used
+ // ids, which is not okay according to OSGi.
+ m_nextId = Math.max(m_nextId, archives[i].getId() + 1);
+
+ try
+ {
+ // It is possible that a bundle in the cache was previously
+ // uninstalled, but not completely deleted (perhaps because
+ // of a crash or a locked file), so if we see an archive
+ // with an UNINSTALLED persistent state, then try to remove
+ // it now.
+ if (archives[i].getPersistentState() == Bundle.UNINSTALLED)
+ {
+ m_cache.remove(archives[i]);
+ }
+ // Otherwise re-install the cached bundle.
+ else
+ {
+ // Install the cached bundle.
+ bundle = (BundleImpl) installBundle(
+ archives[i].getId(), archives[i].getLocation(), null);
+ }
+ }
+ catch (Exception ex)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, bundle, ex);
+ try
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Unable to re-install " + archives[i].getLocation(),
+ ex);
+ }
+ catch (Exception ex2)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Unable to re-install bundle " + archives[i].getId(),
+ ex);
+ }
+ // TODO: Perhaps we should remove the cached bundle?
+ }
+ }
+
+ // Get the framework's default start level.
+ int startLevel = FelixConstants.FRAMEWORK_DEFAULT_STARTLEVEL;
+ String s = m_config.get(FelixConstants.FRAMEWORK_STARTLEVEL_PROP);
+ if (s != null)
+ {
+ try
+ {
+ startLevel = Integer.parseInt(s);
+ }
+ catch (NumberFormatException ex)
+ {
+ startLevel = FelixConstants.FRAMEWORK_DEFAULT_STARTLEVEL;
+ }
+ }
+
+ // Load bundles from auto-install and auto-start properties;
+ processAutoProperties();
+
+ // This will restart bundles if necessary.
+ setFrameworkStartLevel(startLevel);
+
+ // The framework is now running.
+ m_frameworkStatus = RUNNING_STATUS;
+
+ // Set the system bundle state to ACTIVE.
+ systembundle.getInfo().setState(Bundle.ACTIVE);
+
+ // Fire started event for system bundle.
+ fireBundleEvent(BundleEvent.STARTED, systembundle);
+
+ // Send a framework event to indicate the framework has started.
+ fireFrameworkEvent(FrameworkEvent.STARTED, getBundle(0), null);
+ }
+
+ /**
+ * This method cleanly shuts down the framework, it must be called at the
+ * end of a session in order to shutdown all active bundles.
+ **/
+ public synchronized void shutdown()
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+
+ // Change framework status from running to stopping.
+ // If framework is not running, then just return.
+ if (m_frameworkStatus != RUNNING_STATUS)
+ {
+ return;
+ }
+
+ // The framework is now in its shutdown sequence.
+ m_frameworkStatus = STOPPING_STATUS;
+
+ // Set the start level to zero in order to stop
+ // all bundles in the framework.
+ setFrameworkStartLevel(0);
+
+ // Just like initialize() called the system bundle's start()
+ // method, we must call its stop() method here so that it
+ // can perform any necessary clean up.
+ try
+ {
+ getBundle(0).stop();
+ }
+ catch (Exception ex)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, getBundle(0), ex);
+ m_logger.log(LogWrapper.LOG_ERROR, "Error stopping system bundle.", ex);
+ }
+
+ // Loop through all bundles and update any updated bundles.
+ Bundle[] bundles = getBundles();
+ for (int i = 0; i < bundles.length; i++)
+ {
+ BundleImpl bundle = (BundleImpl) bundles[i];
+ if (bundle.getInfo().isRemovalPending())
+ {
+ try
+ {
+ purgeBundle(bundle);
+ }
+ catch (Exception ex)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, bundle, ex);
+ m_logger.log(LogWrapper.LOG_ERROR, "Unable to purge bundle "
+ + bundle.getInfo().getLocation(), ex);
+ }
+ }
+ }
+
+ // Remove any uninstalled bundles.
+ for (int i = 0;
+ (m_uninstalledBundles != null) && (i < m_uninstalledBundles.length);
+ i++)
+ {
+ try
+ {
+ garbageCollectBundle(m_uninstalledBundles[i]);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Unable to remove "
+ + m_uninstalledBundles[i].getInfo().getLocation(), ex);
+ }
+ }
+
+ // Shutdown event dispatching queue.
+ DispatchQueue.shutdown();
+
+ // The framework is no longer in a usable state.
+ m_frameworkStatus = INITIAL_STATUS;
+ }
+
+ public int getStatus()
+ {
+ return m_frameworkStatus;
+ }
+
+ /**
+ * Returns the active start level of the framework; this method
+ * implements functionality for the Start Level service.
+ * @return The active start level of the framework.
+ **/
+ protected int getStartLevel()
+ {
+ return m_activeStartLevel;
+ }
+
+ /**
+ * Implements the functionality of the <tt>setStartLevel()</tt>
+ * method for the StartLevel service, but does not do the security or
+ * parameter check. The security and parameter check are done in the
+ * StartLevel service implementation because this method is called on
+ * a separate thread and the caller's thread would already be gone if
+ * we did the checks in this method.
+ * @param requestedLevel The new start level of the framework.
+ **/
+ protected synchronized void setFrameworkStartLevel(int requestedLevel)
+ {
+ // Determine if we are lowering or raising the
+ // active start level.
+ boolean lowering = (requestedLevel < m_activeStartLevel);
+
+ // Record new start level.
+ m_activeStartLevel = requestedLevel;
+
+ // Get array of all installed bundles.
+ Bundle[] bundles = getBundles();
+
+ // Sort bundle array by start level either ascending or
+ // descending depending on whether the start level is being
+ // lowered or raised.
+ Comparator comparator = null;
+ if (lowering)
+ {
+ // Sort descending to stop highest start level first.
+ comparator = new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ BundleImpl b1 = (BundleImpl) o1;
+ BundleImpl b2 = (BundleImpl) o2;
+ if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+ < b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+ {
+ return 1;
+ }
+ else if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+ > b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+ {
+ return -1;
+ }
+ return 0;
+ }
+ };
+ }
+ else
+ {
+ // Sort ascending to start lowest start level first.
+ comparator = new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ BundleImpl b1 = (BundleImpl) o1;
+ BundleImpl b2 = (BundleImpl) o2;
+ if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+ > b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+ {
+ return 1;
+ }
+ else if (b1.getInfo().getStartLevel(getInitialBundleStartLevel())
+ < b2.getInfo().getStartLevel(getInitialBundleStartLevel()))
+ {
+ return -1;
+ }
+ return 0;
+ }
+ };
+ }
+
+ Arrays.sort(bundles, comparator);
+
+ // Stop or start the bundles according to the start level.
+ for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+ {
+ BundleImpl impl = (BundleImpl) bundles[i];
+
+ // Ignore the system bundle, since its start() and
+ // stop() methods get called explicitly in initialize()
+ // and shutdown(), respectively.
+ if (impl.getInfo().getBundleId() == 0)
+ {
+ continue;
+ }
+
+ // Start the bundle if necessary.
+ if ((impl.getInfo().getPersistentState() == Bundle.ACTIVE) &&
+ (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+ <= m_activeStartLevel))
+ {
+ try
+ {
+ startBundle(impl, false);
+ }
+ catch (Throwable th)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, impl, th);
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Error starting " + impl.getInfo().getLocation(), th);
+ }
+ }
+ // Stop the bundle if necessary.
+ else if (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+ > m_activeStartLevel)
+ {
+ try
+ {
+ stopBundle(impl, false);
+ }
+ catch (Throwable th)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, impl, th);
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Error stopping " + impl.getInfo().getLocation(), th);
+ }
+ }
+ }
+
+ fireFrameworkEvent(FrameworkEvent.STARTLEVEL_CHANGED, getBundle(0), null);
+ }
+
+ /**
+ * Returns the start level into which newly installed bundles will
+ * be placed by default; this method implements functionality for
+ * the Start Level service.
+ * @return The default start level for newly installed bundles.
+ **/
+ protected int getInitialBundleStartLevel()
+ {
+ String s = m_config.get(FelixConstants.BUNDLE_STARTLEVEL_PROP);
+
+ if (s != null)
+ {
+ try
+ {
+ int i = Integer.parseInt(s);
+ return (i > 0) ? i : FelixConstants.BUNDLE_DEFAULT_STARTLEVEL;
+ }
+ catch (NumberFormatException ex)
+ {
+ // Ignore and return the default value.
+ }
+ }
+ return FelixConstants.BUNDLE_DEFAULT_STARTLEVEL;
+ }
+
+ /**
+ * Sets the default start level into which newly installed bundles
+ * will be placed; this method implements functionality for the Start
+ * Level service.
+ * @param startLevel The new default start level for newly installed
+ * bundles.
+ * @throws java.lang.IllegalArgumentException If the specified start
+ * level is not greater than zero.
+ * @throws java.security.SecurityException If the caller does not
+ * have <tt>AdminPermission</tt>.
+ **/
+ protected void setInitialBundleStartLevel(int startLevel)
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+
+ if (startLevel <= 0)
+ {
+ throw new IllegalArgumentException(
+ "Initial start level must be greater than zero.");
+ }
+
+ m_configProps.put(
+ FelixConstants.BUNDLE_STARTLEVEL_PROP, Integer.toString(startLevel));
+ }
+
+ /**
+ * Returns the start level for the specified bundle; this method
+ * implements functionality for the Start Level service.
+ * @param bundle The bundle to examine.
+ * @return The start level of the specified bundle.
+ * @throws java.lang.IllegalArgumentException If the specified
+ * bundle has been uninstalled.
+ **/
+ protected int getBundleStartLevel(Bundle bundle)
+ {
+ if (bundle.getState() == Bundle.UNINSTALLED)
+ {
+ throw new IllegalArgumentException("Bundle is uninstalled.");
+ }
+
+ return ((BundleImpl) bundle).getInfo().getStartLevel(getInitialBundleStartLevel());
+ }
+
+ /**
+ * Sets the start level of the specified bundle; this method
+ * implements functionality for the Start Level service.
+ * @param bundle The bundle whose start level is to be modified.
+ * @param startLevel The new start level of the specified bundle.
+ * @throws java.lang.IllegalArgumentException If the specified
+ * bundle is the system bundle or if the bundle has been
+ * uninstalled.
+ * @throws java.security.SecurityException If the caller does not
+ * have <tt>AdminPermission</tt>.
+ **/
+ protected void setBundleStartLevel(Bundle bundle, int startLevel)
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+
+ // Cannot change the system bundle.
+ if (bundle.getBundleId() == 0)
+ {
+ throw new IllegalArgumentException(
+ "Cannot change system bundle start level.");
+ }
+
+ // Acquire bundle lock.
+ try
+ {
+ acquireBundleLock((BundleImpl) bundle);
+ }
+ catch (BundleException ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "Unable to acquire lock to set start level.", ex);
+ return;
+ }
+
+ Throwable rethrow = null;
+
+ try
+ {
+ if (bundle.getState() == Bundle.UNINSTALLED)
+ {
+ throw new IllegalArgumentException("Bundle is uninstalled.");
+ }
+
+ if (startLevel >= 1)
+ {
+ BundleImpl impl = (BundleImpl) bundle;
+ impl.getInfo().setStartLevel(startLevel);
+
+ try
+ {
+ // Start the bundle if necessary.
+ if ((impl.getInfo().getPersistentState() == Bundle.ACTIVE) &&
+ (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+ <= m_activeStartLevel))
+ {
+ startBundle(impl, false);
+ }
+ // Stop the bundle if necessary.
+ else if (impl.getInfo().getStartLevel(getInitialBundleStartLevel())
+ > m_activeStartLevel)
+ {
+ stopBundle(impl, false);
+ }
+ }
+ catch (Throwable th)
+ {
+ rethrow = th;
+ m_logger.log(LogWrapper.LOG_ERROR, "Error starting/stopping bundle.", th);
+ }
+ }
+ else
+ {
+ m_logger.log(LogWrapper.LOG_WARNING, "Bundle start level must be greater than zero.");
+ }
+ }
+ finally
+ {
+ // Always release bundle lock.
+ releaseBundleLock((BundleImpl) bundle);
+ }
+
+ if (rethrow != null)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, bundle, rethrow);
+ }
+ }
+
+ /**
+ * Returns whether a bundle is persistently started; this is an
+ * method implementation for the Start Level service.
+ * @param bundle The bundle to examine.
+ * @return <tt>true</tt> if the bundle is marked as persistently
+ * started, <tt>false</tt> otherwise.
+ * @throws java.lang.IllegalArgumentException If the specified
+ * bundle has been uninstalled.
+ **/
+ protected boolean isBundlePersistentlyStarted(Bundle bundle)
+ {
+ if (bundle.getState() == Bundle.UNINSTALLED)
+ {
+ throw new IllegalArgumentException("Bundle is uninstalled.");
+ }
+
+ return (((BundleImpl) bundle).getInfo().getPersistentState() == Bundle.ACTIVE);
+ }
+
+ //
+ // Implementation of Bundle interface methods.
+ //
+
+ /**
+ * Implementation for Bundle.getHeaders().
+ **/
+ protected Dictionary getBundleHeaders(BundleImpl bundle)
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+ return new MapToDictionary(bundle.getInfo().getCurrentHeader());
+ }
+
+ /**
+ * Implementation for Bundle.getLocation().
+ **/
+ protected String getBundleLocation(BundleImpl bundle)
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+ return bundle.getInfo().getLocation();
+ }
+
+ /**
+ * Implementation for Bundle.getResource().
+ **/
+ protected URL getBundleResource(BundleImpl bundle, String name)
+ {
+ if (bundle.getInfo().getState() == Bundle.UNINSTALLED)
+ {
+ throw new IllegalStateException("The bundle is uninstalled.");
+ }
+ else if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+ return bundle.getInfo().getCurrentModule().getClassLoader().getResource(name);
+ }
+
+ protected ServiceReference[] getBundleRegisteredServices(BundleImpl bundle)
+ {
+ if (bundle.getInfo().getState() == Bundle.UNINSTALLED)
+ {
+ throw new IllegalStateException("The bundle is uninstalled.");
+ }
+
+ // Filter list of registered service references.
+ ServiceReference[] refs = m_registry.getRegisteredServices(bundle);
+ List list = new ArrayList();
+ for (int refIdx = 0; (refs != null) && (refIdx < refs.length); refIdx++)
+ {
+ // Check that the current security context has permission
+ // to get at least one of the service interfaces; the
+ // objectClass property of the service stores its service
+ // interfaces.
+ boolean hasPermission = false;
+ if (System.getSecurityManager() != null)
+ {
+ String[] objectClass = (String[])
+ refs[refIdx].getProperty(Constants.OBJECTCLASS);
+ if (objectClass == null)
+ {
+ return null;
+ }
+ for (int ifcIdx = 0;
+ !hasPermission && (ifcIdx < objectClass.length);
+ ifcIdx++)
+ {
+ try
+ {
+ ServicePermission perm =
+ new ServicePermission(
+ objectClass[ifcIdx], ServicePermission.GET);
+ AccessController.checkPermission(perm);
+ hasPermission = true;
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+ }
+ else
+ {
+ hasPermission = true;
+ }
+
+ if (hasPermission)
+ {
+ list.add(refs[refIdx]);
+ }
+ }
+
+ if (list.size() > 0)
+ {
+ return (ServiceReference[])
+ list.toArray(new ServiceReference[list.size()]);
+ }
+
+ return null;
+ }
+
+ protected ServiceReference[] getBundleServicesInUse(Bundle bundle)
+ {
+ // Filter list of "in use" service references.
+ ServiceReference[] refs = m_registry.getServicesInUse(bundle);
+ List list = new ArrayList();
+ for (int refIdx = 0; (refs != null) && (refIdx < refs.length); refIdx++)
+ {
+ // Check that the current security context has permission
+ // to get at least one of the service interfaces; the
+ // objectClass property of the service stores its service
+ // interfaces.
+ boolean hasPermission = false;
+ if (System.getSecurityManager() != null)
+ {
+ String[] objectClass = (String[])
+ refs[refIdx].getProperty(Constants.OBJECTCLASS);
+ if (objectClass == null)
+ {
+ return null;
+ }
+ for (int ifcIdx = 0;
+ !hasPermission && (ifcIdx < objectClass.length);
+ ifcIdx++)
+ {
+ try
+ {
+ ServicePermission perm =
+ new ServicePermission(
+ objectClass[ifcIdx], ServicePermission.GET);
+ AccessController.checkPermission(perm);
+ hasPermission = true;
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+ }
+ else
+ {
+ hasPermission = true;
+ }
+
+ if (hasPermission)
+ {
+ list.add(refs[refIdx]);
+ }
+ }
+
+ if (list.size() > 0)
+ {
+ return (ServiceReference[])
+ list.toArray(new ServiceReference[list.size()]);
+ }
+
+ return null;
+ }
+
+ protected boolean bundleHasPermission(BundleImpl bundle, Object obj)
+ {
+ if (bundle.getInfo().getState() == Bundle.UNINSTALLED)
+ {
+ throw new IllegalStateException("The bundle is uninstalled.");
+ }
+
+// TODO: IMPLEMENT THIS CORRECTLY.
+ return true;
+ }
+
+ /**
+ * Implementation for Bundle.start().
+ **/
+ protected void startBundle(BundleImpl bundle, boolean record)
+ throws BundleException
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+
+ // CONCURRENCY NOTE:
+ // Starting a bundle may actually impact many bundles, since
+ // the bundle being started my need to be resolved, which in
+ // turn may need to resolve other bundles. Despite this fact,
+ // we only acquire the lock for the bundle being started, because
+ // when resolve is called on this bundle, it will eventually
+ // call resolve on the module loader search policy, which does
+ // its own locking on the module manager instance. Since the
+ // resolve algorithm is locking the module manager instance, it
+ // is not possible for other bundles to be installed or removed,
+ // so we don't have to worry about these possibilities.
+ //
+ // Further, if other bundles are started during this operation,
+ // then either they will resolve first because they got the lock
+ // on the module manager or we will resolve first since we got
+ // the lock on the module manager, so there should be no interference.
+ // If other bundles are stopped or uninstalled, this should pose
+ // no problems, since this does not impact their resolved state.
+ // If a refresh occurs, then the refresh algorithm ulimately has
+ // to acquire the module manager instance lock too before it can
+ // completely purge old modules, so it should also complete either
+ // before or after this bundle is started. At least that's the
+ // theory.
+
+ // Acquire bundle lock.
+ acquireBundleLock(bundle);
+
+ try
+ {
+ _startBundle(bundle, record);
+ }
+ finally
+ {
+ // Release bundle lock.
+ releaseBundleLock(bundle);
+ }
+ }
+
+ private void _startBundle(BundleImpl bundle, boolean record)
+ throws BundleException
+ {
+ // Set and save the bundle's persistent state to active
+ // if we are supposed to record state change.
+ if (record)
+ {
+ bundle.getInfo().setPersistentStateActive();
+ }
+
+ // Try to start the bundle.
+ BundleInfo info = bundle.getInfo();
+
+ // Ignore bundles whose persistent state is not active
+ // or whose start level is greater than the framework's.
+ if ((info.getPersistentState() != Bundle.ACTIVE)
+ || (info.getStartLevel(getInitialBundleStartLevel()) > getStartLevel()))
+ {
+ return;
+ }
+
+ switch (info.getState())
+ {
+ case Bundle.UNINSTALLED:
+ throw new IllegalStateException("Cannot start an uninstalled bundle.");
+ case Bundle.STARTING:
+ case Bundle.STOPPING:
+ throw new BundleException("Starting a bundle that is starting or stopping is currently not supported.");
+ case Bundle.ACTIVE:
+ return;
+ case Bundle.INSTALLED:
+ _resolveBundle(bundle);
+ case Bundle.RESOLVED:
+ info.setState(Bundle.STARTING);
+ }
+
+ try
+ {
+ // Set the bundle's activator.
+ bundle.getInfo().setActivator(createBundleActivator(bundle.getInfo()));
+
+ // Activate the bundle if it has an activator.
+ if (bundle.getInfo().getActivator() != null)
+ {
+ if (info.getContext() == null)
+ {
+ info.setContext(new BundleContextImpl(this, bundle));
+ }
+
+ if (System.getSecurityManager() != null)
+ {
+// m_startStopPrivileged.setAction(StartStopPrivileged.START_ACTION);
+// m_startStopPrivileged.setBundle(bundle);
+// AccessController.doPrivileged(m_startStopPrivileged);
+ }
+ else
+ {
+ info.getActivator().start(info.getContext());
+ }
+ }
+
+ info.setState(Bundle.ACTIVE);
+
+ fireBundleEvent(BundleEvent.STARTED, bundle);
+ }
+ catch (Throwable th)
+ {
+ // If there was an error starting the bundle,
+ // then reset its state to RESOLVED.
+ info.setState(Bundle.RESOLVED);
+
+ // Unregister any services offered by this bundle.
+ m_registry.unregisterServices(bundle);
+
+ // Release any services being used by this bundle.
+ m_registry.ungetServices(bundle);
+
+ // Remove any listeners registered by this bundle.
+ removeListeners(bundle);
+
+ // The spec says to expect BundleException or
+ // SecurityException, so rethrow these exceptions.
+ if (th instanceof BundleException)
+ {
+ throw (BundleException) th;
+ }
+ else if (th instanceof SecurityException)
+ {
+ throw (SecurityException) th;
+ }
+ // Convert a privileged action exception to the
+ // nested exception.
+ else if (th instanceof PrivilegedActionException)
+ {
+ th = ((PrivilegedActionException) th).getException();
+ }
+
+ // Rethrow all other exceptions as a BundleException.
+ throw new BundleException("Activator start error.", th);
+ }
+ }
+
+ protected void _resolveBundle(BundleImpl bundle)
+ throws BundleException
+ {
+ // If a security manager is installed, then check for permission
+ // to import the necessary packages.
+ if (System.getSecurityManager() != null)
+ {
+ URL url = null;
+ try
+ {
+ url = new URL(bundle.getInfo().getLocation());
+ }
+ catch (MalformedURLException ex)
+ {
+ throw new BundleException("Cannot resolve, bad URL "
+ + bundle.getInfo().getLocation());
+ }
+
+// try
+// {
+// AccessController.doPrivileged(new CheckImportsPrivileged(url, bundle));
+// }
+// catch (PrivilegedActionException ex)
+// {
+// Exception thrown = ((PrivilegedActionException) ex).getException();
+// if (thrown instanceof AccessControlException)
+// {
+// throw (AccessControlException) thrown;
+// }
+// else
+// {
+// throw new BundleException("Problem resolving: " + ex);
+// }
+// }
+ }
+
+ // Get the import search policy.
+ R4SearchPolicy search = (R4SearchPolicy) m_mgr.getSearchPolicy();
+
+ Module module = bundle.getInfo().getCurrentModule();
+ try
+ {
+ search.resolve(module);
+ }
+ catch (ResolveException ex)
+ {
+ if (ex.getModule() != null)
+ {
+ throw new BundleException(
+ "Unresolved package in bundle "
+ + BundleInfo.getBundleIdFromModuleId(ex.getModule().getId())
+ + ": " + ex.getPackage());
+ }
+ else
+ {
+ throw new BundleException(ex.getMessage());
+ }
+ }
+
+ bundle.getInfo().setState(Bundle.RESOLVED);
+ }
+
+ protected void updateBundle(BundleImpl bundle, InputStream is)
+ throws BundleException
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+
+ // Acquire bundle lock.
+ acquireBundleLock(bundle);
+
+ try
+ {
+ _updateBundle(bundle, is);
+ }
+ finally
+ {
+ // Release bundle lock.
+ releaseBundleLock(bundle);
+ }
+ }
+
+ protected void _updateBundle(BundleImpl bundle, InputStream is)
+ throws BundleException
+ {
+ // We guarantee to close the input stream, so put it in a
+ // finally clause.
+
+ try
+ {
+ // Variable to indicate whether bundle is active or not.
+ Exception rethrow = null;
+
+ // Cannot update an uninstalled bundle.
+ BundleInfo info = bundle.getInfo();
+ if (info.getState() == Bundle.UNINSTALLED)
+ {
+ throw new IllegalStateException("The bundle is uninstalled.");
+ }
+
+ // First get the update-URL from our header.
+ String updateLocation = (String)
+ info.getCurrentHeader().get(Constants.BUNDLE_UPDATELOCATION);
+
+ // If no update location specified, use original location.
+ if (updateLocation == null)
+ {
+ updateLocation = info.getLocation();
+ }
+
+ // Stop the bundle, but do not change the persistent state.
+ stopBundle(bundle, false);
+
+ try
+ {
+ // Get the URL input stream if necessary.
+ if (is == null)
+ {
+ // Do it the manual way to have a chance to
+ // set request properties such as proxy auth.
+ URL url = new URL(updateLocation);
+ URLConnection conn = url.openConnection();
+
+ // Support for http proxy authentication.
+ String auth = System.getProperty("http.proxyAuth");
+ if ((auth != null) && (auth.length() > 0))
+ {
+ if ("http".equals(url.getProtocol()) ||
+ "https".equals(url.getProtocol()))
+ {
+ String base64 = Util.base64Encode(auth);
+ conn.setRequestProperty(
+ "Proxy-Authorization", "Basic " + base64);
+ }
+ }
+ is = conn.getInputStream();
+ }
+
+ // Get the bundle's archive.
+ BundleArchive archive = m_cache.getArchive(info.getBundleId());
+ // Update the bundle; this operation will increase
+ // the revision count for the bundle.
+ m_cache.update(archive, is);
+ // Create a module for the new revision; the revision is
+ // base zero, so subtract one from the revision count to
+ // get the revision of the new update.
+ Module module = createModule(
+ info.getBundleId(),
+ archive.getRevisionCount() - 1,
+ info.getCurrentHeader());
+ // Add module to bundle info.
+ info.addModule(module);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "Unable to update the bundle.", ex);
+ rethrow = ex;
+ }
+
+ info.setState(Bundle.INSTALLED);
+
+ // Mark as needing a refresh.
+ info.setRemovalPending();
+
+ // Fire updated event if successful.
+ if (rethrow == null)
+ {
+ fireBundleEvent(BundleEvent.UPDATED, bundle);
+ }
+
+ // Restart bundle, but do not change the persistent state.
+ // This will not start the bundle if it was not previously
+ // active.
+ startBundle(bundle, false);
+
+ // If update failed, rethrow exception.
+ if (rethrow != null)
+ {
+ throw new BundleException("Update failed.", rethrow);
+ }
+ }
+ finally
+ {
+ try
+ {
+ if (is != null) is.close();
+ }
+ catch (IOException ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "Unable to close input stream.", ex);
+ }
+ }
+ }
+
+ protected void stopBundle(BundleImpl bundle, boolean record)
+ throws BundleException
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+
+ // Acquire bundle lock.
+ acquireBundleLock(bundle);
+
+ try
+ {
+ _stopBundle(bundle, record);
+ }
+ finally
+ {
+ // Always release bundle lock.
+ releaseBundleLock(bundle);
+ }
+ }
+
+ private void _stopBundle(BundleImpl bundle, boolean record)
+ throws BundleException
+ {
+ Throwable rethrow = null;
+
+ // Set the bundle's persistent state to inactive if necessary.
+ if (record)
+ {
+ bundle.getInfo().setPersistentStateInactive();
+ }
+
+ BundleInfo info = bundle.getInfo();
+
+ switch (info.getState())
+ {
+ case Bundle.UNINSTALLED:
+ throw new IllegalStateException("Cannot stop an uninstalled bundle.");
+ case Bundle.STARTING:
+ case Bundle.STOPPING:
+ throw new BundleException("Stopping a bundle that is starting or stopping is currently not supported.");
+ case Bundle.INSTALLED:
+ case Bundle.RESOLVED:
+ return;
+ case Bundle.ACTIVE:
+ // Set bundle state..
+ info.setState(Bundle.STOPPING);
+ }
+
+ try
+ {
+ if (bundle.getInfo().getActivator() != null)
+ {
+ if (System.getSecurityManager() != null)
+ {
+// m_startStopPrivileged.setAction(StartStopPrivileged.STOP_ACTION);
+// m_startStopPrivileged.setBundle(bundle);
+// AccessController.doPrivileged(m_startStopPrivileged);
+ }
+ else
+ {
+ info.getActivator().stop(info.getContext());
+ }
+ }
+
+ // Try to save the activator in the cache.
+ // NOTE: This is non-standard OSGi behavior and only
+ // occurs if strictness is disabled.
+ String strict = m_config.get(FelixConstants.STRICT_OSGI_PROP);
+ boolean isStrict = (strict == null) ? true : strict.equals("true");
+ if (!isStrict)
+ {
+ try
+ {
+ m_cache.getArchive(info.getBundleId())
+ .setActivator(info.getActivator());
+ }
+ catch (Exception ex)
+ {
+ // Problem saving activator, so ignore it.
+ // TODO: Perhaps we should handle this some other way?
+ }
+ }
+ }
+ catch (Throwable th)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "Error stopping bundle.", th);
+ rethrow = th;
+ }
+
+ // Unregister any services offered by this bundle.
+ m_registry.unregisterServices(bundle);
+
+ // Release any services being used by this bundle.
+ m_registry.ungetServices(bundle);
+
+ // The spec says that we must remove all event
+ // listeners for a bundle when it is stopped.
+ removeListeners(bundle);
+
+ info.setState(Bundle.RESOLVED);
+ fireBundleEvent(BundleEvent.STOPPED, bundle);
+
+ // Throw activator error if there was one.
+ if (rethrow != null)
+ {
+ // The spec says to expect BundleException or
+ // SecurityException, so rethrow these exceptions.
+ if (rethrow instanceof BundleException)
+ {
+ throw (BundleException) rethrow;
+ }
+ else if (rethrow instanceof SecurityException)
+ {
+ throw (SecurityException) rethrow;
+ }
+ else if (rethrow instanceof PrivilegedActionException)
+ {
+ rethrow = ((PrivilegedActionException) rethrow).getException();
+ }
+
+ // Rethrow all other exceptions as a BundleException.
+ throw new BundleException("Activator stop error.", rethrow);
+ }
+ }
+
+ protected void uninstallBundle(BundleImpl bundle) throws BundleException
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+
+ // Acquire bundle lock.
+ acquireBundleLock(bundle);
+
+ try
+ {
+ _uninstallBundle(bundle);
+ }
+ finally
+ {
+ // Always release bundle lock.
+ releaseBundleLock(bundle);
+ }
+ }
+
+ private void _uninstallBundle(BundleImpl bundle) throws BundleException
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+
+ BundleException rethrow = null;
+
+ BundleInfo info = bundle.getInfo();
+ if (info.getState() == Bundle.UNINSTALLED)
+ {
+ throw new IllegalStateException("The bundle is uninstalled.");
+ }
+
+ // The spec says that uninstall should always succeed, so
+ // catch an exception here if stop() doesn't succeed and
+ // rethrow it at the end.
+ try
+ {
+ stopBundle(bundle, true);
+ }
+ catch (BundleException ex)
+ {
+ rethrow = ex;
+ }
+
+ // Remove the bundle from the installed map.
+ BundleImpl target = null;
+ synchronized (m_installedBundleLock_Priority2)
+ {
+ target = (BundleImpl) m_installedBundleMap.remove(info.getLocation());
+ }
+
+ // Finally, put the uninstalled bundle into the
+ // uninstalled list for subsequent refreshing.
+ if (target != null)
+ {
+ // Set the bundle's persistent state to uninstalled.
+ target.getInfo().setPersistentStateUninstalled();
+
+ // Mark bundle for removal.
+ target.getInfo().setRemovalPending();
+
+ // Put bundle in uninstalled bundle array.
+ rememberUninstalledBundle(bundle);
+ }
+ else
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR, "Unable to remove bundle from installed map!");
+ }
+
+ // Set state to uninstalled.
+ info.setState(Bundle.UNINSTALLED);
+
+ // Fire bundle event.
+ fireBundleEvent(BundleEvent.UNINSTALLED, bundle);
+
+ if (rethrow != null)
+ {
+ throw rethrow;
+ }
+ }
+
+ //
+ // Implementation of BundleContext interface methods.
+ //
+
+ /**
+ * Implementation for BundleContext.getProperty(). Returns
+ * environment property associated with the framework.
+ *
+ * @param key The name of the property to retrieve.
+ * @return The value of the specified property or null.
+ **/
+ protected String getProperty(String key)
+ {
+ // First, check the config properties.
+ String val = (String) m_configProps.get(key);
+ // If not found, then try the system properties.
+ return (val == null) ? System.getProperty(key) : val;
+ }
+
+ protected Bundle installBundle(String location, InputStream is)
+ throws BundleException
+ {
+ return installBundle(-1, location, is);
+ }
+
+ private Bundle installBundle(long id, String location, InputStream is)
+ throws BundleException
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+
+ BundleImpl bundle = null;
+
+ // Acquire an install lock.
+ acquireInstallLock(location);
+
+ try
+ {
+ // Check to see if the framework is still running;
+ if ((getStatus() == Felix.STOPPING_STATUS) ||
+ (getStatus() == Felix.INITIAL_STATUS))
+ {
+ throw new BundleException("The framework has been shutdown.");
+ }
+
+ // If bundle location is already installed, then
+ // return it as required by the OSGi specification.
+ bundle = (BundleImpl) getBundle(location);
+ if (bundle != null)
+ {
+ return bundle;
+ }
+
+ // Determine if this is a new or existing bundle.
+ boolean isNew = (id < 0);
+
+ // If the bundle is new we must cache its JAR file.
+ if (isNew)
+ {
+ // First generate an identifier for it.
+ id = getNextId();
+
+ try
+ {
+ // Get the URL input stream if necessary.
+ if (is == null)
+ {
+ // Do it the manual way to have a chance to
+ // set request properties such as proxy auth.
+ URL url = new URL(location);
+ URLConnection conn = url.openConnection();
+
+ // Support for http proxy authentication.
+ String auth = System.getProperty("http.proxyAuth");
+ if ((auth != null) && (auth.length() > 0))
+ {
+ if ("http".equals(url.getProtocol()) ||
+ "https".equals(url.getProtocol()))
+ {
+ String base64 = Util.base64Encode(auth);
+ conn.setRequestProperty(
+ "Proxy-Authorization", "Basic " + base64);
+ }
+ }
+ is = conn.getInputStream();
+ }
+ // Add the bundle to the cache.
+ m_cache.create(id, location, is);
+ }
+ catch (Exception ex)
+ {
+ throw new BundleException(
+ "Unable to cache bundle: " + location, ex);
+ }
+ finally
+ {
+ try
+ {
+ if (is != null) is.close();
+ }
+ catch (IOException ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Unable to close input stream.", ex);
+ }
+ }
+ }
+ else
+ {
+ // If the bundle we are installing is not new,
+ // then try to purge old revisions before installing
+ // it; this is done just in case a "refresh"
+ // didn't occur last session...this would only be
+ // due to an error or system crash.
+ try
+ {
+ if (m_cache.getArchive(id).getRevisionCount() > 1)
+ {
+ m_cache.purge(m_cache.getArchive(id));
+ }
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Could not purge bundle.", ex);
+ }
+ }
+
+ try
+ {
+ BundleArchive archive = m_cache.getArchive(id);
+ bundle = new BundleImpl(this, createBundleInfo(archive));
+ }
+ catch (Exception ex)
+ {
+ // If the bundle is new, then remove it from the cache.
+ // TODO: Perhaps it should be removed if it is not new too.
+ if (isNew)
+ {
+ try
+ {
+ m_cache.remove(m_cache.getArchive(id));
+ }
+ catch (Exception ex1)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Could not remove from cache.", ex1);
+ }
+ }
+ throw new BundleException("Could not create bundle object.", ex);
+ }
+
+ // If the bundle is new, then set its start level; existing
+ // bundles already have their start level set.
+ if (isNew)
+ {
+ // This will persistently set the bundle's start level.
+ bundle.getInfo().setStartLevel(getInitialBundleStartLevel());
+ }
+
+ synchronized (m_installedBundleLock_Priority2)
+ {
+ m_installedBundleMap.put(location, bundle);
+ }
+ }
+ finally
+ {
+ // Always release install lock.
+ releaseInstallLock(location);
+
+ // Always try to close the input stream.
+ try
+ {
+ if (is != null) is.close();
+ }
+ catch (IOException ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Unable to close input stream.", ex);
+ // Not much else we can do.
+ }
+ }
+
+ // Fire bundle event.
+ fireBundleEvent(BundleEvent.INSTALLED, bundle);
+
+ // Return new bundle.
+ return bundle;
+ }
+
+ /**
+ * Retrieves a bundle from its location.
+ *
+ * @param location The location of the bundle to retrieve.
+ * @return The bundle associated with the location or null if there
+ * is no bundle associated with the location.
+ **/
+ protected Bundle getBundle(String location)
+ {
+ synchronized (m_installedBundleLock_Priority2)
+ {
+ return (Bundle) m_installedBundleMap.get(location);
+ }
+ }
+
+ /**
+ * Implementation for BundleContext.getBundle(). Retrieves a
+ * bundle from its identifier.
+ *
+ * @param id The identifier of the bundle to retrieve.
+ * @return The bundle associated with the identifier or null if there
+ * is no bundle associated with the identifier.
+ **/
+ protected Bundle getBundle(long id)
+ {
+ synchronized (m_installedBundleLock_Priority2)
+ {
+ BundleImpl bundle = null;
+
+ for (Iterator i = m_installedBundleMap.values().iterator(); i.hasNext(); )
+ {
+ bundle = (BundleImpl) i.next();
+ if (bundle.getInfo().getBundleId() == id)
+ {
+ return bundle;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ // Private member for method below.
+ private Comparator m_comparator = null;
+
+ /**
+ * Implementation for BundleContext.getBundles(). Retrieves
+ * all installed bundles.
+ *
+ * @return An array containing all installed bundles or null if
+ * there are no installed bundles.
+ **/
+ protected Bundle[] getBundles()
+ {
+ if (m_comparator == null)
+ {
+ m_comparator = new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ Bundle b1 = (Bundle) o1;
+ Bundle b2 = (Bundle) o2;
+ if (b1.getBundleId() > b2.getBundleId())
+ return 1;
+ else if (b1.getBundleId() < b2.getBundleId())
+ return -1;
+ return 0;
+ }
+ };
+ }
+
+ Bundle[] bundles = null;
+
+ synchronized (m_installedBundleLock_Priority2)
+ {
+ if (m_installedBundleMap.size() == 0)
+ {
+ return null;
+ }
+
+ bundles = new Bundle[m_installedBundleMap.size()];
+ int counter = 0;
+ for (Iterator i = m_installedBundleMap.values().iterator(); i.hasNext(); )
+ {
+ bundles[counter++] = (Bundle) i.next();
+ }
+ }
+
+ Arrays.sort(bundles, m_comparator);
+
+ return bundles;
+ }
+
+ protected void addBundleListener(Bundle bundle, BundleListener l)
+ {
+ // The spec says do nothing if the listener is
+ // already registered.
+ BundleListenerWrapper old = (BundleListenerWrapper)
+ m_dispatchQueue.getListener(BundleListener.class, l);
+ if (old == null)
+ {
+ l = new BundleListenerWrapper(bundle, l);
+ m_dispatchQueue.addListener(BundleListener.class, l);
+ }
+ }
+
+ protected void removeBundleListener(BundleListener l)
+ {
+ m_dispatchQueue.removeListener(BundleListener.class, l);
+ }
+
+ /**
+ * Implementation for BundleContext.addServiceListener().
+ * Adds service listener to the listener list so that is
+ * can listen for <code>ServiceEvent</code>s.
+ *
+ * @param bundle The bundle that registered the listener.
+ * @param l The service listener to add to the listener list.
+ * @param f The filter for the listener; may be null.
+ **/
+ protected void addServiceListener(Bundle bundle, ServiceListener l, String f)
+ throws InvalidSyntaxException
+ {
+ // The spec says if the listener is already registered,
+ // then replace filter.
+ ServiceListenerWrapper old = (ServiceListenerWrapper)
+ m_dispatchQueue.getListener(ServiceListener.class, l);
+ if (old != null)
+ {
+ old.setFilter((f == null) ? null : new FilterImpl(m_logger, f));
+ }
+ else
+ {
+ l = new ServiceListenerWrapper(
+ bundle, l, (f == null) ? null : new FilterImpl(m_logger, f));
+ m_dispatchQueue.addListener(ServiceListener.class, l);
+ }
+ }
+
+ /**
+ * Implementation for BundleContext.removeServiceListener().
+ * Removes service listeners from the listener list.
+ *
+ * @param l The service listener to remove from the listener list.
+ **/
+ protected void removeServiceListener(ServiceListener l)
+ {
+ m_dispatchQueue.removeListener(ServiceListener.class, l);
+ }
+
+ protected void addFrameworkListener(Bundle bundle, FrameworkListener l)
+ {
+ // The spec says do nothing if the listener is
+ // already registered.
+ FrameworkListenerWrapper old = (FrameworkListenerWrapper)
+ m_dispatchQueue.getListener(FrameworkListener.class, l);
+ if (old == null)
+ {
+ l = new FrameworkListenerWrapper(bundle, l);
+ m_dispatchQueue.addListener(FrameworkListener.class, l);
+ }
+ }
+
+ protected void removeFrameworkListener(FrameworkListener l)
+ {
+ m_dispatchQueue.removeListener(FrameworkListener.class, l);
+ }
+
+ /**
+ * Remove all of the specified bundle's event listeners from
+ * the framework.
+ * @param bundle The bundle whose listeners are to be removed.
+ **/
+ private void removeListeners(Bundle bundle)
+ {
+ if (bundle == null)
+ {
+ return;
+ }
+
+ // Remove all listeners associated with the supplied bundle;
+ // it is only possible to know the bundle associated with a
+ // listener if the listener was wrapper by a ListenerWrapper,
+ // so look for those.
+ Object[] listeners = m_dispatchQueue.getListeners();
+ for (int i = listeners.length - 2; i >= 0; i -= 2)
+ {
+ // Check for listener wrappers and then compare the bundle.
+ if (listeners[i + 1] instanceof ListenerWrapper)
+ {
+ ListenerWrapper lw = (ListenerWrapper) listeners[i + 1];
+ if ((lw.getBundle() != null) && (lw.getBundle().equals(bundle)))
+ {
+ m_dispatchQueue.removeListener(
+ (Class) listeners[i], (EventListener) listeners[i+1]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Implementation for BundleContext.registerService(). Registers
+ * a service for the specified bundle bundle.
+ *
+ * @param classNames A string array containing the names of the classes
+ * under which the new service is available.
+ * @param svcObj The service object or <code>ServiceFactory</code>.
+ * @param dict A dictionary of properties that further describe the
+ * service or null.
+ * @return A <code>ServiceRegistration</code> object or null.
+ **/
+ protected ServiceRegistration registerService(
+ BundleImpl bundle, String[] classNames, Object svcObj, Dictionary dict)
+ {
+ if (classNames == null)
+ {
+ throw new NullPointerException("Service class names cannot be null.");
+ }
+ else if (svcObj == null)
+ {
+ throw new IllegalArgumentException("Service object cannot be null.");
+ }
+
+ // Check for permission to register all passed in interface names.
+ if (System.getSecurityManager() != null)
+ {
+ for (int i = 0; i < classNames.length; i++)
+ {
+ ServicePermission perm = new ServicePermission(
+ classNames[i], ServicePermission.REGISTER);
+ AccessController.checkPermission(perm);
+ }
+ }
+
+ // Acquire bundle lock.
+ try
+ {
+ acquireBundleLock(bundle);
+ }
+ catch (BundleException ex)
+ {
+ // This would probably only happen when the bundle is uninstalled.
+ throw new IllegalStateException(
+ "Can only register services while bundle is active or activating.");
+ }
+
+ ServiceRegistration reg = null;
+
+ try
+ {
+ BundleInfo info = bundle.getInfo();
+
+ // Can only register services if starting or active.
+ if ((info.getState() & (Bundle.STARTING | Bundle.ACTIVE)) == 0)
+ {
+ throw new IllegalStateException(
+ "Can only register services while bundle is active or activating.");
+ }
+
+ // Check to make sure that the service object is
+ // an instance of all service classes; ignore if
+ // service object is a service factory.
+ if (!(svcObj instanceof ServiceFactory))
+ {
+ for (int i = 0; i < classNames.length; i++)
+ {
+ Class clazz = loadClassUsingClass(svcObj.getClass(), classNames[i]);
+ if (clazz == null)
+ {
+ throw new IllegalArgumentException(
+ "Cannot cast service: " + classNames[i]);
+ }
+ else if (!clazz.isAssignableFrom(svcObj.getClass()))
+ {
+ throw new IllegalArgumentException(
+ "Service object is not an instance of \""
+ + classNames[i] + "\".");
+ }
+ }
+ }
+
+ reg = m_registry.registerService(bundle, classNames, svcObj, dict);
+ }
+ finally
+ {
+ // Always release bundle lock.
+ releaseBundleLock(bundle);
+ }
+
+ // NOTE: The service registered event is fired from the service
+ // registry to the framework, where it is then redistributed to
+ // interested service event listeners.
+
+ return reg;
+ }
+
+ /**
+ * <p>
+ * This is a simple utility class that attempts to load the named
+ * class using the class loader of the supplied class or
+ * the class loader of one of its super classes or their implemented
+ * interfaces. This is necessary during service registration to test
+ * whether a given service object implements its declared service
+ * interfaces.
+ * </p>
+ * <p>
+ * To perform this test, the framework must try to load
+ * the classes associated with the declared service interfaces, so
+ * it must choose a class loader. The class loader of the registering
+ * bundle cannot be used, since this disallows third parties to
+ * register service on behalf of another bundle. Consequently, the
+ * class loader of the service object must be used. However, this is
+ * also not sufficient since the class loader of the service object
+ * may not have direct access to the class in question.
+ * </p>
+ * <p>
+ * The service object's class loader may not have direct access to
+ * its service interface if it extends a super class from another
+ * bundle which implements the service interface from an imported
+ * bundle or if it implements an extension of the service interface
+ * from another bundle which imports the base interface from another
+ * bundle. In these cases, the service object's class loader only has
+ * access to the super class's class or the extended service interface,
+ * respectively, but not to the actual service interface.
+ * </p>
+ * <p>
+ * Thus, it is necessary to not only try to load the service interface
+ * class from the service object's class loader, but from the class
+ * loaders of any interfaces it implements and the class loaders of
+ * all super classes.
+ * </p>
+ * @param svcObj the class that is the root of the search.
+ * @param name the name of the class to load.
+ * @return the loaded class or <tt>null</tt> if it could not be
+ * loaded.
+ **/
+ private static Class loadClassUsingClass(Class clazz, String name)
+ {
+ while (clazz != null)
+ {
+ // Get the class loader of the current class object.
+ ClassLoader loader = clazz.getClassLoader();
+ // A null class loader represents the system class loader.
+ loader = (loader == null) ? ClassLoader.getSystemClassLoader() : loader;
+ try
+ {
+ return loader.loadClass(name);
+ }
+ catch (ClassNotFoundException ex)
+ {
+ // Ignore and try interface class loaders.
+ }
+
+ // Try to see if we can load the class from
+ // one of the class's implemented interface
+ // class loaders.
+ Class[] ifcs = clazz.getInterfaces();
+ for (int i = 0; i < ifcs.length; i++)
+ {
+ clazz = loadClassUsingClass(ifcs[i], name);
+ if (clazz != null)
+ {
+ return clazz;
+ }
+ }
+
+ // Try to see if we can load the class from
+ // the super class class loader.
+ clazz = clazz.getSuperclass();
+ }
+
+ return null;
+ }
+
+ protected ServiceReference[] getServiceReferences(
+ BundleImpl bundle, String className, String expr)
+ throws InvalidSyntaxException
+ {
+ // Define filter if expression is not null.
+ Filter filter = null;
+ if (expr != null)
+ {
+ filter = new FilterImpl(m_logger, expr);
+ }
+
+ // Ask the service registry for all matching service references.
+ List refList = m_registry.getServiceReferences(className, filter);
+
+ // The returned reference list must be filtered for two cases:
+ // 1) The requesting bundle may not be wired to the same class
+ // as the providing bundle (i.e, different versions), so filter
+ // any services for which the requesting bundle might get a
+ // class cast exception.
+ // 2) Security is enabled and the requesting bundle does not have
+ // permission access the service.
+ for (int refIdx = 0; (refList != null) && (refIdx < refList.size()); refIdx++)
+ {
+ // Get the current service reference.
+ ServiceReference ref = (ServiceReference) refList.get(refIdx);
+
+ // Get the service's objectClass property.
+ String[] objectClass = (String[]) ref.getProperty(FelixConstants.OBJECTCLASS);
+
+ // Boolean flag.
+ boolean allow = false;
+
+ // Filter the service reference if the requesting bundle
+ // does not have permission.
+ if (System.getSecurityManager() != null)
+ {
+ for (int classIdx = 0;
+ !allow && (classIdx < objectClass.length);
+ classIdx++)
+ {
+ try
+ {
+ ServicePermission perm = new ServicePermission(
+ objectClass[classIdx], ServicePermission.GET);
+ AccessController.checkPermission(perm);
+ // The bundle only needs permission for one
+ // of the service interfaces, so break out
+ // of the loop when permission is granted.
+ allow = true;
+ }
+ catch (Exception ex)
+ {
+ // We do not throw this exception since the bundle
+ // is not supposed to know about the service at all
+ // if it does not have permission.
+ m_logger.log(LogWrapper.LOG_ERROR, ex.getMessage());
+ }
+ }
+
+ if (!allow)
+ {
+ refList.remove(refIdx);
+ refIdx--;
+ continue;
+ }
+ }
+
+ // Now check for castability.
+ if (!isServiceAssignable(bundle, ref))
+ {
+ refList.remove(refIdx);
+ refIdx--;
+ }
+ }
+
+ if (refList.size() > 0)
+ {
+ return (ServiceReference[]) refList.toArray(new ServiceReference[refList.size()]);
+ }
+
+ return null;
+ }
+
+ /**
+ * This method determines if the requesting bundle is able to cast
+ * the specified service reference based on class visibility rules
+ * of the underlying modules.
+ * @param requester The bundle requesting the service.
+ * @param ref The service in question.
+ * @return <tt>true</tt> if the requesting bundle is able to case
+ * the service object to a known type.
+ **/
+ protected boolean isServiceAssignable(BundleImpl requester, ServiceReference ref)
+ {
+ // Boolean flag.
+ boolean allow = true;
+ // Get the service's objectClass property.
+ String[] objectClass = (String[]) ref.getProperty(FelixConstants.OBJECTCLASS);
+
+ // The the service reference is not assignable when the requesting
+ // bundle is wired to a different version of the service object.
+ // NOTE: We are pessimistic here, if any class in the service's
+ // objectClass is not usable by the requesting bundle, then we
+ // disallow the service reference.
+ for (int classIdx = 0; (allow) && (classIdx < objectClass.length); classIdx++)
+ {
+ if (!ref.isAssignableTo(requester, objectClass[classIdx]))
+ {
+ allow = false;
+ }
+ }
+ return allow;
+ }
+
+ protected Object getService(Bundle bundle, ServiceReference ref)
+ {
+ // Check that the bundle has permission to get at least
+ // one of the service interfaces; the objectClass property
+ // of the service stores its service interfaces.
+ String[] objectClass = (String[])
+ ref.getProperty(Constants.OBJECTCLASS);
+ if (objectClass == null)
+ {
+ return null;
+ }
+
+ boolean hasPermission = false;
+ if (System.getSecurityManager() != null)
+ {
+ for (int i = 0;
+ !hasPermission && (i < objectClass.length);
+ i++)
+ {
+ try
+ {
+ ServicePermission perm =
+ new ServicePermission(
+ objectClass[i], ServicePermission.GET);
+ AccessController.checkPermission(perm);
+ hasPermission = true;
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+ }
+ else
+ {
+ hasPermission = true;
+ }
+
+ // If the bundle does not permission to access the service,
+ // then return null.
+ if (!hasPermission)
+ {
+ return null;
+ }
+
+ return m_registry.getService(bundle, ref);
+ }
+
+ protected boolean ungetService(Bundle bundle, ServiceReference ref)
+ {
+ return m_registry.ungetService(bundle, ref);
+ }
+
+ protected File getDataFile(BundleImpl bundle, String s)
+ {
+ // The spec says to throw an error if the bundle
+ // is stopped, which I assume means not active,
+ // starting, or stopping.
+ if ((bundle.getInfo().getState() != Bundle.ACTIVE) &&
+ (bundle.getInfo().getState() != Bundle.STARTING) &&
+ (bundle.getInfo().getState() != Bundle.STOPPING))
+ {
+ throw new IllegalStateException("Only active bundles can create files.");
+ }
+ try
+ {
+ return m_cache.getArchive(
+ bundle.getInfo().getBundleId()).getDataFile(s);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, ex.getMessage());
+ return null;
+ }
+ }
+
+ //
+ // PackageAdmin related methods.
+ //
+
+ /**
+ * Returns the exported package associated with the specified
+ * package name. This is used by the PackageAdmin service
+ * implementation.
+ *
+ * @param name The name of the exported package to find.
+ * @return The exported package or null if no matching package was found.
+ **/
+ protected ExportedPackage getExportedPackage(String pkgName)
+ {
+ // First, find the bundle exporting the package.
+ BundleImpl bundle = null;
+ R4SearchPolicy search = (R4SearchPolicy) m_mgr.getSearchPolicy();
+ Module[] exporters = search.getInUseExporters(new R4Package(pkgName, null, null));
+ if (exporters != null)
+ {
+ // Since OSGi R4 there may be more than one exporting, so just
+ // take the first one.
+ bundle = (BundleImpl) getBundle(
+ BundleInfo.getBundleIdFromModuleId(exporters[0].getId()));
+ }
+
+ // If we have found the exporting bundle, then return the
+ // exported package interface instance.
+ if (bundle != null)
+ {
+ // We need to find the version of the exported package, but this
+ // is tricky since there may be multiple versions of the package
+ // offered by a given bundle, since multiple revisions of the
+ // bundle JAR file may exist if the bundle was updated without
+ // refreshing the framework. In this case, each revision of the
+ // bundle JAR file is represented as a module in the BundleInfo
+ // module array, which is ordered from oldest to newest. We assume
+ // that the first module found to be exporting the package is the
+ // provider of the package, which makes sense since it must have
+ // been resolved first.
+ Module[] modules = bundle.getInfo().getModules();
+ for (int modIdx = 0; modIdx < modules.length; modIdx++)
+ {
+ R4Package pkg = R4SearchPolicy.getExportPackage(modules[modIdx], pkgName);
+ if (pkg != null)
+ {
+ return new ExportedPackageImpl(this, bundle, pkgName, pkg.getVersionLow());
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns an array of all actively exported packages from the specified
+ * bundle or if the specified bundle is <tt>null</tt> an array
+ * containing all actively exported packages by all bundles.
+ *
+ * @param b The bundle whose exported packages are to be retrieved
+ * or <tt>null</tt> if the exported packages of all bundles are
+ * to be retrieved.
+ * @return An array of exported packages.
+ **/
+ protected ExportedPackage[] getExportedPackages(Bundle b)
+ {
+ List list = new ArrayList();
+
+ // If a bundle is specified, then return its
+ // exported packages.
+ if (b != null)
+ {
+ BundleImpl bundle = (BundleImpl) b;
+ getExportedPackages(bundle, list);
+ }
+ // Otherwise return all exported packages.
+ else
+ {
+ // To create a list of all exported packages, we must look
+ // in the installed and uninstalled sets of bundles. To
+ // ensure a somewhat consistent view, we will gather all
+ // of this information from within the installed bundle
+ // lock.
+ synchronized (m_installedBundleLock_Priority2)
+ {
+ // First get exported packages from uninstalled bundles.
+ synchronized (m_uninstalledBundlesLock_Priority3)
+ {
+ for (int bundleIdx = 0;
+ (m_uninstalledBundles != null) && (bundleIdx < m_uninstalledBundles.length);
+ bundleIdx++)
+ {
+ BundleImpl bundle = m_uninstalledBundles[bundleIdx];
+ getExportedPackages(bundle, list);
+ }
+ }
+
+ // Now get exported packages from installed bundles.
+ Bundle[] bundles = getBundles();
+ for (int bundleIdx = 0; bundleIdx < bundles.length; bundleIdx++)
+ {
+ BundleImpl bundle = (BundleImpl) bundles[bundleIdx];
+ getExportedPackages(bundle, list);
+ }
+ }
+ }
+
+ return (ExportedPackage[]) list.toArray(new ExportedPackage[list.size()]);
+ }
+
+ /**
+ * Adds any current active exported packages from the specified bundle
+ * to the passed in list.
+ * @param bundle The bundle from which to retrieve exported packages.
+ * @param list The list to which the exported packages are added
+ **/
+ private void getExportedPackages(BundleImpl bundle, List list)
+ {
+ R4SearchPolicy policy = (R4SearchPolicy) m_mgr.getSearchPolicy();
+
+ // Since a bundle may have many modules associated with it,
+ // one for each revision in the cache, search each module
+ // for each revision to get all exports.
+ Module[] modules = bundle.getInfo().getModules();
+ for (int modIdx = 0; modIdx < modules.length; modIdx++)
+ {
+ R4Package[] exports = R4SearchPolicy.getExportsAttr(modules[modIdx]);
+ if (exports.length > 0)
+ {
+ for (int expIdx = 0; expIdx < exports.length; expIdx++)
+ {
+ // See if the target bundle's module is one of the
+ // "in use" exporters of the package.
+ Module[] inUseModules = policy.getInUseExporters(exports[expIdx]);
+ if (R4SearchPolicy.isModuleInArray(inUseModules, modules[modIdx]))
+ {
+ list.add(new ExportedPackageImpl(
+ this, bundle, exports[expIdx].getId(), exports[expIdx].getVersionLow()));
+ }
+ }
+ }
+ }
+ }
+
+ protected Bundle[] getImportingBundles(ExportedPackage ep)
+ {
+ // Get exporting bundle; we need to use this internal
+ // method because the spec says ep.getExportingBundle()
+ // should return null if the package is stale.
+ BundleImpl exporter = (BundleImpl)
+ ((ExportedPackageImpl) ep).getExportingBundleInternal();
+ BundleInfo exporterInfo = exporter.getInfo();
+
+ // Create list for storing importing bundles.
+ List list = new ArrayList();
+ Bundle[] bundles = getBundles();
+
+ // Check all bundles to see who imports the package.
+ for (int bundleIdx = 0; bundleIdx < bundles.length; bundleIdx++)
+ {
+ BundleImpl importer = (BundleImpl) bundles[bundleIdx];
+
+ // Ignore the bundle if it imports from itself.
+ if (exporter != importer)
+ {
+ // Check the import wires of all modules for all bundles.
+ Module[] modules = importer.getInfo().getModules();
+ for (int modIdx = 0; modIdx < modules.length; modIdx++)
+ {
+ R4Wire wire = R4SearchPolicy.getWire(modules[modIdx], ep.getName());
+
+ // If the resolving module is associated with the
+ // exporting bundle, then add current bundle to
+ // import list.
+ if ((wire != null) && exporterInfo.hasModule(wire.m_module))
+ {
+ // Add the bundle to the list of importers.
+ list.add(bundles[bundleIdx]);
+ break;
+ }
+ }
+ }
+ }
+
+ // Return the results.
+ if (list.size() > 0)
+ {
+ return (Bundle[]) list.toArray(new Bundle[list.size()]);
+ }
+
+ return null;
+ }
+
+ protected void refreshPackages(Bundle[] targets)
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+
+ // Acquire locks for all impacted bundles.
+ BundleImpl[] bundles = acquireBundleRefreshLocks(targets);
+
+ // Remove any targeted bundles from the uninstalled bundles
+ // array, since they will be removed from the system after
+ // the refresh.
+ for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+ {
+ forgetUninstalledBundle(bundles[i]);
+ }
+
+ try
+ {
+ // If there are targets, then refresh each one.
+ if (bundles != null)
+ {
+ // At this point the map contains every bundle that has been
+ // updated and/or removed as well as all bundles that import
+ // packages from these bundles.
+
+ // Create refresh helpers for each bundle.
+ RefreshHelper[] helpers = new RefreshHelper[bundles.length];
+ for (int i = 0; i < bundles.length; i++)
+ {
+ helpers[i] = new RefreshHelper(bundles[i]);
+ }
+
+ // Stop, purge or remove, and reinitialize all bundles first.
+ for (int i = 0; i < helpers.length; i++)
+ {
+ helpers[i].stop();
+ helpers[i].purgeOrRemove();
+ helpers[i].reinitialize();
+ }
+
+ // Then restart all bundles that were previously running.
+ for (int i = 0; i < helpers.length; i++)
+ {
+ helpers[i].restart();
+ }
+ }
+ }
+ finally
+ {
+ // Always release all bundle locks.
+ releaseBundleLocks(bundles);
+ }
+
+ fireFrameworkEvent(FrameworkEvent.PACKAGES_REFRESHED, getBundle(0), null);
+ }
+
+ private void populateImportGraph(BundleImpl target, Map map)
+ {
+ // Get the exported packages for the specified bundle.
+ ExportedPackage[] pkgs = getExportedPackages(target);
+
+ for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+ {
+ // Get all imports of this package.
+ Bundle[] importers = getImportingBundles(pkgs[pkgIdx]);
+
+ for (int impIdx = 0;
+ (importers != null) && (impIdx < importers.length);
+ impIdx++)
+ {
+ // Add each importing bundle to map.
+ map.put(importers[impIdx], importers[impIdx]);
+ // Now recurse into each bundle to get its importers.
+ populateImportGraph(
+ (BundleImpl) importers[impIdx], map);
+ }
+ }
+ }
+
+ //
+ // Miscellaneous private methods.
+ //
+
+ private BundleInfo createBundleInfo(BundleArchive archive)
+ throws Exception
+ {
+ // Get the bundle manifest.
+ Map headerMap = null;
+ try
+ {
+ // Although there should only ever be one revision at this
+ // point, get the header for the current revision to be safe.
+ headerMap = archive.getManifestHeader(archive.getRevisionCount() - 1);
+ }
+ catch (Exception ex)
+ {
+ throw new BundleException("Unable to read JAR manifest.", ex);
+ }
+
+ // We can't do anything without the manifest header.
+ if (headerMap == null)
+ {
+ throw new BundleException("Unable to read JAR manifest header.");
+ }
+
+ // Create the module for the bundle; although there should only
+ // ever be one revision at this point, create the module for
+ // the current revision to be safe.
+ Module module = createModule(
+ archive.getId(), archive.getRevisionCount() - 1, headerMap);
+
+ // Finally, create an return the bundle info.
+ return new BundleInfo(m_logger, archive, module);
+ }
+
+ /**
+ * Creates a module for a given bundle by reading the bundle's
+ * manifest meta-data and converting it to work with the underlying
+ * import/export search policy of the module loader.
+ * @param id The identifier of the bundle for which the module should
+ * be created.
+ * @param headers The headers map associated with the bundle.
+ * @return The initialized and/or newly created module.
+ **/
+ private Module createModule(long id, int revision, Map headerMap)
+ throws Exception
+ {
+ // Get the manifest version.
+ String version = (String) headerMap.get(FelixConstants.BUNDLE_MANIFESTVERSION);
+ version = (version == null) ? "1" : version;
+ if (!version.equals("1") && !version.equals("2"))
+ {
+ throw new BundleException("Unknown 'Bundle-ManifestVersion' value: " + version);
+ }
+
+ // Create the resource sources for the bundle. The resource sources
+ // are comprised of the bundle's class path values (as JarResourceSources).
+ ResourceSource[] resSources = null;
+ try
+ {
+ // Get bundle class path for the specified revision from cache.
+ String[] classPath = m_cache.getArchive(id).getClassPath(revision);
+
+ // Create resource sources for everything.
+ resSources = new ResourceSource[classPath.length];
+ for (int i = 0; i < classPath.length; i++)
+ {
+ resSources[i] = new JarResourceSource(classPath[i]);
+ }
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ throw new BundleException("Error in class path: " + ex);
+ }
+
+ // Get import packages from bundle manifest.
+ R4Package[] imports = R4Package.parseImportOrExportHeader(
+ (String) headerMap.get(Constants.IMPORT_PACKAGE));
+
+
+ // Check to make sure that R3 bundles have only specified
+ // the 'specification-version' attribute and no directives.
+ if (version.equals("1"))
+ {
+ for (int i = 0; (imports != null) && (i < imports.length); i++)
+ {
+ if (imports[i].getDirectives().length != 0)
+ {
+ throw new BundleException("R3 imports cannot contain directives.");
+ }
+ // NOTE: This is checking for "version" rather than "specification-version"
+ // because the package class normalizes to "version" to avoid having
+ // future special cases. This could be changed if more strict behavior
+ // is required.
+ if ((imports[i].getVersionHigh() != null) ||
+ (imports[i].getAttributes().length > 1) ||
+ ((imports[i].getAttributes().length == 1) &&
+ (!imports[i].getAttributes()[0].getName().equals(FelixConstants.VERSION_ATTRIBUTE))))
+ {
+ throw new BundleException(
+ "Import does not conform to R3 syntax: " + imports[i]);
+ }
+ }
+ }
+
+ // Get export packages from bundle manifest.
+ R4Package[] exports = R4Package.parseImportOrExportHeader(
+ (String) headerMap.get(Constants.EXPORT_PACKAGE));
+
+ // Check to make sure that R3 bundles have only specified
+ // the 'specification-version' attribute and no directives.
+ // In addition, all R3 exports imply imports, so add a
+ // corresponding import for each export.
+ if (version.equals("1"))
+ {
+ for (int i = 0; (exports != null) && (i < exports.length); i++)
+ {
+ if (exports[i].getDirectives().length != 0)
+ {
+ throw new BundleException("R3 exports cannot contain directives.");
+ }
+ // NOTE: This is checking for "version" rather than "specification-version"
+ // because the package class normalizes to "version" to avoid having
+ // future special cases. This could be changed if more strict behavior
+ // is required.
+ if ((exports[i].getAttributes().length > 1) ||
+ ((exports[i].getAttributes().length == 1) &&
+ (!exports[i].getAttributes()[0].getName().equals(FelixConstants.VERSION_ATTRIBUTE))))
+ {
+ throw new BundleException(
+ "Export does not conform to R3 syntax: " + imports[i]);
+ }
+ }
+
+ R4Package[] newImports = new R4Package[imports.length + exports.length];
+ System.arraycopy(imports, 0, newImports, 0, imports.length);
+ System.arraycopy(exports, 0, newImports, imports.length, exports.length);
+ imports = newImports;
+ }
+
+ // For R3 bundles, add a "uses" directive onto each export
+ // that references every other import (which will include
+ // exports, since export implies import); this is
+ // necessary since R3 bundles assumed a single class space,
+ // but R4 allows for multiple class spaces.
+ if (version.equals("1"))
+ {
+ String usesValue = "";
+ for (int i = 0; (imports != null) && (i < imports.length); i++)
+ {
+ usesValue = usesValue
+ + ((usesValue.length() > 0) ? "," : "")
+ + imports[i].getId();
+ }
+ R4Directive uses = new R4Directive(
+ FelixConstants.USES_DIRECTIVE, usesValue);
+ for (int i = 0; (exports != null) && (i < exports.length); i++)
+ {
+ exports[i] = new R4Package(
+ exports[i].getId(),
+ new R4Directive[] { uses },
+ exports[i].getAttributes());
+ }
+ }
+
+// TODO: CHECK FOR DUPLICATE IMPORTS/EXPORTS HERE.
+
+ // Get dynamic import packages from bundle manifest.
+ R4Package[] dynamics = R4Package.parseImportOrExportHeader(
+ (String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
+
+ // Check to make sure that R3 bundles have no attributes or
+ // directives.
+ if (version.equals("1"))
+ {
+ for (int i = 0; (dynamics != null) && (i < dynamics.length); i++)
+ {
+ if (dynamics[i].getDirectives().length != 0)
+ {
+ throw new BundleException("R3 dynamic imports cannot contain directives.");
+ }
+ if (dynamics[i].getAttributes().length != 0)
+ {
+ throw new BundleException("R3 dynamic imports cannot contain attributes.");
+ }
+ }
+ }
+
+ Object[][] attributes = {
+ new Object[] { R4SearchPolicy.EXPORTS_ATTR, exports },
+ new Object[] { R4SearchPolicy.IMPORTS_ATTR, imports },
+ new Object[] { R4SearchPolicy.DYNAMICIMPORTS_ATTR, dynamics }
+ };
+
+ // Get native library entry names for module library sources.
+ LibraryInfo[] libraries =
+ Util.parseLibraryStrings(
+ Util.parseDelimitedString(
+ (String) headerMap.get(Constants.BUNDLE_NATIVECODE), ","));
+ LibrarySource[] libSources = {
+ new OSGiLibrarySource(
+ m_logger, m_cache, id, revision,
+ getProperty(Constants.FRAMEWORK_OS_NAME),
+ getProperty(Constants.FRAMEWORK_PROCESSOR),
+ libraries)
+ };
+
+ Module module =
+ m_mgr.addModule(
+ Long.toString(id) + "." + Integer.toString(revision),
+ attributes, resSources, libSources);
+
+ return module;
+ }
+
+ private BundleActivator createBundleActivator(BundleInfo info)
+ throws Exception
+ {
+ // CONCURRENCY NOTE:
+ // This method is called indirectly from startBundle() (via _startBundle()),
+ // which has the exclusion lock, so there is no need to do any locking here.
+
+ BundleActivator activator = null;
+
+ String strict = m_config.get(FelixConstants.STRICT_OSGI_PROP);
+ boolean isStrict = (strict == null) ? true : strict.equals("true");
+ if (!isStrict)
+ {
+ try
+ {
+ activator =
+ m_cache.getArchive(info.getBundleId())
+ .getActivator(info.getCurrentModule().getClassLoader());
+ }
+ catch (Exception ex)
+ {
+ activator = null;
+ }
+ }
+
+ // If there was no cached activator, then get the activator
+ // class from the bundle manifest.
+ if (activator == null)
+ {
+ // Get the associated bundle archive.
+ BundleArchive ba = m_cache.getArchive(info.getBundleId());
+ // Get the manifest from the current revision; revision is
+ // base zero so subtract one from the count to get the
+ // current revision.
+ Map headerMap = ba.getManifestHeader(ba.getRevisionCount() - 1);
+ // Get the activator class attribute.
+ String className = (String) headerMap.get(Constants.BUNDLE_ACTIVATOR);
+ // Try to instantiate activator class if present.
+ if (className != null)
+ {
+ className = className.trim();
+ Class clazz = info.getCurrentModule().getClassLoader().loadClass(className);
+ if (clazz == null)
+ {
+ throw new BundleException("Not found: "
+ + className);
+ }
+ activator = (BundleActivator) clazz.newInstance();
+ }
+ }
+
+ return activator;
+ }
+
+ private void purgeBundle(BundleImpl bundle) throws Exception
+ {
+ // Acquire bundle lock.
+ acquireBundleLock(bundle);
+
+ try
+ {
+ BundleInfo info = bundle.getInfo();
+
+ // In case of a refresh, then we want to physically
+ // remove the bundle's modules from the module manager.
+ // This is necessary for two reasons: 1) because
+ // under Windows we won't be able to delete the bundle
+ // because files might be left open in the resource
+ // sources of its modules and 2) we want to make sure
+ // that no references to old modules exist since they
+ // will all be stale after the refresh. The only other
+ // way to do this is to remove the bundle, but that
+ // would be incorrect, because this is a refresh operation
+ // and should not trigger bundle REMOVE events.
+ Module[] modules = info.getModules();
+ for (int i = 0; i < modules.length; i++)
+ {
+ m_mgr.removeModule(modules[i]);
+ }
+
+ // Purge all bundle revisions, but the current one.
+ m_cache.purge(m_cache.getArchive(info.getBundleId()));
+ }
+ finally
+ {
+ // Always release the bundle lock.
+ releaseBundleLock(bundle);
+ }
+ }
+
+ private void garbageCollectBundle(BundleImpl bundle) throws Exception
+ {
+ // CONCURRENCY NOTE: There is no reason to lock this bundle,
+ // because this method is only called during shutdown or a
+ // refresh operation and these are already guarded by locks.
+
+ // Remove the bundle's associated modules from
+ // the module manager.
+ Module[] modules = bundle.getInfo().getModules();
+ for (int i = 0; i < modules.length; i++)
+ {
+ m_mgr.removeModule(modules[i]);
+ }
+
+ // Remove the bundle from the cache.
+ m_cache.remove(m_cache.getArchive(bundle.getInfo().getBundleId()));
+ }
+
+ //
+ // Event-related methods.
+ //
+
+ /**
+ * Fires bundle events.
+ **/
+ private void fireFrameworkEvent(
+ int type, Bundle bundle, Throwable throwable)
+ {
+ if (m_frameworkDispatcher == null)
+ {
+ m_frameworkDispatcher = new Dispatcher() {
+ public void dispatch(EventListener l, EventObject eventObj)
+ {
+ ((FrameworkListener) l)
+ .frameworkEvent((FrameworkEvent) eventObj);
+ }
+ };
+ }
+ FrameworkEvent event = new FrameworkEvent(type, bundle, throwable);
+ m_dispatchQueue.dispatch(
+ m_frameworkDispatcher, FrameworkListener.class, event);
+ }
+
+ /**
+ * Fires bundle events.
+ *
+ * @param type The type of bundle event to fire.
+ * @param bundle The bundle associated with the event.
+ **/
+ private void fireBundleEvent(int type, Bundle bundle)
+ {
+ if (m_bundleDispatcher == null)
+ {
+ m_bundleDispatcher = new Dispatcher() {
+ public void dispatch(EventListener l, EventObject eventObj)
+ {
+ ((BundleListener) l)
+ .bundleChanged((BundleEvent) eventObj);
+ }
+ };
+ }
+ BundleEvent event = null;
+ event = new BundleEvent(type, bundle);
+ m_dispatchQueue.dispatch(m_bundleDispatcher,
+ BundleListener.class, event);
+ }
+
+ /**
+ * Fires service events.
+ *
+ * @param type The type of service event to fire.
+ * @param ref The service reference associated with the event.
+ **/
+ private void fireServiceEvent(ServiceEvent event)
+ {
+ if (m_serviceDispatcher == null)
+ {
+ m_serviceDispatcher = new Dispatcher() {
+ public void dispatch(EventListener l, EventObject eventObj)
+ {
+// TODO: Filter service events based on service permissions.
+ if (l instanceof ListenerWrapper)
+ {
+ BundleImpl bundle = (BundleImpl) ((ServiceListenerWrapper) l).getBundle();
+ if (isServiceAssignable(bundle, ((ServiceEvent) eventObj).getServiceReference()))
+ {
+ ((ServiceListener) l)
+ .serviceChanged((ServiceEvent) eventObj);
+ }
+ }
+ else
+ {
+ ((ServiceListener) l)
+ .serviceChanged((ServiceEvent) eventObj);
+ }
+ }
+ };
+ }
+ m_dispatchQueue.dispatch(m_serviceDispatcher,
+ ServiceListener.class, event);
+ }
+
+ //
+ // Property related methods.
+ //
+
+ private void initializeFrameworkProperties()
+ {
+ // Standard OSGi properties.
+ m_configProps.put(
+ FelixConstants.FRAMEWORK_VERSION,
+ FelixConstants.FRAMEWORK_VERSION_VALUE);
+ m_configProps.put(
+ FelixConstants.FRAMEWORK_VENDOR,
+ FelixConstants.FRAMEWORK_VENDOR_VALUE);
+ m_configProps.put(
+ FelixConstants.FRAMEWORK_LANGUAGE,
+ System.getProperty("user.language"));
+ m_configProps.put(
+ FelixConstants.FRAMEWORK_OS_VERSION,
+ System.getProperty("os.version"));
+
+ String s = null;
+ s = OSGiLibrarySource.normalizePropertyValue(
+ FelixConstants.FRAMEWORK_OS_NAME,
+ System.getProperty("os.name"));
+ m_configProps.put(FelixConstants.FRAMEWORK_OS_NAME, s);
+ s = OSGiLibrarySource.normalizePropertyValue(
+ FelixConstants.FRAMEWORK_PROCESSOR,
+ System.getProperty("os.arch"));
+ m_configProps.put(FelixConstants.FRAMEWORK_PROCESSOR, s);
+
+ // The framework version property.
+ m_configProps.put(
+ FelixConstants.FELIX_VERSION_PROPERTY,
+ FelixConstants.FELIX_VERSION_VALUE);
+ }
+
+ private void processAutoProperties()
+ {
+ // The auto-install property specifies a space-delimited list of
+ // bundle URLs to be automatically installed into each new profile;
+ // the start level to which the bundles are assigned is specified by
+ // appending a ".n" to the auto-install property name, where "n" is
+ // the desired start level for the list of bundles.
+ String[] keys = m_config.getKeys();
+ for (int i = 0; (keys != null) && (i < keys.length); i++)
+ {
+ if (keys[i].startsWith(FelixConstants.AUTO_INSTALL_PROP))
+ {
+ int startLevel = 1;
+ try
+ {
+ startLevel = Integer.parseInt(keys[i].substring(keys[i].lastIndexOf('.') + 1));
+ }
+ catch (NumberFormatException ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "Invalid property: " + keys[i]);
+ }
+ StringTokenizer st = new StringTokenizer(m_config.get(keys[i]), "\" ",true);
+ if (st.countTokens() > 0)
+ {
+ String location = null;
+ do
+ {
+ location = nextLocation(st);
+ if (location != null)
+ {
+ try
+ {
+ BundleImpl b = (BundleImpl) installBundle(location, null);
+ b.getInfo().setStartLevel(startLevel);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR, "Auto-properties install.", ex);
+ }
+ }
+ }
+ while (location != null);
+ }
+ }
+ }
+
+ // The auto-start property specifies a space-delimited list of
+ // bundle URLs to be automatically installed and started into each
+ // new profile; the start level to which the bundles are assigned
+ // is specified by appending a ".n" to the auto-start property name,
+ // where "n" is the desired start level for the list of bundles.
+ // The following code starts bundles in two passes, first it installs
+ // them, then it starts them.
+ for (int i = 0; (keys != null) && (i < keys.length); i++)
+ {
+ if (keys[i].startsWith(FelixConstants.AUTO_START_PROP))
+ {
+ int startLevel = 1;
+ try
+ {
+ startLevel = Integer.parseInt(keys[i].substring(keys[i].lastIndexOf('.') + 1));
+ }
+ catch (NumberFormatException ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "Invalid property: " + keys[i]);
+ }
+ StringTokenizer st = new StringTokenizer(m_config.get(keys[i]), "\" ",true);
+ if (st.countTokens() > 0)
+ {
+ String location = null;
+ do
+ {
+ location = nextLocation(st);
+ if (location != null)
+ {
+ try
+ {
+ BundleImpl b = (BundleImpl) installBundle(location, null);
+ b.getInfo().setStartLevel(startLevel);
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "Auto-properties install.", ex);
+ }
+ }
+ }
+ while (location != null);
+ }
+ }
+ }
+
+ // Now loop through and start the installed bundles.
+ for (int i = 0; (keys != null) && (i < keys.length); i++)
+ {
+ if (keys[i].startsWith(FelixConstants.AUTO_START_PROP))
+ {
+ StringTokenizer st = new StringTokenizer(m_config.get(keys[i]), "\" ",true);
+ if (st.countTokens() > 0)
+ {
+ String location = null;
+ do
+ {
+ location = nextLocation(st);
+ if (location != null)
+ {
+ // Installing twice just returns the same bundle.
+ try
+ {
+ BundleImpl bundle = (BundleImpl) installBundle(location, null);
+ if (bundle != null)
+ {
+ startBundle(bundle, true);
+ }
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR, "Auto-properties start.", ex);
+ }
+ }
+ }
+ while (location != null);
+ }
+ }
+ }
+ }
+
+ private String nextLocation(StringTokenizer st)
+ {
+ String retVal = null;
+
+ if (st.countTokens() > 0)
+ {
+ String tokenList = "\" ";
+ StringBuffer tokBuf = new StringBuffer(10);
+ String tok = null;
+ boolean inQuote = false;
+ boolean tokStarted = false;
+ boolean exit = false;
+ while ((st.hasMoreTokens()) && (!exit))
+ {
+ tok = st.nextToken(tokenList);
+ if (tok.equals("\""))
+ {
+ inQuote = ! inQuote;
+ if (inQuote)
+ {
+ tokenList = "\"";
+ }
+ else
+ {
+ tokenList = "\" ";
+ }
+
+ }
+ else if (tok.equals(" "))
+ {
+ if (tokStarted)
+ {
+ retVal = tokBuf.toString();
+ tokStarted=false;
+ tokBuf = new StringBuffer(10);
+ exit = true;
+ }
+ }
+ else
+ {
+ tokStarted = true;
+ tokBuf.append(tok.trim());
+ }
+ }
+
+ // Handle case where end of token stream and
+ // still got data
+ if ((!exit) && (tokStarted))
+ {
+ retVal = tokBuf.toString();
+ }
+ }
+
+ return retVal;
+ }
+
+ //
+ // Private utility methods.
+ //
+
+ /**
+ * Generated the next valid bundle identifier.
+ **/
+ private synchronized long getNextId()
+ {
+ return m_nextId++;
+ }
+
+ //
+ // Configuration methods and inner classes.
+ //
+
+ public PropertyResolver getConfig()
+ {
+ return m_config;
+ }
+
+ private class ConfigImpl implements PropertyResolver
+ {
+ public String get(String key)
+ {
+ return (m_configProps == null) ? null : m_configProps.get(key);
+ }
+
+ public String[] getKeys()
+ {
+ return m_configProps.getKeys();
+ }
+ }
+
+ //
+ // Logging methods and inner classes.
+ //
+
+ public LogWrapper getLogger()
+ {
+ return m_logger;
+ }
+
+ /**
+ * Simple class that is used in <tt>refreshPackages()</tt> to embody
+ * the refresh logic in order to keep the code clean. This class is
+ * not static because it needs access to framework event firing methods.
+ **/
+ private class RefreshHelper
+ {
+ private BundleImpl m_bundle = null;
+
+ public RefreshHelper(Bundle bundle)
+ {
+ m_bundle = (BundleImpl) bundle;
+ }
+
+ public void stop()
+ {
+ if (m_bundle.getInfo().getState() == Bundle.ACTIVE)
+ {
+ try
+ {
+ stopBundle(m_bundle, false);
+ }
+ catch (BundleException ex)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+ }
+ }
+ }
+
+ public void purgeOrRemove()
+ {
+ try
+ {
+ BundleInfo info = m_bundle.getInfo();
+
+ // Remove or purge the bundle depending on its
+ // current state.
+ if (info.getState() == Bundle.UNINSTALLED)
+ {
+ // This physically removes the bundle from memory
+ // as well as the bundle cache.
+ garbageCollectBundle(m_bundle);
+ m_bundle = null;
+ }
+ else
+ {
+ // This physically removes all old revisions of the
+ // bundle from memory and only maintains the newest
+ // version in the bundle cache.
+ purgeBundle(m_bundle);
+ }
+ }
+ catch (Exception ex)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+ }
+ }
+
+ public void reinitialize()
+ {
+ if (m_bundle != null)
+ {
+ try
+ {
+ BundleInfo info = m_bundle.getInfo();
+ BundleInfo newInfo = createBundleInfo(info.getArchive());
+ newInfo.syncLock(info);
+ m_bundle.setInfo(newInfo);
+ }
+ catch (Exception ex)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+ }
+ }
+ }
+
+ public void restart()
+ {
+ if (m_bundle != null)
+ {
+ try
+ {
+ startBundle(m_bundle, false);
+ }
+ catch (BundleException ex)
+ {
+ fireFrameworkEvent(FrameworkEvent.ERROR, m_bundle, ex);
+ }
+ }
+ }
+ }
+
+ //
+ // Locking related methods.
+ //
+
+ private void rememberUninstalledBundle(BundleImpl bundle)
+ {
+ synchronized (m_uninstalledBundlesLock_Priority3)
+ {
+ // Verify that the bundle is not already in the array.
+ for (int i = 0;
+ (m_uninstalledBundles != null) && (i < m_uninstalledBundles.length);
+ i++)
+ {
+ if (m_uninstalledBundles[i] == bundle)
+ {
+ return;
+ }
+ }
+
+ if (m_uninstalledBundles != null)
+ {
+ BundleImpl[] newBundles =
+ new BundleImpl[m_uninstalledBundles.length + 1];
+ System.arraycopy(m_uninstalledBundles, 0,
+ newBundles, 0, m_uninstalledBundles.length);
+ newBundles[m_uninstalledBundles.length] = bundle;
+ m_uninstalledBundles = newBundles;
+ }
+ else
+ {
+ m_uninstalledBundles = new BundleImpl[] { bundle };
+ }
+ }
+ }
+
+ private void forgetUninstalledBundle(BundleImpl bundle)
+ {
+ synchronized (m_uninstalledBundlesLock_Priority3)
+ {
+ if (m_uninstalledBundles == null)
+ {
+ return;
+ }
+
+ int idx = -1;
+ for (int i = 0; i < m_uninstalledBundles.length; i++)
+ {
+ if (m_uninstalledBundles[i] == bundle)
+ {
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx >= 0)
+ {
+ // If this is the only bundle, then point to empty list.
+ if ((m_uninstalledBundles.length - 1) == 0)
+ {
+ m_uninstalledBundles = new BundleImpl[0];
+ }
+ // Otherwise, we need to do some array copying.
+ else
+ {
+ BundleImpl[] newBundles =
+ new BundleImpl[m_uninstalledBundles.length - 1];
+ System.arraycopy(m_uninstalledBundles, 0, newBundles, 0, idx);
+ if (idx < newBundles.length)
+ {
+ System.arraycopy(
+ m_uninstalledBundles, idx + 1,
+ newBundles, idx, newBundles.length - idx);
+ }
+ m_uninstalledBundles = newBundles;
+ }
+ }
+ }
+ }
+
+ protected void acquireInstallLock(String location)
+ throws BundleException
+ {
+ synchronized (m_installRequestLock_Priority1)
+ {
+ while (m_installRequestMap.get(location) != null)
+ {
+ try
+ {
+ m_installRequestLock_Priority1.wait();
+ }
+ catch (InterruptedException ex)
+ {
+ throw new BundleException("Unable to install, thread interrupted.");
+ }
+ }
+
+ m_installRequestMap.put(location, location);
+ }
+ }
+
+ protected void releaseInstallLock(String location)
+ {
+ synchronized (m_installRequestLock_Priority1)
+ {
+ m_installRequestMap.remove(location);
+ m_installRequestLock_Priority1.notifyAll();
+ }
+ }
+
+ protected void acquireBundleLock(BundleImpl bundle)
+ throws BundleException
+ {
+ synchronized (m_bundleLock)
+ {
+ while (!bundle.getInfo().isLockable())
+ {
+ try
+ {
+ m_bundleLock.wait();
+ }
+ catch (InterruptedException ex)
+ {
+ // Ignore and just keep waiting.
+ }
+ }
+ bundle.getInfo().lock();
+ }
+ }
+
+ protected void releaseBundleLock(BundleImpl bundle)
+ {
+ synchronized (m_bundleLock)
+ {
+ bundle.getInfo().unlock();
+ m_bundleLock.notifyAll();
+ }
+ }
+
+ protected BundleImpl[] acquireBundleRefreshLocks(Bundle[] targets)
+ {
+ // Hold bundles to be locked.
+ BundleImpl[] bundles = null;
+
+ synchronized (m_bundleLock)
+ {
+ boolean success = false;
+ while (!success)
+ {
+ // If targets is null, then refresh all pending bundles.
+ Bundle[] newTargets = targets;
+ if (newTargets == null)
+ {
+ List list = new ArrayList();
+
+ // First add all uninstalled bundles.
+ synchronized (m_uninstalledBundlesLock_Priority3)
+ {
+ for (int i = 0;
+ (m_uninstalledBundles != null) && (i < m_uninstalledBundles.length);
+ i++)
+ {
+ list.add(m_uninstalledBundles[i]);
+ }
+ }
+
+ // Then add all updated bundles.
+ synchronized (m_installedBundleLock_Priority2)
+ {
+ Iterator iter = m_installedBundleMap.values().iterator();
+ while (iter.hasNext())
+ {
+ BundleImpl bundle = (BundleImpl) iter.next();
+ if (bundle.getInfo().isRemovalPending())
+ {
+ list.add(bundle);
+ }
+ }
+ }
+
+ // Create an array.
+ if (list.size() > 0)
+ {
+ newTargets = (Bundle[]) list.toArray(new Bundle[list.size()]);
+ }
+ }
+
+ // If there are targets, then find all dependencies
+ // for each one.
+ if (newTargets != null)
+ {
+ // Create map of bundles that import the packages
+ // from the target bundles.
+ Map map = new HashMap();
+ for (int targetIdx = 0; targetIdx < newTargets.length; targetIdx++)
+ {
+ // Add the current target bundle to the map of
+ // bundles to be refreshed.
+ BundleImpl target = (BundleImpl) newTargets[targetIdx];
+ map.put(target, target);
+ // Add all importing bundles to map.
+ populateImportGraph(target, map);
+ }
+
+ bundles = (BundleImpl[]) map.values().toArray(new BundleImpl[map.size()]);
+ }
+
+ // Check if all corresponding bundles can be locked
+ boolean lockable = true;
+ if (bundles != null)
+ {
+ for (int i = 0; lockable && (i < bundles.length); i++)
+ {
+ lockable = bundles[i].getInfo().isLockable();
+ }
+
+ // If we can lock all bundles, then lock them.
+ if (lockable)
+ {
+ for (int i = 0; i < bundles.length; i++)
+ {
+ bundles[i].getInfo().lock();
+ }
+ success = true;
+ }
+ // Otherwise, wait and try again.
+ else
+ {
+ try
+ {
+ m_bundleLock.wait();
+ }
+ catch (InterruptedException ex)
+ {
+ // Ignore and just keep waiting.
+ }
+ }
+ }
+ else
+ {
+ // If there were no bundles to lock, then we can just
+ // exit the lock loop.
+ success = true;
+ }
+ }
+ }
+
+ return bundles;
+ }
+
+ protected void releaseBundleLocks(BundleImpl[] bundles)
+ {
+ // Always unlock any locked bundles.
+ synchronized (m_bundleLock)
+ {
+ for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+ {
+ bundles[i].getInfo().unlock();
+ }
+ m_bundleLock.notifyAll();
+ }
+ }
+}
diff --git a/src/org/apache/osgi/framework/FilterImpl.java b/src/org/apache/osgi/framework/FilterImpl.java
new file mode 100644
index 0000000..4381d28
--- /dev/null
+++ b/src/org/apache/osgi/framework/FilterImpl.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.io.CharArrayReader;
+import java.io.IOException;
+import java.util.*;
+
+import org.apache.osgi.framework.util.CaseInsensitiveMap;
+import org.apache.osgi.framework.util.ldap.*;
+import org.osgi.framework.*;
+
+/**
+ * This class implements an RFC 1960-based filter. The syntax of the
+ * filter string is the string representation of LDAP search filters
+ * as defined in RFC 1960. These filters are used to search for services
+ * and to track services using <tt>ServiceTracker</tt> objects.
+**/
+public class FilterImpl implements Filter
+{
+ private LogWrapper m_logger = null;
+ private String m_toString = null;
+ private Evaluator m_evaluator = null;
+ private SimpleMapper m_mapper = null;
+
+// TODO: FilterImpl needs a logger, this is a hack to get FrameworkUtil to compile.
+ public FilterImpl(String expr) throws InvalidSyntaxException
+ {
+ this(null, expr);
+ }
+
+ /**
+ * Construct a filter for a given filter expression string.
+ * @param expr the filter expression string for the filter.
+ **/
+ public FilterImpl(LogWrapper logger, String expr) throws InvalidSyntaxException
+ {
+ m_logger = logger;
+ if (expr == null)
+ {
+ throw new InvalidSyntaxException("Filter cannot be null", null);
+ }
+
+ if (expr != null)
+ {
+ CharArrayReader car = new CharArrayReader(expr.toCharArray());
+ LdapLexer lexer = new LdapLexer(car);
+ Parser parser = new Parser(lexer);
+ try
+ {
+ if (!parser.start())
+ {
+ throw new InvalidSyntaxException(
+ "Failed to parse LDAP query.", expr);
+ }
+ }
+ catch (ParseException ex)
+ {
+ throw new InvalidSyntaxException(
+ ex.getMessage(), expr);
+ }
+ catch (IOException ex)
+ {
+ throw new InvalidSyntaxException(
+ ex.getMessage(), expr);
+ }
+ m_evaluator = new Evaluator(parser.getProgram());
+ m_mapper = new SimpleMapper();
+ }
+ }
+
+ /**
+ * Compares the <tt>Filter</tt> object to another.
+ * @param o the object to compare this <tt>Filter</tt> against.
+ * @return If the other object is a <tt>Filter</tt> object, it
+ * returns <tt>this.toString().equals(obj.toString())</tt>;
+ * <tt>false</tt> otherwise.
+ **/
+ public boolean equals(Object o)
+ {
+ if (o == null)
+ {
+ return false;
+ }
+ else if (o instanceof Filter)
+ {
+ return toString().equals(o.toString());
+ }
+ return false;
+ }
+
+ /**
+ * Returns the hash code for the <tt>Filter</tt> object.
+ * @return The value <tt>this.toString().hashCode()</tt>.
+ **/
+ public int hashCode()
+ {
+ return toString().hashCode();
+ }
+
+ /**
+ * Filter using a <tt>Dictionary</tt> object. The <tt>Filter</tt>
+ * is executed using the <tt>Dictionary</tt> object's keys and values.
+ * @param dict the <tt>Dictionary</tt> object whose keys and values
+ * are used to determine a match.
+ * @return <tt>true</tt> if the <tt>Dictionary</tt> object's keys
+ * and values match this filter; <tt>false</tt> otherwise.
+ * @throws IllegalArgumentException if the dictionary contains case
+ * variants of the same key name.
+ **/
+ public boolean match(Dictionary dict)
+ throws IllegalArgumentException
+ {
+ try
+ {
+ m_mapper.setSource(dict);
+ return m_evaluator.evaluate(m_mapper);
+ }
+ catch (AttributeNotFoundException ex)
+ {
+ m_logger.log(LogWrapper.LOG_DEBUG, "FilterImpl: " + ex);
+ }
+ catch (EvaluationException ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "FilterImpl: " + toString(), ex);
+ }
+ return false;
+ }
+
+ /**
+ * Filter using a service's properties. The <tt>Filter</tt>
+ * is executed using the properties of the referenced service.
+ * @param ref A reference to the service whose properties
+ * are used to determine a match.
+ * @return <tt>true</tt> if the service's properties match this
+ * filter; <tt>false</tt> otherwise.
+ **/
+ public boolean match(ServiceReference ref)
+ {
+ try
+ {
+ m_mapper.setSource(ref);
+ return m_evaluator.evaluate(m_mapper);
+ }
+ catch (AttributeNotFoundException ex)
+ {
+ m_logger.log(LogWrapper.LOG_DEBUG, "FilterImpl: " + ex);
+ }
+ catch (EvaluationException ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "FilterImpl: " + toString(), ex);
+ }
+ return false;
+ }
+
+ public boolean matchCase(Dictionary dictionary)
+ {
+ // TODO: Implement Filter.matchCase()
+ return false;
+ }
+
+ /**
+ * Returns the <tt>Filter</tt> object's filter string.
+ * @return Filter string.
+ **/
+ public String toString()
+ {
+ if (m_toString == null)
+ {
+ m_toString = m_evaluator.toStringInfix();
+ }
+ return m_toString;
+ }
+
+ static class SimpleMapper implements Mapper
+ {
+ private ServiceReference m_ref = null;
+ private Map m_map = null;
+
+ public void setSource(ServiceReference ref)
+ {
+ m_ref = ref;
+ m_map = null;
+ }
+
+ public void setSource(Dictionary dict)
+ {
+ if (m_map == null)
+ {
+ m_map = new CaseInsensitiveMap();
+ }
+ else
+ {
+ m_map.clear();
+ }
+
+ if (dict != null)
+ {
+ Enumeration keys = dict.keys();
+ while (keys.hasMoreElements())
+ {
+ Object key = keys.nextElement();
+ if (m_map.get(key) == null)
+ {
+ m_map.put(key, dict.get(key));
+ }
+ else
+ {
+ throw new IllegalArgumentException(
+ "Duplicate attribute: " + key.toString());
+ }
+ }
+ }
+ m_ref = null;
+ }
+
+ public Object lookup(String name)
+ {
+ if (m_map == null)
+ {
+ return m_ref.getProperty(name);
+ }
+ return m_map.get(name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/FrameworkUtil.java b/src/org/apache/osgi/framework/FrameworkUtil.java
new file mode 100644
index 0000000..c20aea6
--- /dev/null
+++ b/src/org/apache/osgi/framework/FrameworkUtil.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+
+public class FrameworkUtil
+{
+ public static Filter createFilter(String filter)
+ throws InvalidSyntaxException
+ {
+ return new FilterImpl(filter);
+ }
+}
diff --git a/src/org/apache/osgi/framework/LogWrapper.java b/src/org/apache/osgi/framework/LogWrapper.java
new file mode 100644
index 0000000..a99341c
--- /dev/null
+++ b/src/org/apache/osgi/framework/LogWrapper.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * <p>
+ * This class mimics the standard OSGi <tt>LogService</tt> interface. An
+ * instance of this class will be used by the framework for all logging. Currently,
+ * the implementation of this class just sends log messages to standard output,
+ * but in the future it will be modified to use a log service if one is
+ * installed in the framework. To do so, it will need to use reflection to
+ * call the log service methods, since it will not have access to the
+ * <tt>LogService</tt> class.
+ * </p>
+**/
+// TODO: Modify LogWrapper to get LogService service object and invoke with reflection.
+public class LogWrapper
+{
+ public static final int LOG_ERROR = 1;
+ public static final int LOG_WARNING = 2;
+ public static final int LOG_INFO = 3;
+ public static final int LOG_DEBUG = 4;
+
+ private Object m_logObj = null;
+
+ public LogWrapper()
+ {
+ }
+
+ public void log(int level, String msg)
+ {
+ synchronized (this)
+ {
+ if (m_logObj != null)
+ {
+// Will use reflection.
+// m_logObj.log(level, msg);
+ }
+ else
+ {
+ _log(null, level, msg, null);
+ }
+ }
+ }
+
+ public void log(int level, String msg, Throwable ex)
+ {
+ synchronized (this)
+ {
+ if (m_logObj != null)
+ {
+// Will use reflection.
+// m_logObj.log(level, msg);
+ }
+ else
+ {
+ _log(null, level, msg, ex);
+ }
+ }
+ }
+
+ public void log(ServiceReference sr, int level, String msg)
+ {
+ synchronized (this)
+ {
+ if (m_logObj != null)
+ {
+// Will use reflection.
+// m_logObj.log(level, msg);
+ }
+ else
+ {
+ _log(sr, level, msg, null);
+ }
+ }
+ }
+
+ public void log(ServiceReference sr, int level, String msg, Throwable ex)
+ {
+ synchronized (this)
+ {
+ if (m_logObj != null)
+ {
+// Will use reflection.
+// m_logObj.log(level, msg);
+ }
+ else
+ {
+ _log(sr, level, msg, ex);
+ }
+ }
+ }
+
+ private void _log(ServiceReference sr, int level, String msg, Throwable ex)
+ {
+ String s = (sr == null) ? null : "SvcRef " + sr;
+ s = (s == null) ? msg : s + " " + msg;
+ s = (ex == null) ? s : s + " (" + ex + ")";
+ switch (level)
+ {
+ case LOG_DEBUG:
+ System.out.println("DEBUG: " + s);
+ break;
+ case LOG_ERROR:
+ System.out.println("ERROR: " + s);
+ if (ex != null)
+ {
+ if ((ex instanceof BundleException) &&
+ (((BundleException) ex).getNestedException() != null))
+ {
+ ex = ((BundleException) ex).getNestedException();
+ }
+ ex.printStackTrace();
+ }
+ break;
+ case LOG_INFO:
+ System.out.println("INFO: " + s);
+ break;
+ case LOG_WARNING:
+ System.out.println("WARNING: " + s);
+ break;
+ default:
+ System.out.println("UNKNOWN[" + level + "]: " + s);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/Main.java b/src/org/apache/osgi/framework/Main.java
new file mode 100644
index 0000000..c17b184
--- /dev/null
+++ b/src/org/apache/osgi/framework/Main.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import org.apache.osgi.framework.cache.DefaultBundleCache;
+import org.apache.osgi.framework.util.CaseInsensitiveMap;
+import org.apache.osgi.framework.util.MutablePropertyResolverImpl;
+
+/**
+ * <p>
+ * This class is the default way to instantiate and execute the framework. It is not
+ * intended to be the only way to instantiate and execute the framework; rather, it is
+ * one example of how to do so. When embedding the framework in a host application,
+ * this class can serve as a simple guide of how to do so. It may even be
+ * worthwhile to reuse some of its property handling capabilities. This class
+ * is completely static and is only intended to start a single instance of
+ * the framework.
+ * </p>
+**/
+public class Main
+{
+ /**
+ * The system property name used to specify an URL to the system
+ * property file.
+ **/
+ public static final String SYSTEM_PROPERTIES_PROP = "felix.system.properties";
+ /**
+ * The default name used for the system properties file.
+ **/
+ public static final String SYSTEM_PROPERTIES_FILE_VALUE = "system.properties";
+ /**
+ * The system property name used to specify an URL to the configuration
+ * property file to be used for the created the framework instance.
+ **/
+ public static final String CONFIG_PROPERTIES_PROP = "felix.config.properties";
+ /**
+ * The default name used for the configuration properties file.
+ **/
+ public static final String CONFIG_PROPERTIES_FILE_VALUE = "config.properties";
+
+ private static Felix m_felix = null;
+
+ /**
+ * <p>
+ * This method performs the main task of constructing an framework instance
+ * and starting its execution. The following functions are performed
+ * when invoked:
+ * </p>
+ * <ol>
+ * <li><i><b>Read the system properties file.<b></i> This is a file
+ * containing properties to be pushed into <tt>System.setProperty()</tt>
+ * before starting the framework. This mechanism is mainly shorthand
+ * for people starting the framework from the command line to avoid having
+ * to specify a bunch of <tt>-D</tt> system property definitions.
+ * The only properties defined in this file that will impact the framework's
+ * behavior are the those concerning setting HTTP proxies, such as
+ * <tt>http.proxyHost</tt>, <tt>http.proxyPort</tt>, and
+ * <tt>http.proxyAuth</tt>.
+ * </li>
+ * <li><i><b>Perform system property variable substitution on system
+ * properties.</b></i> Any system properties in the system property
+ * file whose value adheres to <tt>${<system-prop-name>}</tt>
+ * syntax will have their value substituted with the appropriate
+ * system property value.
+ * </li>
+ * <li><i><b>Read the framework's configuration property file.</b></i> This is
+ * a file containing properties used to configure the framework
+ * instance and to pass configuration information into
+ * bundles installed into the framework instance. The configuration
+ * property file is called <tt>config.properties</tt> by default
+ * and is located in the same directory as the <tt>felix.jar</tt>
+ * file, which is typically in the <tt>lib/</tt> directory of the
+ * Felix installation directory. It is possible to use a different
+ * location for the property file by specifying the desired URL
+ * using the <tt>felix.config.properties</tt> system property;
+ * this should be set using the <tt>-D</tt> syntax when executing
+ * the JVM. Refer to the
+ * <a href="Felix.html#start(org.apache.osgi.framework.util.MutablePropertyResolver, org.apache.osgi.framework.util.MutablePropertyResolver, java.util.List)">
+ * <tt>Felix.start()</tt></a> method documentation for more
+ * information on the framework configuration options.
+ * </li>
+ * <li><i><b>Perform system property variable substitution on configuration
+ * properties.</b></i> Any configuration properties whose value adheres to
+ * <tt>${<system-prop-name>}</tt> syntax will have their value
+ * substituted with the appropriate system property value.
+ * </li>
+ * <li><i><b>Ensure the default bundle cache has sufficient information to
+ * initialize.</b></i> The default implementation of the bundle cache
+ * requires either a profile name or a profile directory in order to
+ * start. The configuration properties are checked for at least one
+ * of the <tt>felix.cache.profile</tt> or <tt>felix.cache.profiledir</tt>
+ * properties. If neither is found, the user is asked to supply a profile
+ * name that is added to the configuration property set. See the
+ * <a href="cache/DefaultBundleCache.html"><tt>DefaultBundleCache</tt></a>
+ * documentation for more details its configuration options.
+ * </li>
+ * <li><i><b>Creates and starts a framework instance.</b></i> A simple
+ * <a href="util/MutablePropertyResolver.html"><tt>MutablePropertyResolver</tt></a>
+ * is created for the configuration property file and is passed
+ * into the framework when it is started.
+ * </li>
+ * </ol>
+ * <p>
+ * It should be noted that simply starting an instance of the framework is not enough
+ * to create an interactive session with it. It is necessary to install
+ * and start bundles that provide an interactive shell; this is generally
+ * done by specifying an "auto-start" property in the framework configuration
+ * property file. If no interactive shell bundles are installed or if
+ * the configuration property file cannot be found, the framework will appear to
+ * be hung or deadlocked. This is not the case, it is executing correctly,
+ * there is just no way to interact with it. Refer to the
+ * <a href="Felix.html#start(org.apache.osgi.framework.util.MutablePropertyResolver, org.apache.osgi.framework.util.MutablePropertyResolver, java.util.List)">
+ * <tt>Felix.start()</tt></a> method documentation for more information on
+ * framework configuration options.
+ * </p>
+ * @param argv An array of arguments, all of which are ignored.
+ * @throws Exception If an error occurs.
+ **/
+ public static void main(String[] argv) throws Exception
+ {
+ // Load system properties.
+ Main.loadSystemProperties();
+
+ // Read configuration properties.
+ Properties configProps = Main.readConfigProperties();
+
+ // See if the profile name property was specified.
+ String profileName = configProps.getProperty(DefaultBundleCache.CACHE_PROFILE_PROP);
+
+ // See if the profile directory property was specified.
+ String profileDirName = configProps.getProperty(DefaultBundleCache.CACHE_PROFILE_DIR_PROP);
+
+ // Print welcome banner.
+ System.out.println("\nWelcome to Felix.");
+ System.out.println("=================\n");
+
+ // If no profile or profile directory is specified in the
+ // properties, then ask for a profile name.
+ if ((profileName == null) && (profileDirName == null))
+ {
+ System.out.print("Enter profile name: ");
+ BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+ try
+ {
+ profileName = in.readLine();
+ }
+ catch (IOException ex)
+ {
+ System.err.println("Could not read input.");
+ System.exit(-1);
+ }
+ System.out.println("");
+ if (profileName.length() != 0)
+ {
+ configProps.setProperty(DefaultBundleCache.CACHE_PROFILE_PROP, profileName);
+ }
+ }
+
+ // A profile directory or name must be specified.
+ if ((profileDirName == null) && (profileName.length() == 0))
+ {
+ System.err.println("You must specify a profile name or directory.");
+ System.exit(-1);
+ }
+
+ try
+ {
+ // Now create an instance of the framework.
+ m_felix = new Felix();
+ m_felix.start(
+ new MutablePropertyResolverImpl(new CaseInsensitiveMap(configProps)),
+ null);
+ }
+ catch (Exception ex)
+ {
+ System.err.println("Could not create framework: " + ex);
+ ex.printStackTrace();
+ System.exit(-1);
+ }
+ }
+
+ /**
+ * <p>
+ * Loads the properties in the system property file associated with the
+ * framework installation into <tt>System.setProperty()</tt>. These properties
+ * are not directly used by the framework in anyway. By default, the system property
+ * file is located in the same directory as the <tt>felix.jar</tt> file and
+ * is called "<tt>system.properties</tt>". This may be changed by setting the
+ * "<tt>felix.system.properties</tt>" system property to an
+ * arbitrary URL.
+ * </p>
+ **/
+ public static void loadSystemProperties()
+ {
+ // The system properties file is either specified by a system
+ // property or it is in the same directory as the Felix JAR file.
+ // Try to load it from one of these places.
+
+ // See if the property URL was specified as a property.
+ URL propURL = null;
+ String custom = System.getProperty(SYSTEM_PROPERTIES_PROP);
+ if (custom != null)
+ {
+ try
+ {
+ propURL = new URL(custom);
+ }
+ catch (MalformedURLException ex)
+ {
+ System.err.print("Main: " + ex);
+ return;
+ }
+ }
+ else
+ {
+ // Determine where felix.jar is located by looking at the
+ // system class path.
+ String jarLoc = null;
+ String classpath = System.getProperty("java.class.path");
+ int index = classpath.toLowerCase().indexOf("felix.jar");
+ int start = classpath.lastIndexOf(File.pathSeparator, index) + 1;
+ if (index > start)
+ {
+ jarLoc = classpath.substring(start, index);
+ if (jarLoc.length() == 0)
+ {
+ jarLoc = ".";
+ }
+ }
+ else
+ {
+ // Can't figure it out so use the current directory as default.
+ jarLoc = System.getProperty("user.dir");
+ }
+
+ try
+ {
+ propURL = new File(jarLoc, SYSTEM_PROPERTIES_FILE_VALUE).toURL();
+ }
+ catch (MalformedURLException ex)
+ {
+ System.err.print("Main: " + ex);
+ return;
+ }
+ }
+
+ // Read the properties file.
+ Properties props = new Properties();
+ InputStream is = null;
+ try
+ {
+ is = propURL.openConnection().getInputStream();
+ props.load(is);
+ is.close();
+ }
+ catch (FileNotFoundException ex)
+ {
+ // Ignore file not found.
+ }
+ catch (Exception ex)
+ {
+ System.err.println(
+ "Main: Error loading system properties from " + propURL);
+ System.err.println("Main: " + ex);
+ try
+ {
+ if (is != null) is.close();
+ }
+ catch (IOException ex2)
+ {
+ // Nothing we can do.
+ }
+ return;
+ }
+
+ // Perform variable substitution for system properties.
+ for (Enumeration e = props.propertyNames(); e.hasMoreElements(); )
+ {
+ String name = (String) e.nextElement();
+ System.setProperty(name, substVars((String) props.getProperty(name)));
+ }
+ }
+
+ /**
+ * <p>
+ * Reads the configuration properties in the configuration property
+ * file associated with the framework installation; these properties are
+ * only accessible to the framework and are intended for configuration
+ * purposes. By default, the configuration property file is located in
+ * the same directory as the <tt>felix.jar</tt> file and is called
+ * "<tt>config.properties</tt>". This may be changed by setting the
+ * "<tt>felix.config.properties</tt>" system property to an
+ * arbitrary URL.
+ * </p>
+ * @return A <tt>Properties</tt> instance or <tt>null</tt> if there was an error.
+ **/
+ public static Properties readConfigProperties()
+ {
+ // The config properties file is either specified by a system
+ // property or it is in the same directory as the Felix JAR file.
+ // Try to load it from one of these places.
+
+ // See if the property URL was specified as a property.
+ URL propURL = null;
+ String custom = System.getProperty(CONFIG_PROPERTIES_PROP);
+ if (custom != null)
+ {
+ try
+ {
+ propURL = new URL(custom);
+ }
+ catch (MalformedURLException ex)
+ {
+ System.err.print("Main: " + ex);
+ return null;
+ }
+ }
+ else
+ {
+ // Determine where felix.jar is located by looking at the
+ // system class path.
+ String jarLoc = null;
+ String classpath = System.getProperty("java.class.path");
+ int index = classpath.toLowerCase().indexOf("felix.jar");
+ int start = classpath.lastIndexOf(File.pathSeparator, index) + 1;
+ if (index > start)
+ {
+ jarLoc = classpath.substring(start, index);
+ if (jarLoc.length() == 0)
+ {
+ jarLoc = ".";
+ }
+ }
+ else
+ {
+ // Can't figure it out so use the current directory as default.
+ jarLoc = System.getProperty("user.dir");
+ }
+
+ try
+ {
+ propURL = new File(jarLoc, CONFIG_PROPERTIES_FILE_VALUE).toURL();
+ }
+ catch (MalformedURLException ex)
+ {
+ System.err.print("Main: " + ex);
+ return null;
+ }
+ }
+
+ // Read the properties file.
+ Properties props = new Properties();
+ InputStream is = null;
+ try
+ {
+ is = propURL.openConnection().getInputStream();
+ props.load(is);
+ is.close();
+ }
+ catch (FileNotFoundException ex)
+ {
+ // Ignore file not found.
+ }
+ catch (Exception ex)
+ {
+ System.err.println(
+ "Error loading config properties from " + propURL);
+ System.err.println("Main: " + ex);
+ try
+ {
+ if (is != null) is.close();
+ }
+ catch (IOException ex2)
+ {
+ // Nothing we can do.
+ }
+ return null;
+ }
+
+ // Perform variable substitution for system properties.
+ for (Enumeration e = props.propertyNames(); e.hasMoreElements(); )
+ {
+ String name = (String) e.nextElement();
+ props.setProperty(name, substVars((String) props.getProperty(name)));
+ }
+
+ return props;
+ }
+
+ private static final String DELIM_START = "${";
+ private static final char DELIM_STOP = '}';
+ private static final int DELIM_START_LEN = 2;
+ private static final int DELIM_STOP_LEN = 1;
+
+ /**
+ * <p>
+ * This method performs system property variable substitution on the
+ * specified string value. If the specified string contains the
+ * syntax <tt>${<system-prop-name>}</tt>, then the corresponding
+ * system property value is substituted for the marker.
+ * </p>
+ * @param val The string on which to perform system property substitution.
+ * @return The value of the specified string after system property substitution.
+ * @throws IllegalArgumentException If there was a syntax error in the
+ * system property variable marker syntax.
+ **/
+ public static String substVars(String val)
+ throws IllegalArgumentException
+ {
+ StringBuffer sbuf = new StringBuffer();
+
+ if (val == null)
+ {
+ return val;
+ }
+
+ int i = 0;
+ int j, k;
+
+ while (true)
+ {
+ j = val.indexOf(DELIM_START, i);
+ if (j == -1)
+ {
+ if (i == 0)
+ {
+ return val;
+ }
+ else
+ {
+ sbuf.append(val.substring(i, val.length()));
+ return sbuf.toString();
+ }
+ }
+ else
+ {
+ sbuf.append(val.substring(i, j));
+ k = val.indexOf(DELIM_STOP, j);
+ if (k == -1)
+ {
+ throw new IllegalArgumentException(
+ '"' + val +
+ "\" has no closing brace. Opening brace at position "
+ + j + '.');
+ }
+ else
+ {
+ j += DELIM_START_LEN;
+ String key = val.substring(j, k);
+ // Try system properties.
+ String replacement = System.getProperty(key, null);
+ if (replacement != null)
+ {
+ sbuf.append(replacement);
+ }
+ i = k + DELIM_STOP_LEN;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/OSGiLibrarySource.java b/src/org/apache/osgi/framework/OSGiLibrarySource.java
new file mode 100644
index 0000000..9196be3
--- /dev/null
+++ b/src/org/apache/osgi/framework/OSGiLibrarySource.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import org.apache.osgi.framework.cache.BundleCache;
+import org.apache.osgi.framework.util.LibraryInfo;
+import org.apache.osgi.moduleloader.LibrarySource;
+import org.osgi.framework.Constants;
+
+public class OSGiLibrarySource implements LibrarySource
+{
+ private LogWrapper m_logger = null;
+ private boolean m_opened = false;
+ private BundleCache m_cache = null;
+ private long m_bundleId = -1;
+ private int m_revision = -1;
+ private String m_os = null;
+ private String m_processor = null;
+ private LibraryInfo[] m_libraries = null;
+
+ public OSGiLibrarySource(
+ LogWrapper logger, BundleCache cache, long bundleId, int revision,
+ String os, String processor, LibraryInfo[] libraries)
+ {
+ m_logger = logger;
+ m_cache = cache;
+ m_bundleId = bundleId;
+ m_revision = revision;
+ m_os = normalizePropertyValue(Constants.FRAMEWORK_OS_NAME, os);
+ m_processor = normalizePropertyValue(Constants.FRAMEWORK_PROCESSOR, processor);
+ m_libraries = libraries;
+ }
+
+ public void open()
+ {
+ m_opened = true;
+ }
+
+ public void close()
+ {
+ m_opened = false;
+ }
+
+ public String getPath(String name) throws IllegalStateException
+ {
+ if (!m_opened)
+ {
+ throw new IllegalStateException("OSGiLibrarySource is not open");
+ }
+
+ if (m_libraries != null)
+ {
+ String libname = System.mapLibraryName(name);
+
+ // Check to see if we have a matching library.
+ // TODO: This "matching" algorithm does not fully
+ // match the spec and should be improved.
+ LibraryInfo library = null;
+ for (int i = 0; (library == null) && (i < m_libraries.length); i++)
+ {
+ boolean osOkay = checkOS(m_libraries[i].getOSNames());
+ boolean procOkay = checkProcessor(m_libraries[i].getProcessors());
+ if (m_libraries[i].getName().endsWith(libname) && osOkay && procOkay)
+ {
+ library = m_libraries[i];
+ }
+ }
+
+ if (library != null)
+ {
+ try {
+ return m_cache.getArchive(m_bundleId)
+ .findLibrary(m_revision, library.getName());
+ } catch (Exception ex) {
+ m_logger.log(LogWrapper.LOG_ERROR, "OSGiLibrarySource: Error finding library.", ex);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private boolean checkOS(String[] osnames)
+ {
+ for (int i = 0; (osnames != null) && (i < osnames.length); i++)
+ {
+ String osname =
+ normalizePropertyValue(Constants.FRAMEWORK_OS_NAME, osnames[i]);
+ if (m_os.equals(osname))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkProcessor(String[] processors)
+ {
+ for (int i = 0; (processors != null) && (i < processors.length); i++)
+ {
+ String processor =
+ normalizePropertyValue(Constants.FRAMEWORK_PROCESSOR, processors[i]);
+ if (m_processor.equals(processor))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This is simply a hack to try to create some standardized
+ * property values, since there seems to be many possible
+ * values for each JVM implementation. Currently, this
+ * focuses on Windows and Linux and will certainly need
+ * to be changed in the future or at least edited.
+ **/
+ public static String normalizePropertyValue(String prop, String value)
+ {
+ prop = prop.toLowerCase();
+ value = value.toLowerCase();
+
+ if (prop.equals(Constants.FRAMEWORK_OS_NAME))
+ {
+ if (value.startsWith("linux"))
+ {
+ return "linux";
+ }
+ else if (value.startsWith("win"))
+ {
+ String os = "win";
+ if (value.indexOf("95") >= 0)
+ {
+ os = "win95";
+ }
+ else if (value.indexOf("98") >= 0)
+ {
+ os = "win98";
+ }
+ else if (value.indexOf("NT") >= 0)
+ {
+ os = "winnt";
+ }
+ else if (value.indexOf("2000") >= 0)
+ {
+ os = "win2000";
+ }
+ else if (value.indexOf("xp") >= 0)
+ {
+ os = "winxp";
+ }
+ return os;
+ }
+ }
+ else if (prop.equals(Constants.FRAMEWORK_PROCESSOR))
+ {
+ if (value.endsWith("86"))
+ {
+ return "x86";
+ }
+ }
+
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/OSGiURLPolicy.java b/src/org/apache/osgi/framework/OSGiURLPolicy.java
new file mode 100644
index 0000000..b2d50c3
--- /dev/null
+++ b/src/org/apache/osgi/framework/OSGiURLPolicy.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+
+import org.apache.osgi.framework.util.FelixConstants;
+import org.apache.osgi.moduleloader.*;
+
+public class OSGiURLPolicy implements URLPolicy
+{
+ private Felix m_felix = null;
+ private BundleURLStreamHandler m_handler = null;
+ private FakeURLStreamHandler m_fakeHandler = null;
+
+ public OSGiURLPolicy(Felix felix)
+ {
+ m_felix = felix;
+ }
+
+ public URL createCodeSourceURL(ModuleManager mgr, Module module)
+ {
+ URL url = null;
+/*
+ BundleImpl bundle = null;
+ try
+ {
+ bundle = (BundleImpl)
+ m_felix.getBundle(BundleInfo.getBundleIdFromModuleId(module.getId()));
+ if (bundle != null)
+ {
+ url = new URL(bundle.getInfo().getLocation());
+ }
+ }
+ catch (NumberFormatException ex)
+ {
+ url = null;
+ }
+ catch (MalformedURLException ex)
+ {
+ if (m_fakeHandler == null)
+ {
+ m_fakeHandler = new FakeURLStreamHandler();
+ }
+ try
+ {
+ url = new URL(null,
+ FelixConstants.FAKE_URL_PROTOCOL_VALUE
+ + "//" + bundle.getLocation(), m_fakeHandler);
+ }
+ catch (Exception ex2)
+ {
+ url = null;
+ }
+ }
+*/
+ return url;
+ }
+
+ public URL createResourceURL(ModuleManager mgr, Module module, int rsIdx, String name)
+ {
+ if (m_handler == null)
+ {
+ m_handler = new BundleURLStreamHandler(mgr);
+ }
+
+ // Add a slash if there is one already, otherwise
+ // the is no slash separating the host from the file
+ // in the resulting URL.
+ if (!name.startsWith("/"))
+ {
+ name = "/" + name;
+ }
+
+ try
+ {
+ if (System.getSecurityManager() != null)
+ {
+ return (URL) AccessController.doPrivileged(
+ new CreateURLPrivileged(module.getId(), rsIdx, name));
+ }
+ else
+ {
+ return new URL(FelixConstants.BUNDLE_URL_PROTOCOL,
+ module.getId(), -1, "/" + rsIdx + name, m_handler);
+ }
+ }
+ catch (Exception ex)
+ {
+ System.err.println("OSGiURLPolicy: " + ex);
+ return null;
+ }
+ }
+
+ /**
+ * This simple class is used to perform the privileged action of
+ * creating a URL using the "bundle:" protocol stream handler.
+ **/
+ private class CreateURLPrivileged implements PrivilegedExceptionAction
+ {
+ private String m_id = null;
+ private int m_rsIdx = 0;
+ private String m_name = null;
+
+ public CreateURLPrivileged(String id, int rsIdx, String name)
+ {
+ m_id = id;
+ m_rsIdx = rsIdx;
+ m_name = name;
+ }
+
+ public Object run() throws Exception
+ {
+ return new URL("bundle", m_id, -1, "/" + m_rsIdx + m_name, m_handler);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/PackageAdminActivator.java b/src/org/apache/osgi/framework/PackageAdminActivator.java
new file mode 100644
index 0000000..4e07315
--- /dev/null
+++ b/src/org/apache/osgi/framework/PackageAdminActivator.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import org.osgi.framework.*;
+
+class PackageAdminActivator implements BundleActivator
+{
+ private Felix m_felix = null;
+ private ServiceRegistration m_reg = null;
+
+ public PackageAdminActivator(Felix felix)
+ {
+ m_felix = felix;
+ }
+
+ public void start(BundleContext context) throws Exception
+ {
+ m_reg = context.registerService(
+ org.osgi.service.packageadmin.PackageAdmin.class.getName(),
+ new PackageAdminImpl(m_felix),
+ null);
+ }
+
+ public void stop(BundleContext context) throws Exception
+ {
+ m_reg.unregister();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/PackageAdminImpl.java b/src/org/apache/osgi/framework/PackageAdminImpl.java
new file mode 100644
index 0000000..5a029c0
--- /dev/null
+++ b/src/org/apache/osgi/framework/PackageAdminImpl.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.packageadmin.*;
+
+class PackageAdminImpl implements PackageAdmin, Runnable
+{
+ private Felix m_felix = null;
+ private Bundle[][] m_reqBundles = null;
+
+ public PackageAdminImpl(Felix felix)
+ {
+ m_felix = felix;
+
+ // Start a thread to perform asynchronous package refreshes.
+ Thread t = new Thread(this, "FelixPackageAdmin");
+ t.setDaemon(true);
+ t.start();
+ }
+
+ /**
+ * Returns the exported package associated with the specified
+ * package name.
+ *
+ * @param name the name of the exported package to find.
+ * @return the exported package or null if no matching package was found.
+ **/
+ public ExportedPackage getExportedPackage(String name)
+ {
+ return m_felix.getExportedPackage(name);
+ }
+
+ /**
+ * Returns the packages exported by the specified bundle.
+ *
+ * @param bundle the bundle whose exported packages are to be returned.
+ * @return an array of packages exported by the bundle or null if the
+ * bundle does not export any packages.
+ **/
+ public ExportedPackage[] getExportedPackages(Bundle b)
+ {
+ return m_felix.getExportedPackages(b);
+ }
+
+ /**
+ * The OSGi specification states that refreshing packages is
+ * asynchronous; this method simply notifies the package admin
+ * thread to do a refresh.
+ * @param bundles array of bundles to refresh or <tt>null</tt> to refresh
+ * any bundles in need of refreshing.
+ **/
+ public synchronized void refreshPackages(Bundle[] bundles)
+ throws SecurityException
+ {
+ // Save our request parameters and notify all.
+ if (m_reqBundles == null)
+ {
+ m_reqBundles = new Bundle[][] { bundles };
+ }
+ else
+ {
+ Bundle[][] newReqBundles = new Bundle[m_reqBundles.length + 1][];
+ System.arraycopy(m_reqBundles, 0,
+ newReqBundles, 0, m_reqBundles.length);
+ newReqBundles[m_reqBundles.length] = bundles;
+ m_reqBundles = newReqBundles;
+ }
+ notifyAll();
+ }
+
+ /**
+ * The OSGi specification states that package refreshes happen
+ * asynchronously; this is the run() method for the package
+ * refreshing thread.
+ **/
+ public void run()
+ {
+ // This thread loops forever, thus it should
+ // be a daemon thread.
+ Bundle[] bundles = null;
+ while (true)
+ {
+ synchronized (this)
+ {
+ // Wait for a refresh request.
+ while (m_reqBundles == null)
+ {
+ try
+ {
+ wait();
+ }
+ catch (InterruptedException ex)
+ {
+ }
+ }
+
+ // Get the bundles parameter for the current
+ // refresh request.
+ if (m_reqBundles != null)
+ {
+ bundles = m_reqBundles[0];
+ }
+ }
+
+ // Perform refresh.
+ m_felix.refreshPackages(bundles);
+
+ // Remove the first request since it is now completed.
+ synchronized (this)
+ {
+ if (m_reqBundles.length == 1)
+ {
+ m_reqBundles = null;
+ }
+ else
+ {
+ Bundle[][] newReqBundles = new Bundle[m_reqBundles.length - 1][];
+ System.arraycopy(m_reqBundles, 1,
+ newReqBundles, 0, m_reqBundles.length - 1);
+ m_reqBundles = newReqBundles;
+ }
+ }
+ }
+ }
+
+ public ExportedPackage[] getExportedPackages(String name)
+ {
+ // TODO: Implement PackageAdmin.getExportedPackages()
+ return null;
+ }
+
+ public boolean resolveBundles(Bundle[] bundles)
+ {
+ // TODO: Implement PackageAdmin.resolveBundles()
+ return false;
+ }
+
+ public RequiredBundle[] getRequiredBundles(String symbolicName)
+ {
+ // TODO: Implement PackageAdmin.getRequiredBundles()
+ return null;
+ }
+
+ public Bundle[] getBundles(String symbolicName, String versionRange)
+ {
+ // TODO: Implement PackageAdmin.getBundles()
+ return null;
+ }
+
+ public Bundle[] getFragments(Bundle bundle)
+ {
+ // TODO: Implement PackageAdmin.getFragments()
+ return null;
+ }
+
+ public Bundle[] getHosts(Bundle bundle)
+ {
+ // TODO: Implement PackageAdmin.getHosts()
+ return null;
+ }
+
+ public Bundle getBundle(Class clazz)
+ {
+ // TODO: Implement PackageAdmin.getBundle()
+ return null;
+ }
+
+ public int getBundleType(Bundle bundle)
+ {
+ // TODO: Implement PackageAdmin.getBundleType()
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/ServiceReferenceImpl.java b/src/org/apache/osgi/framework/ServiceReferenceImpl.java
new file mode 100644
index 0000000..8cf5ae4
--- /dev/null
+++ b/src/org/apache/osgi/framework/ServiceReferenceImpl.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import org.apache.osgi.framework.searchpolicy.R4SearchPolicy;
+import org.apache.osgi.framework.searchpolicy.R4Wire;
+import org.apache.osgi.framework.util.FelixConstants;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+class ServiceReferenceImpl implements ServiceReference
+{
+ private ServiceRegistrationImpl m_registration = null;
+ private Bundle m_bundle = null;
+
+ public ServiceReferenceImpl(ServiceRegistrationImpl reg, Bundle bundle)
+ {
+ m_registration = reg;
+ m_bundle = bundle;
+ }
+
+ protected ServiceRegistrationImpl getServiceRegistration()
+ {
+ return m_registration;
+ }
+
+ public Object getProperty(String s)
+ {
+ return m_registration.getProperty(s);
+ }
+
+ public String[] getPropertyKeys()
+ {
+ return m_registration.getPropertyKeys();
+ }
+
+ public Bundle getBundle()
+ {
+ return m_bundle;
+ }
+
+ public Bundle[] getUsingBundles()
+ {
+ return m_registration.getUsingBundles();
+ }
+
+ public boolean equals(Object obj)
+ {
+ try
+ {
+ ServiceReferenceImpl ref = (ServiceReferenceImpl) obj;
+ return ref.m_registration == m_registration;
+ }
+ catch (ClassCastException ex)
+ {
+ // Ignore and return false.
+ }
+ catch (NullPointerException ex)
+ {
+ // Ignore and return false.
+ }
+
+ return false;
+ }
+
+ public int hashCode()
+ {
+ if (m_registration.getReference() != null)
+ {
+ if (m_registration.getReference() != this)
+ {
+ return m_registration.getReference().hashCode();
+ }
+ return super.hashCode();
+ }
+ return 0;
+ }
+
+ public String toString()
+ {
+ String[] ocs = (String[]) getProperty("objectClass");
+ String oc = "[";
+ for(int i = 0; i < ocs.length; i++)
+ {
+ oc = oc + ocs[i];
+ if (i < ocs.length - 1)
+ oc = oc + ", ";
+ }
+ oc = oc + "]";
+ return oc;
+ }
+
+ public boolean isAssignableTo(Bundle requester, String className)
+ {
+ // Always return true if the requester is the same as the provider.
+ if (requester == m_bundle)
+ {
+ return true;
+ }
+
+ // Boolean flag.
+ boolean allow = true;
+ // Get the package.
+ String pkgName =
+ org.apache.osgi.moduleloader.Util.getClassPackage(className);
+ // Get package wiring from service provider and requester.
+ R4Wire requesterWire = R4SearchPolicy.getWire(
+ ((BundleImpl) requester).getInfo().getCurrentModule(), pkgName);
+ R4Wire providerWire = R4SearchPolicy.getWire(
+ ((BundleImpl) m_bundle).getInfo().getCurrentModule(), pkgName);
+
+ // There are three situations that may occur here:
+ // 1. The requester does not have a wire for the package.
+ // 2. The provider does not have a wire for the package.
+ // 3. Both have a wire for the package.
+ // For case 1, we do not filter the service reference since we
+ // assume that the bundle is using reflection or that it won't
+ // use that class at all since it does not import it. For
+ // case 2, we have to try to load the class from the class
+ // loader of the service object and then compare the class
+ // loaders to determine if we should filter the service
+ // refernce. In case 3, we simply compare the exporting
+ // modules from the package wiring to determine if we need
+ // to filter the service reference.
+
+ // Case 1: Always include service reference.
+ if (requesterWire == null)
+ {
+ // This is an intentional no-op.
+ }
+ // Case 2: Only include service reference if the service
+ // object uses the same class as the requester.
+ else if (providerWire == null)
+ {
+ try
+ {
+ // Load the class from the requesting bundle.
+ Class requestClass =
+ ((BundleImpl) requester).getInfo().getCurrentModule().getClassLoader()
+ .loadClass(className);
+ // Get the service registration and ask it to check
+ // if the service object is assignable to the requesting
+ // bundle's class.
+ allow = getServiceRegistration().isClassAccessible(requestClass);
+ }
+ catch (Exception ex)
+ {
+ // This should not happen, filter to be safe.
+ allow = false;
+ }
+ }
+ // Case 3: Include service reference if the wires have the
+ // same source module.
+ else
+ {
+ allow = providerWire.m_module.equals(requesterWire.m_module);
+ }
+
+ return allow;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/ServiceRegistrationImpl.java b/src/org/apache/osgi/framework/ServiceRegistrationImpl.java
new file mode 100644
index 0000000..9ba8b28
--- /dev/null
+++ b/src/org/apache/osgi/framework/ServiceRegistrationImpl.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+import java.util.*;
+
+import org.apache.osgi.framework.util.CaseInsensitiveMap;
+import org.osgi.framework.*;
+
+class ServiceRegistrationImpl implements ServiceRegistration
+{
+ // Service registry.
+ private ServiceRegistry m_registry = null;
+ // Bundle implementing the service.
+ private Bundle m_bundle = null;
+ // Interfaces associated with the service object.
+ private String[] m_classes = null;
+ // Service Id associated with the service object.
+ private Long m_serviceId = null;
+ // Service object.
+ private Object m_svcObj = null;
+ // Service factory interface.
+ private ServiceFactory m_factory = null;
+ // Associated property dictionary.
+ private Map m_propMap = null;
+ // Re-usable service reference.
+ private ServiceReferenceImpl m_ref = null;
+
+ public ServiceRegistrationImpl(
+ ServiceRegistry registry, Bundle bundle,
+ String[] classes, Long serviceId,
+ Object svcObj, Dictionary dict)
+ {
+ m_registry = registry;
+ m_bundle = bundle;
+ m_classes = classes;
+ m_serviceId = serviceId;
+ m_svcObj = svcObj;
+ m_factory = (m_svcObj instanceof ServiceFactory)
+ ? (ServiceFactory) m_svcObj : null;
+
+ initializeProperties(dict);
+
+ // This reference is the "standard" reference for this
+ // service and will always be returned by getReference().
+ // Since all reference to this service are supposed to
+ // be equal, we use the hashcode of this reference for
+ // a references to this service in ServiceReference.
+ m_ref = new ServiceReferenceImpl(this, m_bundle);
+ }
+
+ protected boolean isValid()
+ {
+ return (m_svcObj != null);
+ }
+
+ public ServiceReference getReference()
+ {
+ return m_ref;
+ }
+
+ public void setProperties(Dictionary dict)
+ {
+ // Make sure registration is valid.
+ if (!isValid())
+ {
+ throw new IllegalStateException(
+ "The service registration is no longer valid.");
+ }
+ // Set the properties.
+ initializeProperties(dict);
+ // Tell registry about it.
+ m_registry.servicePropertiesModified(this);
+ }
+
+ public void unregister()
+ {
+ m_registry.unregisterService(m_bundle, this);
+ m_svcObj = null;
+ m_factory = null;
+ }
+
+ //
+ // Utility methods.
+ //
+
+ /**
+ * This method determines if the class loader of the service object
+ * has access to the specified class.
+ * @param clazz the class to test for reachability.
+ * @return <tt>true</tt> if the specified class is reachable from the
+ * service object's class loader, <tt>false</tt> otherwise.
+ **/
+ protected boolean isClassAccessible(Class clazz)
+ {
+ ClassLoader loader = (m_factory != null)
+ ? m_factory.getClass().getClassLoader()
+ : m_svcObj.getClass().getClassLoader();
+ try
+ {
+ Class target = loader.loadClass(clazz.getName());
+ return (target.getClassLoader() == clazz.getClassLoader());
+ }
+ catch (Exception ex)
+ {
+ }
+ return false;
+ }
+
+ protected Object getProperty(String key)
+ {
+ return m_propMap.get(key);
+ }
+
+ private transient ArrayList m_list = new ArrayList();
+
+ protected String[] getPropertyKeys()
+ {
+ synchronized (m_list)
+ {
+ m_list.clear();
+ Iterator i = m_propMap.entrySet().iterator();
+ while (i.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+ m_list.add(entry.getKey());
+ }
+ return (String[]) m_list.toArray(new String[m_list.size()]);
+ }
+ }
+
+ protected Bundle[] getUsingBundles()
+ {
+ return m_registry.getUsingBundles(m_ref);
+ }
+
+ protected Object getService(Bundle acqBundle)
+ {
+ // If the service object is a service factory, then
+ // let it create the service object.
+ if (m_factory != null)
+ {
+ try
+ {
+ if (System.getSecurityManager() != null)
+ {
+ return AccessController.doPrivileged(
+ new ServiceFactoryPrivileged(acqBundle, null));
+ }
+ else
+ {
+ return getFactoryUnchecked(acqBundle);
+ }
+ }
+ catch (Exception ex)
+ {
+ m_registry.getLogger().log(
+ LogWrapper.LOG_ERROR, "ServiceRegistrationImpl: Error getting service.", ex);
+ return null;
+ }
+ }
+ else
+ {
+ return m_svcObj;
+ }
+ }
+
+ protected void ungetService(Bundle relBundle, Object svcObj)
+ {
+ // If the service object is a service factory, then
+ // let is release the service object.
+ if (m_factory != null)
+ {
+ try
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.doPrivileged(
+ new ServiceFactoryPrivileged(relBundle, svcObj));
+ }
+ else
+ {
+ ungetFactoryUnchecked(relBundle, svcObj);
+ }
+ }
+ catch (Exception ex)
+ {
+ m_registry.getLogger().log(
+ LogWrapper.LOG_ERROR, "ServiceRegistrationImpl: Error ungetting service.", ex);
+ }
+ }
+ }
+
+ private void initializeProperties(Dictionary dict)
+ {
+ // Create a case insensitive map.
+ if (m_propMap == null)
+ {
+ m_propMap = new CaseInsensitiveMap();
+ }
+ else
+ {
+ m_propMap.clear();
+ }
+
+ if (dict != null)
+ {
+ Enumeration keys = dict.keys();
+ while (keys.hasMoreElements())
+ {
+ Object key = keys.nextElement();
+ m_propMap.put(key, dict.get(key));
+ }
+ }
+
+ // Add the framework assigned properties.
+ m_propMap.put(Constants.OBJECTCLASS, m_classes);
+ m_propMap.put(Constants.SERVICE_ID, m_serviceId);
+ }
+
+ private Object getFactoryUnchecked(Bundle bundle)
+ {
+ return m_factory.getService(bundle, this);
+ }
+
+ private void ungetFactoryUnchecked(Bundle bundle, Object svcObj)
+ {
+ m_factory.ungetService(bundle, this, svcObj);
+ }
+
+ /**
+ * This simple class is used to ensure that when a service factory
+ * is called, that no other classes on the call stack interferes
+ * with the permissions of the factory itself.
+ **/
+ private class ServiceFactoryPrivileged implements PrivilegedExceptionAction
+ {
+ private Bundle m_bundle = null;
+ private Object m_svcObj = null;
+
+ public ServiceFactoryPrivileged(Bundle bundle, Object svcObj)
+ {
+ m_bundle = bundle;
+ m_svcObj = svcObj;
+ }
+
+ public Object run() throws Exception
+ {
+ if (m_svcObj == null)
+ {
+ return getFactoryUnchecked(m_bundle);
+ }
+ else
+ {
+ ungetFactoryUnchecked(m_bundle, m_svcObj);
+ }
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/ServiceRegistry.java b/src/org/apache/osgi/framework/ServiceRegistry.java
new file mode 100644
index 0000000..02f206b
--- /dev/null
+++ b/src/org/apache/osgi/framework/ServiceRegistry.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.util.*;
+
+import org.apache.osgi.framework.util.FelixConstants;
+import org.osgi.framework.*;
+
+public class ServiceRegistry
+{
+ private LogWrapper m_logger = null;
+ private long m_currentServiceId = 1L;
+ // Maps bundle to an array of service registrations.
+ private Map m_serviceRegsMap = new HashMap();
+ // Maps bundle to an array of usage counts.
+ private Map m_inUseMap = new HashMap();
+
+ private ServiceListener m_serviceListener = null;
+
+ public ServiceRegistry(LogWrapper logger)
+ {
+ m_logger = logger;
+ }
+
+ public synchronized ServiceReference[] getRegisteredServices(Bundle bundle)
+ {
+ ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
+ if (regs != null)
+ {
+ ServiceReference[] refs = new ServiceReference[regs.length];
+ for (int i = 0; i < refs.length; i++)
+ {
+ refs[i] = regs[i].getReference();
+ }
+ return refs;
+ }
+ return null;
+ }
+
+ public ServiceRegistration registerService(
+ Bundle bundle, String[] classNames, Object svcObj, Dictionary dict)
+ {
+ ServiceRegistration reg = null;
+
+ synchronized (this)
+ {
+ // Create the service registration.
+ reg = new ServiceRegistrationImpl(
+ this, bundle, classNames, new Long(m_currentServiceId++), svcObj, dict);
+ // Get the bundles current registered services.
+ ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
+ m_serviceRegsMap.put(bundle, addServiceRegistration(regs, reg));
+ }
+ fireServiceChanged(new ServiceEvent(ServiceEvent.REGISTERED, reg.getReference()));
+ return reg;
+ }
+
+ public void unregisterService(Bundle bundle, ServiceRegistration reg)
+ {
+ synchronized (this)
+ {
+ ServiceRegistration[] regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
+ m_serviceRegsMap.put(bundle, removeServiceRegistration(regs, reg));
+ }
+ fireServiceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, reg.getReference()));
+ }
+
+ public void unregisterServices(Bundle bundle)
+ {
+ // Simply remove all service registrations for the bundle.
+ ServiceRegistration[] regs = null;
+ synchronized (this)
+ {
+ regs = (ServiceRegistration[]) m_serviceRegsMap.get(bundle);
+ m_serviceRegsMap.remove(bundle);
+ }
+
+ // Fire all events outside of synchronized block.
+ for (int i = 0; (regs != null) && (i < regs.length); i++)
+ {
+ fireServiceChanged(
+ new ServiceEvent(ServiceEvent.UNREGISTERING, regs[i].getReference()));
+ }
+ }
+
+ public synchronized List getServiceReferences(String className, Filter filter)
+ throws InvalidSyntaxException
+ {
+ // Create a filtered list of service references.
+ List list = new ArrayList();
+
+ // Iterator over all service registrations.
+ for (Iterator i = m_serviceRegsMap.entrySet().iterator(); i.hasNext(); )
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+ ServiceRegistration[] regs = (ServiceRegistration[]) entry.getValue();
+
+ for (int regIdx = 0;
+ (regs != null) && (regIdx < regs.length);
+ regIdx++)
+ {
+ // Determine if the registered services matches
+ // the search criteria.
+ boolean matched = false;
+
+ // If className is null, then look at filter only.
+ if ((className == null) &&
+ ((filter == null) || filter.match(regs[regIdx].getReference())))
+ {
+ matched = true;
+ }
+ // If className is not null, then first match the
+ // objectClass property before looking at the
+ // filter.
+ else if (className != null)
+ {
+ String[] objectClass = (String[])
+ ((ServiceRegistrationImpl) regs[regIdx]).getProperty(FelixConstants.OBJECTCLASS);
+ for (int classIdx = 0;
+ classIdx < objectClass.length;
+ classIdx++)
+ {
+ if (objectClass[classIdx].equals(className) &&
+ ((filter == null) || filter.match(regs[regIdx].getReference())))
+ {
+ matched = true;
+ break;
+ }
+ }
+ }
+
+ // Add reference if it was a match.
+ if (matched)
+ {
+ list.add(regs[regIdx].getReference());
+ }
+ }
+ }
+
+ return list;
+ }
+
+ public synchronized ServiceReference[] getServicesInUse(Bundle bundle)
+ {
+ UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+ if (usages != null)
+ {
+ ServiceReference[] refs = new ServiceReference[usages.length];
+ for (int i = 0; i < refs.length; i++)
+ {
+ refs[i] = usages[i].m_ref;
+ }
+ return refs;
+ }
+ return null;
+ }
+
+ public synchronized Object getService(Bundle bundle, ServiceReference ref)
+ {
+ // Get usage counts for specified bundle.
+ UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+
+ // Make sure the service registration is still valid.
+ if (!((ServiceReferenceImpl) ref).getServiceRegistration().isValid())
+ {
+ // If the service registration is not valid, then this means
+ // that the service provider unregistered the service. The spec
+ // says that calls to get an unregistered service should always
+ // return null (assumption: even if it is currently cached
+ // by the bundle). So in this case, flush the service reference
+ // from the cache and return null.
+ m_inUseMap.put(bundle, removeUsageCount(usages, ref));
+
+ // It is not necessary to unget the service object from
+ // the providing bundle, since the associated service is
+ // unregistered and hence not in the list of registered services
+ // of the providing bundle. This is precisely why the service
+ // registration was not found above in the first place.
+ return null;
+ }
+
+ // Get the service registration.
+ ServiceRegistrationImpl reg = ((ServiceReferenceImpl) ref).getServiceRegistration();
+
+ // Get the usage count, if any.
+ UsageCount usage = getUsageCount(usages, ref);
+
+ // If the service object is cached, then increase the usage
+ // count and return the cached service object.
+ Object svcObj = null;
+ if (usage != null)
+ {
+ usage.m_count++;
+ svcObj = usage.m_svcObj;
+ }
+ else
+ {
+ // Get service object from service registration.
+ svcObj = reg.getService(bundle);
+
+ // Cache the service object.
+ if (svcObj != null)
+ {
+ m_inUseMap.put(bundle, addUsageCount(usages, ref, svcObj));
+ }
+ }
+
+ return svcObj;
+ }
+
+ public synchronized boolean ungetService(Bundle bundle, ServiceReference ref)
+ {
+ // Get usage count.
+ UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+ UsageCount usage = getUsageCount(usages, ref);
+
+ // If no usage count, then return.
+ if (usage == null)
+ {
+ return false;
+ }
+
+ // Make sure the service registration is still valid.
+ if (!((ServiceReferenceImpl) ref).getServiceRegistration().isValid())
+ {
+ // If the service registration is not valid, then this means
+ // that the service provider unregistered the service. The spec
+ // says that calls to get an unregistered service should always
+ // return null (assumption: even if it is currently cached
+ // by the bundle). So in this case, flush the service reference
+ // from the cache and return null.
+ m_inUseMap.put(bundle, removeUsageCount(usages, ref));
+ return false;
+ }
+
+ // Decrement usage count.
+ usage.m_count--;
+
+ // Remove reference when usage count goes to zero
+ // and unget the service object from the exporting
+ // bundle.
+ if (usage.m_count == 0)
+ {
+ m_inUseMap.put(bundle, removeUsageCount(usages, ref));
+ ServiceRegistrationImpl reg =
+ ((ServiceReferenceImpl) ref).getServiceRegistration();
+ reg.ungetService(bundle, usage.m_svcObj);
+ usage.m_svcObj = null;
+ }
+
+ // Return true if the usage count is greater than zero.
+ return (usage.m_count > 0);
+ }
+
+
+ /**
+ * This is a utility method to release all services being
+ * used by the specified bundle.
+ * @param bundle the bundle whose services are to be released.
+ **/
+ public synchronized void ungetServices(Bundle bundle)
+ {
+ UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+ if (usages == null)
+ {
+ return;
+ }
+
+ // Remove each service object from the
+ // service cache.
+ for (int i = 0; i < usages.length; i++)
+ {
+ // Keep ungetting until all usage count is zero.
+ while (ungetService(bundle, usages[i].m_ref))
+ {
+ // Empty loop body.
+ }
+ }
+ }
+
+ public synchronized Bundle[] getUsingBundles(ServiceReference ref)
+ {
+ Bundle[] bundles = null;
+ for (Iterator iter = m_inUseMap.entrySet().iterator(); iter.hasNext(); )
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ Bundle bundle = (Bundle) entry.getKey();
+ UsageCount[] usages = (UsageCount[]) entry.getValue();
+ for (int useIdx = 0; useIdx < usages.length; useIdx++)
+ {
+ if (usages[useIdx].m_ref.equals(ref))
+ {
+ // Add the bundle to the array to be returned.
+ if (bundles == null)
+ {
+ bundles = new Bundle[] { bundle };
+ }
+ else
+ {
+ Bundle[] nbs = new Bundle[bundles.length + 1];
+ System.arraycopy(bundles, 0, nbs, 0, bundles.length);
+ nbs[bundles.length] = bundle;
+ bundles = nbs;
+ }
+ }
+ }
+ }
+ return bundles;
+ }
+
+ public void servicePropertiesModified(ServiceRegistration reg)
+ {
+ fireServiceChanged(new ServiceEvent(ServiceEvent.MODIFIED, reg.getReference()));
+ }
+
+ public LogWrapper getLogger()
+ {
+ return m_logger;
+ }
+
+ private static ServiceRegistration[] addServiceRegistration(
+ ServiceRegistration[] regs, ServiceRegistration reg)
+ {
+ if (regs == null)
+ {
+ regs = new ServiceRegistration[] { reg };
+ }
+ else
+ {
+ ServiceRegistration[] newRegs = new ServiceRegistration[regs.length + 1];
+ System.arraycopy(regs, 0, newRegs, 0, regs.length);
+ newRegs[regs.length] = reg;
+ regs = newRegs;
+ }
+ return regs;
+ }
+
+ private static ServiceRegistration[] removeServiceRegistration(
+ ServiceRegistration[] regs, ServiceRegistration reg)
+ {
+ for (int i = 0; (regs != null) && (i < regs.length); i++)
+ {
+ if (regs[i].equals(reg))
+ {
+ // If this is the only usage, then point to empty list.
+ if ((regs.length - 1) == 0)
+ {
+ regs = new ServiceRegistration[0];
+ }
+ // Otherwise, we need to do some array copying.
+ else
+ {
+ ServiceRegistration[] newRegs = new ServiceRegistration[regs.length - 1];
+ System.arraycopy(regs, 0, newRegs, 0, i);
+ if (i < newRegs.length)
+ {
+ System.arraycopy(
+ regs, i + 1, newRegs, i, newRegs.length - i);
+ }
+ regs = newRegs;
+ }
+ }
+ }
+ return regs;
+ }
+
+ public synchronized void addServiceListener(ServiceListener l)
+ {
+ m_serviceListener = ServiceListenerMulticaster.add(m_serviceListener, l);
+ }
+
+ public synchronized void removeServiceListener(ServiceListener l)
+ {
+ m_serviceListener = ServiceListenerMulticaster.remove(m_serviceListener, l);
+ }
+
+ protected void fireServiceChanged(ServiceEvent event)
+ {
+ // Grab a copy of the listener list.
+ ServiceListener listener = m_serviceListener;
+ // If not null, then dispatch event.
+ if (listener != null)
+ {
+ m_serviceListener.serviceChanged(event);
+ }
+ }
+
+ private static class ServiceListenerMulticaster implements ServiceListener
+ {
+ protected ServiceListener m_a = null, m_b = null;
+
+ protected ServiceListenerMulticaster(ServiceListener a, ServiceListener b)
+ {
+ m_a = a;
+ m_b = b;
+ }
+
+ public void serviceChanged(ServiceEvent e)
+ {
+ m_a.serviceChanged(e);
+ m_b.serviceChanged(e);
+ }
+
+ public static ServiceListener add(ServiceListener a, ServiceListener b)
+ {
+ if (a == null)
+ {
+ return b;
+ }
+ else if (b == null)
+ {
+ return a;
+ }
+ else
+ {
+ return new ServiceListenerMulticaster(a, b);
+ }
+ }
+
+ public static ServiceListener remove(ServiceListener a, ServiceListener b)
+ {
+ if ((a == null) || (a == b))
+ {
+ return null;
+ }
+ else if (a instanceof ServiceListenerMulticaster)
+ {
+ return add(
+ remove(((ServiceListenerMulticaster) a).m_a, b),
+ remove(((ServiceListenerMulticaster) a).m_b, b));
+ }
+ else
+ {
+ return a;
+ }
+ }
+ }
+
+ private static UsageCount getUsageCount(UsageCount[] usages, ServiceReference ref)
+ {
+ for (int i = 0; (usages != null) && (i < usages.length); i++)
+ {
+ if (usages[i].m_ref.equals(ref))
+ {
+ return usages[i];
+ }
+ }
+ return null;
+ }
+
+ private static UsageCount[] addUsageCount(UsageCount[] usages, ServiceReference ref, Object svcObj)
+ {
+ UsageCount usage = new UsageCount();
+ usage.m_ref = ref;
+ usage.m_svcObj = svcObj;
+ usage.m_count++;
+
+ if (usages == null)
+ {
+ usages = new UsageCount[] { usage };
+ }
+ else
+ {
+ UsageCount[] newUsages = new UsageCount[usages.length + 1];
+ System.arraycopy(usages, 0, newUsages, 0, usages.length);
+ newUsages[usages.length] = usage;
+ usages = newUsages;
+ }
+ return usages;
+ }
+
+ private static UsageCount[] removeUsageCount(UsageCount[] usages, ServiceReference ref)
+ {
+ for (int i = 0; (usages != null) && (i < usages.length); i++)
+ {
+ if (usages[i].m_ref.equals(ref))
+ {
+ // If this is the only usage, then point to empty list.
+ if ((usages.length - 1) == 0)
+ {
+ usages = new UsageCount[0];
+ }
+ // Otherwise, we need to do some array copying.
+ else
+ {
+ UsageCount[] newUsages= new UsageCount[usages.length - 1];
+ System.arraycopy(usages, 0, newUsages, 0, i);
+ if (i < newUsages.length)
+ {
+ System.arraycopy(
+ usages, i + 1, newUsages, i, newUsages.length - i);
+ }
+ usages = newUsages;
+ }
+ }
+ }
+
+ return usages;
+ }
+
+ private static class UsageCount
+ {
+ public int m_count = 0;
+ public ServiceReference m_ref = null;
+ public Object m_svcObj = null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/StartLevelActivator.java b/src/org/apache/osgi/framework/StartLevelActivator.java
new file mode 100644
index 0000000..222b55e
--- /dev/null
+++ b/src/org/apache/osgi/framework/StartLevelActivator.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import org.osgi.framework.*;
+
+class StartLevelActivator implements BundleActivator
+{
+ private Felix m_felix = null;
+ private ServiceRegistration m_reg = null;
+
+ public StartLevelActivator(Felix felix)
+ {
+ m_felix = felix;
+ }
+
+ public void start(BundleContext context) throws Exception
+ {
+ m_reg = context.registerService(
+ org.osgi.service.startlevel.StartLevel.class.getName(),
+ new StartLevelImpl(m_felix),
+ null);
+ }
+
+ public void stop(BundleContext context) throws Exception
+ {
+ m_reg.unregister();
+ }
+}
diff --git a/src/org/apache/osgi/framework/StartLevelImpl.java b/src/org/apache/osgi/framework/StartLevelImpl.java
new file mode 100644
index 0000000..831af64
--- /dev/null
+++ b/src/org/apache/osgi/framework/StartLevelImpl.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.AdminPermission;
+import org.osgi.framework.Bundle;
+import org.osgi.service.startlevel.StartLevel;
+
+/**
+ * @author rickhall
+ *
+ * To change the template for this generated type comment go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+**/
+public class StartLevelImpl implements StartLevel, Runnable
+{
+ private Felix m_felix = null;
+ private List m_requestList = null;
+ // Reusable admin permission.
+ private static AdminPermission m_adminPerm = new AdminPermission();
+
+ public StartLevelImpl(Felix felix)
+ {
+ m_felix = felix;
+ m_requestList = new ArrayList();
+
+ // Start a thread to perform asynchronous package refreshes.
+ Thread t = new Thread(this, "FelixStartLevel");
+ t.setDaemon(true);
+ t.start();
+ }
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.startlevel.StartLevel#getStartLevel()
+ **/
+ public int getStartLevel()
+ {
+ return m_felix.getStartLevel();
+ }
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.startlevel.StartLevel#setStartLevel(int)
+ **/
+ public void setStartLevel(int startlevel)
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(m_adminPerm);
+ }
+ else if (startlevel <= 0)
+ {
+ throw new IllegalArgumentException(
+ "Start level must be greater than zero.");
+ }
+ synchronized (m_requestList)
+ {
+ m_requestList.add(new Integer(startlevel));
+ m_requestList.notifyAll();
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.startlevel.StartLevel#getBundleStartLevel(org.osgi.framework.Bundle)
+ **/
+ public int getBundleStartLevel(Bundle bundle)
+ {
+ return m_felix.getBundleStartLevel(bundle);
+ }
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.startlevel.StartLevel#setBundleStartLevel(org.osgi.framework.Bundle, int)
+ **/
+ public void setBundleStartLevel(Bundle bundle, int startlevel)
+ {
+ m_felix.setBundleStartLevel(bundle, startlevel);
+ }
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.startlevel.StartLevel#getInitialBundleStartLevel()
+ **/
+ public int getInitialBundleStartLevel()
+ {
+ return m_felix.getInitialBundleStartLevel();
+ }
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.startlevel.StartLevel#setInitialBundleStartLevel(int)
+ **/
+ public void setInitialBundleStartLevel(int startlevel)
+ {
+ m_felix.setInitialBundleStartLevel(startlevel);
+ }
+
+ /* (non-Javadoc)
+ * @see org.osgi.service.startlevel.StartLevel#isBundlePersistentlyStarted(org.osgi.framework.Bundle)
+ **/
+ public boolean isBundlePersistentlyStarted(Bundle bundle)
+ {
+ return m_felix.isBundlePersistentlyStarted(bundle);
+ }
+
+ public void run()
+ {
+ int startLevel = 0;
+
+ // This thread loops forever, thus it should
+ // be a daemon thread.
+ while (true)
+ {
+ synchronized (m_requestList)
+ {
+ // Wait for a request.
+ while (m_requestList.size() == 0)
+ {
+ try {
+ m_requestList.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+
+ // Get the requested start level.
+ startLevel = ((Integer) m_requestList.remove(0)).intValue();
+ }
+
+ // Set the new start level.
+ m_felix.setFrameworkStartLevel(startLevel);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/SystemBundle.java b/src/org/apache/osgi/framework/SystemBundle.java
new file mode 100644
index 0000000..d201e7d
--- /dev/null
+++ b/src/org/apache/osgi/framework/SystemBundle.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.io.InputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+
+import org.apache.osgi.framework.searchpolicy.*;
+import org.apache.osgi.framework.util.CaseInsensitiveMap;
+import org.apache.osgi.framework.util.FelixConstants;
+import org.apache.osgi.moduleloader.LibrarySource;
+import org.apache.osgi.moduleloader.ResourceSource;
+import org.osgi.framework.*;
+
+
+class SystemBundle extends BundleImpl
+{
+ private List m_activatorList = null;
+ private BundleActivator m_activator = null;
+ private Thread m_shutdownThread = null;
+ private Object[][] m_attributes = null;
+ private ResourceSource[] m_resSources = null;
+ private LibrarySource[] m_libSources = null;
+
+ protected SystemBundle(Felix felix, BundleInfo info, List activatorList)
+ throws BundleException
+ {
+ super(felix, info);
+
+ // Create an activator list if necessary.
+ if (activatorList == null)
+ {
+ activatorList = new ArrayList();
+ }
+
+ // Add the bundle activator for the package admin service.
+ activatorList.add(new PackageAdminActivator(felix));
+
+ // Add the bundle activator for the start level service.
+ activatorList.add(new StartLevelActivator(felix));
+
+ m_activatorList = activatorList;
+
+ // The system bundle exports framework packages as well as
+ // arbitrary user-defined packages from the system class path.
+ // We must construct the system bundle's export metadata.
+
+ // Get system property that specifies which class path
+ // packages should be exported by the system bundle.
+ R4Package[] classPathPkgs = null;
+ try
+ {
+ classPathPkgs = R4Package.parseImportOrExportHeader(
+ getFelix().getConfig().get(FelixConstants.FRAMEWORK_SYSTEMPACKAGES));
+ }
+ catch (Exception ex)
+ {
+ getFelix().getLogger().log(
+ LogWrapper.LOG_ERROR,
+ "Error parsing system bundle export statement.", ex);
+ }
+
+ // Now, create the list of standard framework exports for
+ // the system bundle.
+ R4Package[] exports = new R4Package[classPathPkgs.length + 3];
+
+ exports[0] = new R4Package(
+ "org.osgi.framework",
+ new R4Directive[0],
+ new R4Attribute[] { new R4Attribute("version", "1.2.0", false) });
+
+ exports[1] = new R4Package(
+ "org.osgi.service.packageadmin",
+ new R4Directive[0],
+ new R4Attribute[] { new R4Attribute("version", "1.2.0", false) });
+
+ exports[2] = new R4Package(
+ "org.osgi.service.startlevel",
+ new R4Directive[0],
+ new R4Attribute[] { new R4Attribute("version", "1.0.0", false) });
+
+ // Copy the class path exported packages.
+ System.arraycopy(classPathPkgs, 0, exports, 3, classPathPkgs.length);
+
+ m_attributes = new Object[][] {
+ new Object[] { R4SearchPolicy.EXPORTS_ATTR, exports },
+ new Object[] { R4SearchPolicy.IMPORTS_ATTR, new R4Package[0] }
+ };
+
+ m_resSources = new ResourceSource[0];
+
+ m_libSources = null;
+
+ String exportString = "";
+ for (int i = 0; i < exports.length; i++)
+ {
+ exportString = exportString +
+ exports[i].getId()
+ + "; specification-version=\""
+ + exports[i].getVersionLow().toString() + "\"";
+
+ if (i < (exports.length - 1))
+ {
+ exportString = exportString + ", ";
+ exportString = exportString +
+ exports[i].getId()
+ + "; specification-version=\""
+ + exports[i].getVersionLow().toString() + "\", ";
+ }
+ }
+
+ // Initialize header map as a case insensitive map.
+ Map map = new CaseInsensitiveMap();
+ map.put(FelixConstants.BUNDLE_VERSION, FelixConstants.FELIX_VERSION_VALUE);
+ map.put(FelixConstants.BUNDLE_NAME, "System Bundle");
+ map.put(FelixConstants.BUNDLE_DESCRIPTION,
+ "This bundle is system specific; it implements various system services.");
+ map.put(FelixConstants.EXPORT_PACKAGE, exportString);
+ ((SystemBundleArchive) getInfo().getArchive()).setManifestHeader(map);
+ }
+
+ public Object[][] getAttributes()
+ {
+ return m_attributes;
+ }
+
+ public ResourceSource[] getResourceSources()
+ {
+ return m_resSources;
+ }
+
+ public LibrarySource[] getLibrarySources()
+ {
+ return m_libSources;
+ }
+
+ public synchronized void start() throws BundleException
+ {
+ // The system bundle is only started once and it
+ // is started by the framework.
+ if (getState() == Bundle.ACTIVE)
+ {
+ throw new BundleException("Cannot start the system bundle.");
+ }
+
+ getInfo().setState(Bundle.STARTING);
+
+ try {
+ getInfo().setContext(new BundleContextImpl(getFelix(), this));
+ getActivator().start(getInfo().getContext());
+ } catch (Throwable throwable) {
+throwable.printStackTrace();
+ throw new BundleException(
+ "Unable to start system bundle.", throwable);
+ }
+
+ // Do NOT set the system bundle state to active yet, this
+ // must be done after all other bundles have been restarted.
+ // This will be done after the framework is initialized.
+ }
+
+ public synchronized void stop() throws BundleException
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(new AdminPermission());
+ }
+
+ // Spec says stop() on SystemBundle should return immediately and
+ // shutdown framework on another thread.
+ if (getFelix().getStatus() == Felix.RUNNING_STATUS)
+ {
+ // Initial call of stop, so kick off shutdown.
+ m_shutdownThread = new Thread("FelixShutdown") {
+ public void run()
+ {
+ try
+ {
+ getFelix().shutdown();
+ }
+ catch (Exception ex)
+ {
+ getFelix().getLogger().log(
+ LogWrapper.LOG_ERROR,
+ "SystemBundle: Error while shutting down.", ex);
+ }
+
+ // Only shutdown the JVM if the framework is running stand-alone.
+ String embedded = getFelix().getConfig()
+ .get(FelixConstants.EMBEDDED_EXECUTION_PROP);
+ boolean isEmbedded = (embedded == null)
+ ? false : embedded.equals("true");
+ if (!isEmbedded)
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run()
+ {
+ System.exit(0);
+ return null;
+ }
+ });
+ }
+ else
+ {
+ System.exit(0);
+ }
+ }
+ }
+ };
+ getInfo().setState(Bundle.STOPPING);
+ m_shutdownThread.start();
+ }
+ else if ((getFelix().getStatus() == Felix.STOPPING_STATUS) &&
+ (Thread.currentThread() == m_shutdownThread))
+ {
+ // Callback from shutdown thread, so do our own stop.
+ try
+ {
+ getActivator().stop(getInfo().getContext());
+ }
+ catch (Throwable throwable)
+ {
+ throw new BundleException(
+ "Unable to stop system bundle.", throwable);
+ }
+ }
+ }
+
+ public synchronized void uninstall() throws BundleException
+ {
+ throw new BundleException("Cannot uninstall the system bundle.");
+ }
+
+ public synchronized void update() throws BundleException
+ {
+ update(null);
+ }
+
+ public synchronized void update(InputStream is) throws BundleException
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.checkPermission(new AdminPermission());
+ }
+
+ // TODO: This is supposed to stop and then restart the framework.
+ throw new BundleException("System bundle update not implemented yet.");
+ }
+
+ protected BundleActivator getActivator()
+ throws Exception
+ {
+ if (m_activator == null)
+ {
+ m_activator = new SystemBundleActivator(getFelix(), m_activatorList);
+ }
+ return m_activator;
+ }
+}
diff --git a/src/org/apache/osgi/framework/SystemBundleActivator.java b/src/org/apache/osgi/framework/SystemBundleActivator.java
new file mode 100644
index 0000000..0dbd1a7
--- /dev/null
+++ b/src/org/apache/osgi/framework/SystemBundleActivator.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.util.List;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+class SystemBundleActivator implements BundleActivator
+{
+ private Felix m_felix = null;
+ private List m_activatorList = null;
+ private BundleContext m_context = null;
+
+ SystemBundleActivator(Felix felix, List activatorList)
+ {
+ this.m_felix = felix;
+ this.m_activatorList = activatorList;
+ }
+
+ public void start(BundleContext context) throws Exception
+ {
+ this.m_context = context;
+
+ // Start all activators.
+ if (m_activatorList != null)
+ {
+ for (int i = 0; i < m_activatorList.size(); i++)
+ {
+ ((BundleActivator) m_activatorList.get(i)).start(context);
+ }
+ }
+ }
+
+ public void stop(BundleContext context) throws Exception
+ {
+ if (m_activatorList != null)
+ {
+ // Stop all activators.
+ for (int i = 0; i < m_activatorList.size(); i++)
+ {
+ ((BundleActivator) m_activatorList.get(i)).stop(context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/SystemBundleArchive.java b/src/org/apache/osgi/framework/SystemBundleArchive.java
new file mode 100644
index 0000000..611d350
--- /dev/null
+++ b/src/org/apache/osgi/framework/SystemBundleArchive.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework;
+
+import java.io.File;
+import java.util.Map;
+
+import org.apache.osgi.framework.cache.BundleArchive;
+import org.apache.osgi.framework.util.FelixConstants;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+
+public class SystemBundleArchive implements BundleArchive
+{
+ private Map m_headerMap = null;
+
+ public long getId()
+ {
+ return 0;
+ }
+
+ public String getLocation()
+ throws Exception
+ {
+ return FelixConstants.SYSTEM_BUNDLE_LOCATION;
+ }
+
+ public int getPersistentState()
+ throws Exception
+ {
+ return Bundle.ACTIVE;
+ }
+
+ public void setPersistentState(int state)
+ throws Exception
+ {
+ }
+
+ public int getStartLevel()
+ throws Exception
+ {
+ return FelixConstants.SYSTEMBUNDLE_DEFAULT_STARTLEVEL;
+ }
+
+ public void setStartLevel(int level)
+ throws Exception
+ {
+ }
+
+ public File getDataFile(String fileName)
+ throws Exception
+ {
+ return null;
+ }
+
+ public BundleActivator getActivator(ClassLoader loader)
+ throws Exception
+ {
+ return null;
+ }
+
+ public void setActivator(Object obj)
+ throws Exception
+ {
+ }
+
+ public int getRevisionCount()
+ throws Exception
+ {
+ return 1;
+ }
+
+ public Map getManifestHeader(int revision)
+ throws Exception
+ {
+ return m_headerMap;
+ }
+
+ protected void setManifestHeader(Map headerMap)
+ {
+ m_headerMap = headerMap;
+ }
+
+ public String[] getClassPath(int revision)
+ throws Exception
+ {
+ return null;
+ }
+
+ public String findLibrary(int revision, String libName)
+ throws Exception
+ {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/cache/BundleArchive.java b/src/org/apache/osgi/framework/cache/BundleArchive.java
new file mode 100644
index 0000000..9ad76c1
--- /dev/null
+++ b/src/org/apache/osgi/framework/cache/BundleArchive.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.cache;
+
+import java.io.File;
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+
+/**
+ * <p>
+ * This interface represents an individual cached bundle in the
+ * bundle cache. Felix uses this interface to access all information
+ * about the associated bundle's cached information. Classes that implement
+ * this interface will be related to a specific implementation of the
+ * <tt>BundleCache</tt> interface.
+ * </p>
+ * @see org.apache.osgi.framework.BundleCache
+**/
+public interface BundleArchive
+{
+ /**
+ * <p>
+ * Returns the identifier of the bundle associated with this archive.
+ * </p>
+ * @return the identifier of the bundle associated with this archive.
+ **/
+ public long getId();
+
+ /**
+ * <p>
+ * Returns the location string of the bundle associated with this archive.
+ * </p>
+ * @return the location string of the bundle associated with this archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public String getLocation()
+ throws Exception;
+
+ /**
+ * <p>
+ * Returns the persistent state of the bundle associated with the archive;
+ * this value will be either <tt>Bundle.INSTALLED</tt> or <tt>Bundle.ACTIVE</tt>.
+ * </p>
+ * @return the persistent state of the bundle associated with this archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public int getPersistentState()
+ throws Exception;
+
+ /**
+ * <p>
+ * Sets the persistent state of the bundle associated with this archive;
+ * this value will be either <tt>Bundle.INSTALLED</tt> or <tt>Bundle.ACTIVE</tt>.
+ * </p>
+ * @param state the new bundle state to write to the archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public void setPersistentState(int state)
+ throws Exception;
+
+ /**
+ * <p>
+ * Returns the start level of the bundle associated with this archive.
+ * </p>
+ * @return the start level of the bundle associated with this archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public int getStartLevel()
+ throws Exception;
+
+ /**
+ * <p>
+ * Sets the start level of the bundle associated with this archive.
+ * </p>
+ * @param level the new bundle start level to write to the archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public void setStartLevel(int level)
+ throws Exception;
+
+ /**
+ * <p>
+ * Returns an appropriate data file for the bundle associated with the
+ * archive using the supplied file name.
+ * </p>
+ * @return a <tt>File</tt> corresponding to the requested data file for
+ * the bundle associated with this archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public File getDataFile(String fileName)
+ throws Exception;
+
+ /**
+ * <p>
+ * Returns the persistent bundle activator of the bundle associated with
+ * this archive; this is a non-standard OSGi method that is only called
+ * when Felix is running in non-strict OSGi mode.
+ * </p>
+ * @param loader the class loader to use when trying to instantiate
+ * the bundle activator.
+ * @return the persistent bundle activator of the bundle associated with
+ * this archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public BundleActivator getActivator(ClassLoader loader)
+ throws Exception;
+
+ /**
+ * <p>
+ * Sets the persistent bundle activator of the bundle associated with
+ * this archive; this is a non-standard OSGi method that is only called
+ * when Felix is running in non-strict OSGi mode.
+ * </p>
+ * @param obj the new persistent bundle activator to write to the archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public void setActivator(Object obj)
+ throws Exception;
+
+ /**
+ * <p>
+ * Returns the number of revisions of the bundle associated with the
+ * archive. When a bundle is updated, the previous version of the bundle
+ * is maintained along with the new revision until old revisions are
+ * purged. The revision count reflects how many revisions of the bundle
+ * are currently available in the cache.
+ * </p>
+ * @return the number of revisions of the bundle associated with this archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public int getRevisionCount()
+ throws Exception;
+
+ /**
+ * <p>
+ * Returns the main attributes of the JAR file manifest header of the
+ * specified revision of the bundle associated with this archive. The
+ * returned map should be case insensitive.
+ * </p>
+ * @param revision the specified revision.
+ * @return the case-insensitive JAR file manifest header of the specified
+ * revision of the bundle associated with this archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public Map getManifestHeader(int revision)
+ throws Exception;
+
+ /**
+ * <p>
+ * Returns an array of <tt>String</tt>s that represent the class path of
+ * the specified revision of the bundle associated with this archive.
+ * Currently, these values are restricted to absolute paths in the file
+ * system, but this may be lifted in the future (perhaps they should be
+ * <tt>ResourceSource</tt>s from the Module Loader.
+ * </p>
+ * @param revision the specified revision.
+ * @return a <tt>String</tt> array of the absolute path names that
+ * comprise the class path of the specified revision of the
+ * bundle associated with this archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public String[] getClassPath(int revision)
+ throws Exception;
+
+ /**
+ * <p>
+ * Returns the absolute file path for the specified native library of the
+ * specified revision of the bundle associated with this archive.
+ * </p>
+ * @param revision the specified revision.
+ * @param libName the name of the library.
+ * @return a <tt>String</tt> that contains the absolute path name to
+ * the requested native library of the specified revision of the
+ * bundle associated with this archive.
+ * @throws java.lang.Exception if any error occurs.
+ **/
+ public String findLibrary(int revision, String libName)
+ throws Exception;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/cache/BundleCache.java b/src/org/apache/osgi/framework/cache/BundleCache.java
new file mode 100644
index 0000000..87c0ee9
--- /dev/null
+++ b/src/org/apache/osgi/framework/cache/BundleCache.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.cache;
+
+import java.io.InputStream;
+
+import org.apache.osgi.framework.LogWrapper;
+import org.apache.osgi.framework.util.PropertyResolver;
+
+/**
+ * <p>
+ * This interface represents the storage mechanism that Felix uses for
+ * caching bundles. It is possible for multiple implementations of
+ * this interface to exist for different storage technologies, such as the
+ * file system, memory, or a database. Felix includes a default implementation
+ * of this interface that uses the file system. Felix allows you to specify
+ * alternative implementations to use by specifying a class name via the
+ * <tt>felix.cache.class</tt> system property. Bundle cache implemenations
+ * should implement this interface and provide a default constructor.
+ * </p>
+ * @see org.apache.osgi.framework.BundleArchive
+**/
+public interface BundleCache
+{
+ /**
+ * <p>
+ * This method is called before using the BundleCache implementation
+ * to initialize it and to pass it a reference to its associated
+ * configuration property resolver and logger. The <tt>BundleCache</tt>
+ * implementation should not use <tt>System.getProperty()</tt> directly
+ * for configuration properties, it should use the property resolver
+ * instance passed into this method. The property resolver
+ * provides access to properties passed into the Felix instance's
+ * constructor. This approach allows multiple instances of Felix to
+ * exist in memory at the same time, but for
+ * them to be configured differently. For example, an application may
+ * want two instances of Felix, where each instance stores their cache
+ * in a different location in the file system. When using multiple
+ * instances of Felix in memory at the same time, system properties
+ * should be avoided and all properties should be passed in to Felix's
+ * constructor.
+ * </p>
+ * @param cfg the property resolver for obtaining configuration properties.
+ * @param logger the logger to use for reporting errors.
+ * @throws Exception if any error occurs.
+ **/
+ public void initialize(PropertyResolver cfg, LogWrapper logger)
+ throws Exception;
+
+ /**
+ * <p>
+ * Returns all cached bundle archives.
+ * </p>
+ * @return an array of all cached bundle archives.
+ * @throws Exception if any error occurs.
+ **/
+ public BundleArchive[] getArchives()
+ throws Exception;
+
+ /**
+ * <p>
+ * Returns the bundle archive associated with the specified
+ * bundle indentifier.
+ * </p>
+ * @param id the identifier of the bundle archive to retrieve.
+ * @return the bundle archive assocaited with the specified bundle identifier.
+ * @throws Exception if any error occurs.
+ **/
+ public BundleArchive getArchive(long id)
+ throws Exception;
+
+ /**
+ * <p>
+ * Creates a new bundle archive for the specified bundle
+ * identifier using the supplied location string and input stream. The
+ * contents of the bundle JAR file should be read from the supplied
+ * input stream, which will not be <tt>null</tt>. The input stream is
+ * closed by the caller; the implementation is only responsible for
+ * closing streams it opens. If this method completes successfully, then
+ * it means that the initial bundle revision of the specified bundle was
+ * successfully cached.
+ * </p>
+ * @param id the identifier of the bundle associated with the new archive.
+ * @param location the location of the bundle associated with the new archive.
+ * @param is the input stream to the bundle's JAR file.
+ * @return the created bundle archive.
+ * @throws Exception if any error occurs.
+ **/
+ public BundleArchive create(long id, String location, InputStream is)
+ throws Exception;
+
+ /**
+ * <p>
+ * Saves an updated revision of the specified bundle to
+ * the bundle cache using the supplied input stream. The contents of the
+ * updated bundle JAR file should be read from the supplied input stream,
+ * which will not be <tt>null</tt>. The input stream is closed by the
+ * caller; the implementation is only responsible for closing streams
+ * it opens. Updating a bundle in the cache does not replace the current
+ * revision of the bundle, it makes a new revision available. If this
+ * method completes successfully, then it means that the number of
+ * revisions of the specified bundle has increased by one.
+ * </p>
+ * @param ba the bundle archive of the bundle to update.
+ * @param is the input stream to the bundle's updated JAR file.
+ * @throws Exception if any error occurs.
+ **/
+ public void update(BundleArchive ba, InputStream is)
+ throws Exception;
+
+ /**
+ * <p>
+ * Purges all old revisions of the specified bundle from
+ * the cache. If this method completes successfully, then it means that
+ * only the most current revision of the bundle should exist in the cache.
+ * </p>
+ * @param ba the bundle archive of the bundle to purge.
+ * @throws Exception if any error occurs.
+ **/
+ public void purge(BundleArchive ba)
+ throws Exception;
+
+ /**
+ * <p>
+ * Removes the specified bundle from the cache. If this method
+ * completes successfully, there should be no trace of the removed bundle
+ * in the cache.
+ * </p>
+ * @param ba the bundle archive of the bundle to remove.
+ * @throws Exception if any error occurs.
+ **/
+ public void remove(BundleArchive ba)
+ throws Exception;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/cache/DefaultBundleArchive.java b/src/org/apache/osgi/framework/cache/DefaultBundleArchive.java
new file mode 100644
index 0000000..18d04c0
--- /dev/null
+++ b/src/org/apache/osgi/framework/cache/DefaultBundleArchive.java
@@ -0,0 +1,1471 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.cache;
+
+import java.io.*;
+import java.security.*;
+import java.util.*;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import org.apache.osgi.framework.LogWrapper;
+import org.apache.osgi.framework.util.*;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+
+/**
+ * <p>
+ * This class, combined with <tt>DefaultBundleCache</tt>, implements the
+ * default file system-based bundle cache for Felix.
+ * </p>
+ * @see org.apache.osgi.framework.util.DefaultBundleCache
+**/
+public class DefaultBundleArchive implements BundleArchive
+{
+ private static final transient String BUNDLE_JAR_FILE = "bundle.jar";
+ private static final transient String BUNDLE_LOCATION_FILE = "bundle.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 REVISION_DIRECTORY = "version";
+ private static final transient String EMBEDDED_DIRECTORY = "embedded";
+ private static final transient String LIBRARY_DIRECTORY = "lib";
+ 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 LogWrapper m_logger = null;
+ private long m_id = -1;
+ private File m_dir = null;
+ private String m_location = null;
+ private int m_persistentState = -1;
+ private int m_startLevel = -1;
+ private Map m_currentHeader = null;
+
+ private long m_refreshCount = -1;
+ private int m_revisionCount = -1;
+
+ public DefaultBundleArchive(LogWrapper logger, File dir, long id, String location, InputStream is)
+ throws Exception
+ {
+ this(logger, dir, id);
+ m_location = location;
+
+ // Try to save and pre-process the bundle JAR.
+ try
+ {
+ initialize(is);
+ }
+ catch (Exception ex)
+ {
+ if (!deleteDirectoryTree(dir))
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Unable to delete the archive directory: " + id);
+ }
+ throw ex;
+ }
+ }
+
+ public DefaultBundleArchive(LogWrapper logger, File dir, long id)
+ {
+ m_logger = logger;
+ m_dir = dir;
+ m_id = id;
+ if (m_id <= 0)
+ {
+ throw new IllegalArgumentException(
+ "Bundle ID cannot be less than or equal to zero.");
+ }
+ }
+
+ private void initialize(InputStream is)
+ throws Exception
+ {
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.INITIALIZE_ACTION, this, is));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ initializeUnchecked(is);
+ }
+ }
+
+ private void initializeUnchecked(InputStream is)
+ throws Exception
+ {
+ FileWriter fw = null;
+ BufferedWriter bw = null;
+
+ try
+ {
+ // Create archive directory.
+ if (!m_dir.mkdir())
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "DefaultBundleArchive: Unable to create archive directory.");
+ throw new IOException("Unable to create archive directory.");
+ }
+
+ // Save location string.
+ File file = new File(m_dir, BUNDLE_LOCATION_FILE);
+ fw = new FileWriter(file);
+ bw = new BufferedWriter(fw);
+ bw.write(m_location, 0, m_location.length());
+
+ // Create version/revision directory for bundle JAR.
+ // Since this is only called when the bundle JAR is
+ // first saved, the update and revision will always
+ // be "0.0" for the directory name.
+ File revisionDir = new File(m_dir, REVISION_DIRECTORY + "0.0");
+ if (!revisionDir.mkdir())
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "DefaultBundleArchive: Unable to create revision directory.");
+ throw new IOException("Unable to create revision directory.");
+ }
+
+ // Save the bundle jar file.
+ file = new File(revisionDir, BUNDLE_JAR_FILE);
+ copy(is, file);
+
+ // This will always be revision zero.
+ preprocessBundleJar(0, revisionDir);
+
+ }
+ finally
+ {
+ if (is != null) is.close();
+ if (bw != null) bw.close();
+ if (fw != null) fw.close();
+ }
+ }
+
+ public File getDirectory()
+ {
+ return m_dir;
+ }
+
+ public long getId()
+ {
+ return m_id;
+ }
+
+ public String getLocation()
+ throws Exception
+ {
+ if (m_location != null)
+ {
+ return m_location;
+ }
+ else if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ return (String) AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.GET_LOCATION_ACTION, this));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ return getLocationUnchecked();
+ }
+ }
+
+ private String getLocationUnchecked()
+ throws Exception
+ {
+ // Get bundle location file.
+ File locFile = new File(m_dir, BUNDLE_LOCATION_FILE);
+
+ // Read bundle location.
+ FileReader fr = null;
+ BufferedReader br = null;
+ try
+ {
+ fr = new FileReader(locFile);
+ br = new BufferedReader(fr);
+ m_location = br.readLine();
+ return m_location;
+ }
+ finally
+ {
+ if (br != null) br.close();
+ if (fr != null) fr.close();
+ }
+ }
+
+ public int getPersistentState()
+ throws Exception
+ {
+ if (m_persistentState >= 0)
+ {
+ return m_persistentState;
+ }
+ else if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ return ((Integer) AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.GET_PERSISTENT_STATE_ACTION, this))).intValue();
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ return getPersistentStateUnchecked();
+ }
+ }
+
+ private int getPersistentStateUnchecked()
+ throws Exception
+ {
+ // Get bundle state file.
+ File stateFile = new File(m_dir, BUNDLE_STATE_FILE);
+
+ // If the state file doesn't exist, then
+ // assume the bundle was installed.
+ if (!stateFile.exists())
+ {
+ return Bundle.INSTALLED;
+ }
+
+ // Read the bundle state.
+ FileReader fr = null;
+ BufferedReader br= null;
+ try
+ {
+ fr = new FileReader(stateFile);
+ br = new BufferedReader(fr);
+ 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 (fr != null) fr.close();
+ }
+ }
+
+ public void setPersistentState(int state)
+ throws Exception
+ {
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.SET_PERSISTENT_STATE_ACTION, this, state));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ setPersistentStateUnchecked(state);
+ }
+ }
+
+ private void setPersistentStateUnchecked(int state)
+ throws Exception
+ {
+ // Get bundle state file.
+ File stateFile = new File(m_dir, BUNDLE_STATE_FILE);
+
+ // Write the bundle state.
+ FileWriter fw = null;
+ BufferedWriter bw= null;
+ try
+ {
+ fw = new FileWriter(stateFile);
+ bw = new BufferedWriter(fw);
+ 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(
+ LogWrapper.LOG_ERROR,
+ "DefaultBundleArchive: Unable to record state: " + ex);
+ throw ex;
+ }
+ finally
+ {
+ if (bw != null) bw.close();
+ if (fw != null) fw.close();
+ }
+ }
+
+ public int getStartLevel()
+ throws Exception
+ {
+ if (m_startLevel >= 0)
+ {
+ return m_startLevel;
+ }
+ else if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ return ((Integer) AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.GET_START_LEVEL_ACTION, this))).intValue();
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ return getStartLevelUnchecked();
+ }
+ }
+
+ private int getStartLevelUnchecked()
+ throws Exception
+ {
+ // Get bundle start level file.
+ File levelFile = new File(m_dir, BUNDLE_START_LEVEL_FILE);
+
+ // If the start level file doesn't exist, then
+ // return an error.
+ if (!levelFile.exists())
+ {
+ return -1;
+ }
+
+ // Read the bundle start level.
+ FileReader fr = null;
+ BufferedReader br= null;
+ try
+ {
+ fr = new FileReader(levelFile);
+ br = new BufferedReader(fr);
+ m_startLevel = Integer.parseInt(br.readLine());
+ return m_startLevel;
+ }
+ finally
+ {
+ if (br != null) br.close();
+ if (fr != null) fr.close();
+ }
+ }
+
+ public void setStartLevel(int level)
+ throws Exception
+ {
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.SET_START_LEVEL_ACTION, this, level));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ setStartLevelUnchecked(level);
+ }
+ }
+
+ private void setStartLevelUnchecked(int level)
+ throws Exception
+ {
+ // Get bundle start level file.
+ File levelFile = new File(m_dir, BUNDLE_START_LEVEL_FILE);
+
+ // Write the bundle start level.
+ FileWriter fw = null;
+ BufferedWriter bw = null;
+ try
+ {
+ fw = new FileWriter(levelFile);
+ bw = new BufferedWriter(fw);
+ String s = Integer.toString(level);
+ bw.write(s, 0, s.length());
+ m_startLevel = level;
+ }
+ catch (IOException ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "DefaultBundleArchive: Unable to record start leel: " + ex);
+ throw ex;
+ }
+ finally
+ {
+ if (bw != null) bw.close();
+ if (fw != null) fw.close();
+ }
+ }
+
+ public 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_dir, DATA_DIRECTORY);
+
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.CREATE_DATA_DIR_ACTION, this, dataDir));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ createDataDirectoryUnchecked(dataDir);
+ }
+
+ // Return the data file.
+ return new File(dataDir, fileName);
+ }
+
+ private void createDataDirectoryUnchecked(File dir)
+ throws Exception
+ {
+ // Create data directory if necessary.
+ if (!dir.exists())
+ {
+ if (!dir.mkdir())
+ {
+ throw new IOException("Unable to create bundle data directory.");
+ }
+ }
+ }
+
+ public BundleActivator getActivator(ClassLoader loader)
+ throws Exception
+ {
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ return (BundleActivator) AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.GET_ACTIVATOR_ACTION, this, loader));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ return getActivatorUnchecked(loader);
+ }
+ }
+
+ private BundleActivator getActivatorUnchecked(ClassLoader loader)
+ throws Exception
+ {
+ // Get bundle activator file.
+ File activatorFile = new File(m_dir, BUNDLE_ACTIVATOR_FILE);
+ // If the activator file doesn't exist, then
+ // assume there isn't one.
+ if (!activatorFile.exists())
+ return null;
+
+ // Deserialize the activator object.
+ InputStream is = null;
+ ObjectInputStreamX ois = null;
+ try
+ {
+ is = new FileInputStream(activatorFile);
+ ois = new ObjectInputStreamX(is, loader);
+ Object o = ois.readObject();
+ return (BundleActivator) o;
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "DefaultBundleArchive: Trying to deserialize - " + ex);
+ }
+ finally
+ {
+ if (ois != null) ois.close();
+ if (is != null) is.close();
+ }
+
+ return null;
+ }
+
+ public void setActivator(Object obj)
+ throws Exception
+ {
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.SET_ACTIVATOR_ACTION, this, obj));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ setActivatorUnchecked(obj);
+ }
+ }
+
+ private void setActivatorUnchecked(Object obj)
+ throws Exception
+ {
+ if (!(obj instanceof Serializable))
+ {
+ return;
+ }
+
+ // Get bundle activator file.
+ File activatorFile = new File(m_dir, BUNDLE_ACTIVATOR_FILE);
+
+ // Serialize the activator object.
+ OutputStream os = null;
+ ObjectOutputStream oos = null;
+ try
+ {
+ os = new FileOutputStream(activatorFile);
+ oos = new ObjectOutputStream(os);
+ oos.writeObject(obj);
+ }
+ catch (IOException ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "DefaultBundleArchive: Unable to serialize activator - " + ex);
+ throw ex;
+ }
+ finally
+ {
+ if (oos != null) oos.close();
+ if (os != null) os.close();
+ }
+ }
+
+ public int getRevisionCount()
+ throws Exception
+ {
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ return ((Integer) AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.GET_REVISION_COUNT_ACTION, this))).intValue();
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ return getRevisionCountUnchecked();
+ }
+ }
+
+ public int getRevisionCountUnchecked()
+ {
+ // We should always have at least one revision
+ // directory, so try to count them if the value
+ // has not been initialized yet.
+ if (m_revisionCount <= 0)
+ {
+ m_revisionCount = 0;
+ File[] children = m_dir.listFiles();
+ for (int i = 0; (children != null) && (i < children.length); i++)
+ {
+ if (children[i].getName().startsWith(REVISION_DIRECTORY))
+ {
+ m_revisionCount++;
+ }
+ }
+ }
+ return m_revisionCount;
+ }
+
+ public Map getManifestHeader(int revision)
+ throws Exception
+ {
+ // If the request is for the current revision header,
+ // then return the cached copy if it is present.
+ if ((revision == (getRevisionCount() - 1)) && (m_currentHeader != null))
+ {
+ return m_currentHeader;
+ }
+
+ // Get the revision directory.
+ File revisionDir = new File(
+ m_dir, REVISION_DIRECTORY + getRefreshCount() + "." + revision);
+
+ // Get the embedded resource.
+ JarFile jarFile = null;
+
+ try
+ {
+ // Create JarFile object using privileged block.
+ if (System.getSecurityManager() != null)
+ {
+ jarFile = (JarFile) AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.OPEN_BUNDLE_JAR_ACTION, this, revisionDir));
+ }
+ else
+ {
+ jarFile = openBundleJarUnchecked(revisionDir);
+ }
+
+ // Error if no jar file.
+ if (jarFile == null)
+ {
+ throw new IOException("No JAR file found.");
+ }
+
+ // Get manifest.
+ Manifest mf = jarFile.getManifest();
+ // Create a case insensitive map of manifest attributes.
+ Map map = new CaseInsensitiveMap(mf.getMainAttributes());
+ // If the request is for the current revision's header,
+ // then cache it.
+ if (revision == (getRevisionCount() - 1))
+ {
+ m_currentHeader = map;
+ }
+ return map;
+
+ } catch (PrivilegedActionException ex) {
+ throw ((PrivilegedActionException) ex).getException();
+ } finally {
+ if (jarFile != null) jarFile.close();
+ }
+ }
+
+ private JarFile openBundleJarUnchecked(File revisionDir)
+ throws Exception
+ {
+ // Get bundle jar file.
+ File bundleJar = new File(revisionDir, BUNDLE_JAR_FILE);
+ // Get bundle jar file.
+ return new JarFile(bundleJar);
+ }
+
+ public String[] getClassPath(int revision)
+ throws Exception
+ {
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ return (String []) AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.GET_CLASS_PATH_ACTION, this, revision));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ return getClassPathUnchecked(revision);
+ }
+ }
+
+ private String[] getClassPathUnchecked(int revision)
+ throws Exception
+ {
+ // Get the revision directory.
+ File revisionDir = new File(
+ m_dir, REVISION_DIRECTORY + getRefreshCount() + "." + revision);
+
+ // Get the bundle's manifest header.
+ Map map = getManifestHeader(revision);
+ if (map == null)
+ {
+ map = new HashMap();
+ }
+
+ // Find class path meta-data.
+ String classPath = null;
+ Iterator iter = map.entrySet().iterator();
+ while ((classPath == null) && iter.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ if (entry.getKey().toString().toLowerCase().equals(
+ FelixConstants.BUNDLE_CLASSPATH.toLowerCase()))
+ {
+ classPath = entry.getValue().toString();
+ }
+ }
+
+ // Parse the class path into strings.
+ String[] classPathStrings = Util.parseDelimitedString(
+ classPath, FelixConstants.CLASS_PATH_SEPARATOR);
+
+ if (classPathStrings == null)
+ {
+ classPathStrings = new String[0];
+ }
+
+ // Now, check for "." in the class path.
+ boolean includeDot = false;
+ for (int i = 0; !includeDot && (i < classPathStrings.length); i++)
+ {
+ if (classPathStrings[i].equals(FelixConstants.CLASS_PATH_DOT))
+ {
+ includeDot = true;
+ }
+ }
+
+ // Include all JARs in the embedded jar directory, since they
+ // were extracted when the bundle was initially saved.
+ File embedDir = new File(revisionDir, EMBEDDED_DIRECTORY);
+ String[] paths = null;
+ if (embedDir.exists())
+ {
+ // The size of the paths array is the number of
+ // embedded JAR files plus one, if we need to include
+ // ".", otherwise it is just the number of JAR files.
+ // If "." is included, then it will be added to the
+ // first place in the path array below.
+ File[] children = embedDir.listFiles();
+ int size = (children == null) ? 0 : children.length;
+ size = (includeDot) ? size + 1 : size;
+ paths = new String[size];
+ for (int i = 0; i < children.length; i++)
+ {
+ // If we are including "." then skip the first slot,
+ // because this is where we will put the bundle JAR file.
+ paths[(includeDot) ? i + 1 : i] = children[i].getPath();
+ }
+ }
+
+ // If there is nothing on the class path, then include
+ // "." by default, as per the spec.
+ if ((paths == null) || (paths.length == 0))
+ {
+ includeDot = true;
+ paths = new String[1];
+ }
+
+ // Put the bundle jar file first, if included.
+ if (includeDot)
+ {
+ paths[0] = revisionDir + File.separator + BUNDLE_JAR_FILE;
+ }
+
+ return paths;
+ }
+
+// TODO: This will need to consider security.
+ public String findLibrary(int revision, String libName)
+ throws Exception
+ {
+ return findLibraryUnchecked(revision, libName);
+ }
+
+ private String findLibraryUnchecked(int revision, String libName)
+ throws Exception
+ {
+ // Get the revision directory.
+ File revisionDir = new File(
+ m_dir.getAbsoluteFile(),
+ REVISION_DIRECTORY + getRefreshCount() + "." + revision);
+
+ // Get bundle lib directory.
+ File libDir = new File(revisionDir, LIBRARY_DIRECTORY);
+ // Get lib file.
+ File libFile = new File(libDir, File.separatorChar + libName);
+ // Make sure that the library's parent directory exists;
+ // it may be in a sub-directory.
+ libDir = libFile.getParentFile();
+ if (!libDir.exists())
+ {
+ if (!libDir.mkdirs())
+ {
+ throw new IOException("Unable to create library directory.");
+ }
+ }
+ // Extract the library from the JAR file if it does not
+ // already exist.
+ if (!libFile.exists())
+ {
+ JarFile jarFile = null;
+ InputStream is = null;
+
+ try
+ {
+ jarFile = openBundleJarUnchecked(revisionDir);
+ ZipEntry ze = jarFile.getEntry(libName);
+ if (ze == null)
+ {
+ throw new IOException("No JAR entry: " + libName);
+ }
+ is = new BufferedInputStream(
+ jarFile.getInputStream(ze), DefaultBundleCache.BUFSIZE);
+ if (is == null)
+ {
+ throw new IOException("No input stream: " + libName);
+ }
+
+ // Create the file.
+ copy(is, libFile);
+
+ }
+ finally
+ {
+ if (jarFile != null) jarFile.close();
+ if (is != null) is.close();
+ }
+ }
+
+ return libFile.toString();
+ }
+
+ /**
+ * This utility method is used to retrieve the current refresh
+ * counter value for the bundle. This value is used when generating
+ * the bundle JAR 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 JAR 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 update counter file,
+ // then just return the result.
+ if (m_refreshCount >= 0)
+ {
+ return m_refreshCount;
+ }
+
+ // Get update counter file.
+ File counterFile = new File(m_dir, REFRESH_COUNTER_FILE);
+
+ // If the update counter file doesn't exist, then
+ // assume the counter is at zero.
+ if (!counterFile.exists())
+ {
+ return 0;
+ }
+
+ // Read the bundle update counter.
+ FileReader fr = null;
+ BufferedReader br = null;
+ try
+ {
+ fr = new FileReader(counterFile);
+ br = new BufferedReader(fr);
+ long counter = Long.parseLong(br.readLine());
+ return counter;
+ }
+ finally
+ {
+ if (br != null) br.close();
+ if (fr != null) fr.close();
+ }
+ }
+
+ /**
+ * This utility method is used to retrieve the current refresh
+ * counter value for the bundle. This value is used when generating
+ * the bundle JAR 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 JAR 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 update counter file.
+ File counterFile = new File(m_dir, REFRESH_COUNTER_FILE);
+
+ // Write the update counter.
+ FileWriter fw = null;
+ BufferedWriter bw = null;
+ try
+ {
+ fw = new FileWriter(counterFile);
+ bw = new BufferedWriter(fw);
+ String s = Long.toString(counter);
+ bw.write(s, 0, s.length());
+ m_refreshCount = counter;
+ }
+ catch (IOException ex)
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "DefaultBundleArchive: Unable to write counter: " + ex);
+ throw ex;
+ }
+ finally
+ {
+ if (bw != null) bw.close();
+ if (fw != null) fw.close();
+ }
+ }
+
+ //
+ // File-oriented utility methods.
+ //
+
+ protected static boolean deleteDirectoryTree(File target)
+ {
+ if (!target.exists())
+ {
+ return true;
+ }
+
+ if (target.isDirectory())
+ {
+ File[] files = target.listFiles();
+ for (int i = 0; i < files.length; i++)
+ {
+ deleteDirectoryTree(files[i]);
+ }
+ }
+
+ return target.delete();
+ }
+
+ /**
+ * This method copies an input stream to the specified file.
+ * <p>
+ * Security: This method must be called from within a <tt>doPrivileged()</tt>
+ * block since it accesses the disk.
+ * @param is the input stream to copy.
+ * @param outputFile the file to which the input stream should be copied.
+ **/
+ private void copy(InputStream is, File outputFile)
+ throws IOException
+ {
+ OutputStream os = null;
+
+ try
+ {
+ os = new BufferedOutputStream(
+ new FileOutputStream(outputFile), DefaultBundleCache.BUFSIZE);
+ byte[] b = new byte[DefaultBundleCache.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();
+ }
+ }
+
+ /**
+ * This method pre-processes a bundle JAR file making it ready
+ * for use. This entails extracting all embedded JAR files and
+ * all native libraries.
+ * @throws java.lang.Exception if any error occurs while processing JAR file.
+ **/
+ private void preprocessBundleJar(int revision, File revisionDir)
+ throws Exception
+ {
+ //
+ // Create special directories so that we can avoid checking
+ // for their existence all the time.
+ //
+
+ File embedDir = new File(revisionDir, EMBEDDED_DIRECTORY);
+ if (!embedDir.exists())
+ {
+ if (!embedDir.mkdir())
+ {
+ throw new IOException("Could not create embedded JAR directory.");
+ }
+ }
+
+ File libDir = new File(revisionDir, LIBRARY_DIRECTORY);
+ if (!libDir.exists())
+ {
+ if (!libDir.mkdir())
+ {
+ throw new IOException("Unable to create native library directory.");
+ }
+ }
+
+ //
+ // This block extracts all embedded JAR files.
+ //
+
+ try
+ {
+ // Get the bundle's manifest header.
+ Map map = getManifestHeader(revision);
+ if (map == null)
+ {
+ map = new HashMap();
+ }
+
+ // Find class path meta-data.
+ String classPath = null;
+ Iterator iter = map.entrySet().iterator();
+ while ((classPath == null) && iter.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ if (entry.getKey().toString().toLowerCase().equals(
+ FelixConstants.BUNDLE_CLASSPATH.toLowerCase()))
+ {
+ classPath = entry.getValue().toString();
+ }
+ }
+
+ // Parse the class path into strings.
+ String[] classPathStrings = Util.parseDelimitedString(
+ classPath, FelixConstants.CLASS_PATH_SEPARATOR);
+
+ if (classPathStrings == null)
+ {
+ classPathStrings = new String[0];
+ }
+
+ for (int i = 0; i < classPathStrings.length; i++)
+ {
+ if (!classPathStrings[i].equals(FelixConstants.CLASS_PATH_DOT))
+ {
+ extractEmbeddedJar(revisionDir, classPathStrings[i]);
+ }
+ }
+
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+
+ /**
+ * This method extracts an embedded JAR file from the bundle's
+ * JAR file.
+ * <p>
+ * Security: This method must be called from within a <tt>doPrivileged()</tt>
+ * block since it accesses the disk.
+ * @param id the identifier of the bundle that owns the embedded JAR file.
+ * @param jarPath the path to the embedded JAR file inside the bundle JAR file.
+ **/
+ private void extractEmbeddedJar(File revisionDir, String jarPath)
+ throws Exception
+ {
+ // Remove leading slash if present.
+ jarPath = (jarPath.charAt(0) == '/') ? jarPath.substring(1) : jarPath;
+ // Get only the JAR file name.
+ String jarName = (jarPath.lastIndexOf('/') >= 0)
+ ? jarPath.substring(jarPath.lastIndexOf('/') + 1) : jarPath;
+
+ // If JAR is already extracted, then don't
+ // re-extract it...
+ File embedFile = new File(
+ revisionDir, EMBEDDED_DIRECTORY + File.separatorChar + jarName);
+ if (!embedFile.exists())
+ {
+ JarFile jarFile = null;
+ InputStream is = null;
+
+ try
+ {
+ jarFile = openBundleJarUnchecked(revisionDir);
+ ZipEntry ze = jarFile.getEntry(jarPath);
+ if (ze == null)
+ {
+ throw new IOException("No JAR entry: " + jarPath);
+ }
+ is = new BufferedInputStream(jarFile.getInputStream(ze), DefaultBundleCache.BUFSIZE);
+ if (is == null)
+ {
+ throw new IOException("No input stream: " + jarPath);
+ }
+
+ // Create the file.
+ copy(is, embedFile);
+
+ }
+ finally
+ {
+ if (jarFile != null) jarFile.close();
+ if (is != null) is.close();
+ }
+ }
+ }
+
+ // INCREASES THE REVISION COUNT.
+ protected void update(InputStream is) throws Exception
+ {
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.UPDATE_ACTION, this, is));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ updateUnchecked(is);
+ }
+ }
+
+ // INCREASES THE REVISION COUNT.
+ private void updateUnchecked(InputStream is) throws Exception
+ {
+ File revisionDir = null;
+
+ try
+ {
+ // Create the new revision directory.
+ int revision = getRevisionCountUnchecked();
+ revisionDir = new File(
+ m_dir, REVISION_DIRECTORY
+ + getRefreshCount() + "." + revision);
+ if (!revisionDir.mkdir())
+ {
+ throw new IOException("Unable to create revision directory.");
+ }
+
+ // Save the new revision bundle jar file.
+ File file = new File(revisionDir, BUNDLE_JAR_FILE);
+ copy(is, file);
+
+ preprocessBundleJar(revision, revisionDir);
+ }
+ catch (Exception ex)
+ {
+ if ((revisionDir != null) && revisionDir.exists())
+ {
+ try
+ {
+ deleteDirectoryTree(revisionDir);
+ }
+ catch (Exception ex2)
+ {
+ // There is very little we can do here.
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "Unable to remove partial revision directory.", ex2);
+ }
+ }
+ throw ex;
+ }
+
+ // If everything was successful, then update
+ // the revision count.
+ m_revisionCount++;
+ // Clear the cached revision header, since it is
+ // no longer the current revision.
+ m_currentHeader = null;
+ }
+
+ // DECREASES THE REVISION COUNT.
+ protected void purge() throws Exception
+ {
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.PURGE_ACTION, this));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ purgeUnchecked();
+ }
+ }
+
+ // DECREASES THE REVISION COUNT.
+ private void purgeUnchecked() throws Exception
+ {
+ // Get the current update count.
+ long update = getRefreshCount();
+ // Get the current revision count.
+ int count = getRevisionCountUnchecked();
+
+ File revisionDir = null;
+ for (int i = 0; i < count - 1; i++)
+ {
+ revisionDir = new File(m_dir, REVISION_DIRECTORY + update + "." + i);
+ if (revisionDir.exists())
+ {
+ deleteDirectoryTree(revisionDir);
+ }
+ }
+ // Increment the update count.
+ setRefreshCount(update + 1);
+
+ // Rename the current revision to be the current update.
+ File currentDir = new File(m_dir, REVISION_DIRECTORY + (update + 1) + ".0");
+ revisionDir = new File(m_dir, REVISION_DIRECTORY + update + "." + (count - 1));
+ revisionDir.renameTo(currentDir);
+
+ // If everything is successful, then set the revision
+ // count to one.
+ m_revisionCount = 1;
+ // Although the cached current header should stay the same
+ // here, clear it for consistency.
+ m_currentHeader = null;
+ }
+
+ protected void remove() throws Exception
+ {
+ if (System.getSecurityManager() != null)
+ {
+ try
+ {
+ AccessController.doPrivileged(
+ new PrivilegedAction(
+ PrivilegedAction.REMOVE_ACTION, this));
+ }
+ catch (PrivilegedActionException ex)
+ {
+ throw ((PrivilegedActionException) ex).getException();
+ }
+ }
+ else
+ {
+ removeUnchecked();
+ }
+ }
+
+ private void removeUnchecked() throws Exception
+ {
+ deleteDirectoryTree(m_dir);
+ }
+
+ //
+ // Utility class for performing privileged actions.
+ //
+
+ private static class PrivilegedAction implements PrivilegedExceptionAction
+ {
+ private static final int INITIALIZE_ACTION = 0;
+ private static final int UPDATE_ACTION = 1;
+ private static final int PURGE_ACTION = 2;
+ private static final int REMOVE_ACTION = 3;
+ private static final int GET_REVISION_COUNT_ACTION = 4;
+ private static final int GET_LOCATION_ACTION = 5;
+ private static final int GET_PERSISTENT_STATE_ACTION = 6;
+ private static final int SET_PERSISTENT_STATE_ACTION = 7;
+ private static final int GET_START_LEVEL_ACTION = 8;
+ private static final int SET_START_LEVEL_ACTION = 9;
+ private static final int OPEN_BUNDLE_JAR_ACTION = 10;
+ private static final int CREATE_DATA_DIR_ACTION = 11;
+ private static final int GET_CLASS_PATH_ACTION = 12;
+ private static final int GET_ACTIVATOR_ACTION = 13;
+ private static final int SET_ACTIVATOR_ACTION = 14;
+
+ private int m_action = 0;
+ private DefaultBundleArchive m_archive = null;
+ private InputStream m_isArg = null;
+ private int m_intArg = 0;
+ private File m_fileArg = null;
+ private ClassLoader m_loaderArg = null;
+ private Object m_objArg = null;
+
+ public PrivilegedAction(int action, DefaultBundleArchive archive)
+ {
+ m_action = action;
+ m_archive = archive;
+ }
+
+ public PrivilegedAction(int action, DefaultBundleArchive archive, InputStream isArg)
+ {
+ m_action = action;
+ m_archive = archive;
+ m_isArg = isArg;
+ }
+
+ public PrivilegedAction(int action, DefaultBundleArchive archive, int intArg)
+ {
+ m_action = action;
+ m_archive = archive;
+ m_intArg = intArg;
+ }
+
+ public PrivilegedAction(int action, DefaultBundleArchive archive, File fileArg)
+ {
+ m_action = action;
+ m_archive = archive;
+ m_fileArg = fileArg;
+ }
+
+ public PrivilegedAction(int action, DefaultBundleArchive archive, ClassLoader loaderArg)
+ {
+ m_action = action;
+ m_archive = archive;
+ m_loaderArg = loaderArg;
+ }
+
+ public PrivilegedAction(int action, DefaultBundleArchive archive, Object objArg)
+ {
+ m_action = action;
+ m_archive = archive;
+ m_objArg = objArg;
+ }
+
+ public Object run() throws Exception
+ {
+ switch (m_action)
+ {
+ case INITIALIZE_ACTION:
+ m_archive.initializeUnchecked(m_isArg);
+ return null;
+ case UPDATE_ACTION:
+ m_archive.updateUnchecked(m_isArg);
+ return null;
+ case PURGE_ACTION:
+ m_archive.purgeUnchecked();
+ return null;
+ case REMOVE_ACTION:
+ m_archive.removeUnchecked();
+ return null;
+ case GET_REVISION_COUNT_ACTION:
+ return new Integer(m_archive.getRevisionCountUnchecked());
+ case GET_LOCATION_ACTION:
+ return m_archive.getLocationUnchecked();
+ case GET_PERSISTENT_STATE_ACTION:
+ return new Integer(m_archive.getPersistentStateUnchecked());
+ case SET_PERSISTENT_STATE_ACTION:
+ m_archive.setPersistentStateUnchecked(m_intArg);
+ return null;
+ case GET_START_LEVEL_ACTION:
+ return new Integer(m_archive.getStartLevelUnchecked());
+ case SET_START_LEVEL_ACTION:
+ m_archive.setStartLevelUnchecked(m_intArg);
+ return null;
+ case OPEN_BUNDLE_JAR_ACTION:
+ return m_archive.openBundleJarUnchecked(m_fileArg);
+ case CREATE_DATA_DIR_ACTION:
+ m_archive.createDataDirectoryUnchecked(m_fileArg);
+ return null;
+ case GET_CLASS_PATH_ACTION:
+ return m_archive.getClassPathUnchecked(m_intArg);
+ case GET_ACTIVATOR_ACTION:
+ return m_archive.getActivatorUnchecked(m_loaderArg);
+ case SET_ACTIVATOR_ACTION:
+ m_archive.setActivatorUnchecked(m_objArg);
+ return null;
+ }
+
+ throw new IllegalArgumentException("Invalid action specified.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/cache/DefaultBundleCache.java b/src/org/apache/osgi/framework/cache/DefaultBundleCache.java
new file mode 100644
index 0000000..eb6b0b7
--- /dev/null
+++ b/src/org/apache/osgi/framework/cache/DefaultBundleCache.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.cache;
+
+import java.io.*;
+
+import org.apache.osgi.framework.LogWrapper;
+import org.apache.osgi.framework.util.PropertyResolver;
+
+/**
+ * <p>
+ * This class, combined with <tt>DefaultBundleArchive</tt>, implements the
+ * default file system-based bundle cache for Felix. 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.osgi.framework.util.DefaultBundleArchive
+**/
+public class DefaultBundleCache implements 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 PropertyResolver m_cfg = null;
+ private LogWrapper m_logger = null;
+ private File m_profileDir = null;
+ private BundleArchive[] m_archives = null;
+
+ public DefaultBundleCache()
+ {
+ }
+
+ public void initialize(PropertyResolver cfg, LogWrapper logger) throws Exception
+ {
+ // Save Properties reference.
+ m_cfg = cfg;
+ // Save LogService reference.
+ m_logger = logger;
+
+ // Get buffer size value.
+ try
+ {
+ String sBufSize = m_cfg.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 = m_cfg.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 = m_cfg.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 = m_cfg.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 (!m_profileDir.exists())
+ {
+ if (!m_profileDir.mkdirs())
+ {
+ m_logger.log(
+ LogWrapper.LOG_ERROR,
+ "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.
+ File[] children = m_profileDir.listFiles();
+ int count = 0;
+ for (int i = 0; (children != null) && (i < children.length); i++)
+ {
+ // Count the legitimate bundle directories.
+ if (children[i].getName().startsWith(BUNDLE_DIR_PREFIX))
+ {
+ count++;
+ }
+ }
+ m_archives = new BundleArchive[count];
+ count = 0;
+ for (int i = 0; (children != null) && (i < children.length); i++)
+ {
+ // Ignore directories that aren't bundle directories.
+ if (children[i].getName().startsWith(BUNDLE_DIR_PREFIX))
+ {
+ String id = children[i].getName().substring(BUNDLE_DIR_PREFIX.length());
+ m_archives[count] = new DefaultBundleArchive(
+ m_logger, children[i], Long.parseLong(id));
+ count++;
+ }
+ }
+ }
+
+ public BundleArchive[] getArchives()
+ throws Exception
+ {
+ return m_archives;
+ }
+
+ public 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 BundleArchive create(long id, String location, InputStream is)
+ throws Exception
+ {
+ // Define new bundle's directory.
+ File bundleDir = new File(m_profileDir, "bundle" + id);
+
+ try
+ {
+ // Buffer the input stream.
+ is = new BufferedInputStream(is, DefaultBundleCache.BUFSIZE);
+ // Create an archive instance for the new bundle.
+ BundleArchive ba = new DefaultBundleArchive(
+ m_logger, bundleDir, id, location, is);
+ // Add the archive instance to the list of bundle archives.
+ BundleArchive[] bas = new BundleArchive[m_archives.length + 1];
+ System.arraycopy(m_archives, 0, bas, 0, m_archives.length);
+ bas[m_archives.length] = ba;
+ m_archives = bas;
+ return ba;
+ }
+ finally
+ {
+ if (is != null) is.close();
+ }
+ }
+
+ public void update(BundleArchive ba, InputStream is)
+ throws Exception
+ {
+ try
+ {
+ // Buffer the input stream.
+ is = new BufferedInputStream(is, DefaultBundleCache.BUFSIZE);
+ // Do the update.
+ ((DefaultBundleArchive) ba).update(is);
+ }
+ finally
+ {
+ if (is != null) is.close();
+ }
+ }
+
+ public void purge(BundleArchive ba)
+ throws Exception
+ {
+ ((DefaultBundleArchive) ba).purge();
+ }
+
+ public void remove(BundleArchive ba)
+ throws Exception
+ {
+ ((DefaultBundleArchive) ba).remove();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/ext/FelixBundleContext.java b/src/org/apache/osgi/framework/ext/FelixBundleContext.java
new file mode 100644
index 0000000..e1d6cc9
--- /dev/null
+++ b/src/org/apache/osgi/framework/ext/FelixBundleContext.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.ext;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+
+public interface FelixBundleContext extends BundleContext
+{
+ public void addImportPackage() throws BundleException;
+ public void removeImportPackage() throws BundleException;
+ public void addExportPackage() throws BundleException;
+ public void removeExportPackage() throws BundleException;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/Artifact.java b/src/org/apache/osgi/framework/installer/Artifact.java
new file mode 100644
index 0000000..42f8227
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/Artifact.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+public interface Artifact
+{
+ public StringProperty getSourceName();
+ public StringProperty getDestinationDirectory();
+ public InputStream getInputStream(Status status) throws IOException;
+ public boolean localize();
+ public boolean process(Status status, Map propMap);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/BooleanProperty.java b/src/org/apache/osgi/framework/installer/BooleanProperty.java
new file mode 100644
index 0000000..f470bce
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/BooleanProperty.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer;
+
+public interface BooleanProperty extends Property
+{
+ public boolean getBooleanValue();
+ public void setBooleanValue(boolean b);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/Install.java b/src/org/apache/osgi/framework/installer/Install.java
new file mode 100644
index 0000000..7e6eae6
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/Install.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.border.BevelBorder;
+
+import org.apache.osgi.framework.installer.artifact.*;
+import org.apache.osgi.framework.installer.editor.BooleanEditor;
+import org.apache.osgi.framework.installer.editor.FileEditor;
+import org.apache.osgi.framework.installer.property.*;
+import org.apache.osgi.framework.util.FelixConstants;
+
+public class Install extends JFrame
+{
+ private static transient final String PROPERTY_FILE = "property.xml";
+ private static transient final String ARTIFACT_FILE = "artifact.xml";
+
+ public static transient final String JAVA_DIR = "Java directory";
+ public static transient final String INSTALL_DIR = "Install directory";
+
+ private PropertyPanel m_propPanel = null;
+ private JButton m_okayButton = null;
+ private JButton m_cancelButton = null;
+ private JLabel m_statusLabel = null;
+
+ private java.util.List m_propList = null;
+ private java.util.List m_artifactList = null;
+
+ public Install()
+ throws Exception
+ {
+ super("Install");
+
+ // Load properties before resources, because resources
+ // refer to properties.
+ m_propList = loadPropertyList();
+ m_artifactList = loadArtifactList();
+
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(
+ m_propPanel = new PropertyPanel(m_propList), BorderLayout.CENTER);
+ getContentPane().add(createButtonPanel(), BorderLayout.SOUTH);
+ pack();
+ setResizable(true);
+ centerWindow(this);
+
+ // Make window closeable.
+ setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+ addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent event)
+ {
+ doCancel();
+ }
+ });
+ }
+
+ public java.util.List loadPropertyList()
+ {
+ String installDir = System.getProperty("user.home");
+ if (!installDir.endsWith(File.separator))
+ {
+ installDir = installDir + File.separator;
+ }
+
+ Property prop = null;
+
+ // Eventually these should be read from a file.
+ java.util.List list = new ArrayList();
+
+ // Add the shell choice property.
+ prop = new BooleanPropertyImpl("Shell", true);
+ prop.setEditor(new BooleanEditor((BooleanProperty) prop, "Text", "GUI"));
+ list.add(prop);
+
+ // Add the java directory property.
+ prop = new StringPropertyImpl(JAVA_DIR, System.getProperty("java.home"));
+ prop.setEditor(new FileEditor((StringProperty) prop, true));
+ list.add(prop);
+
+ // Add the installation directory property.
+ prop = new StringPropertyImpl(INSTALL_DIR, installDir + "Felix");
+ prop.setEditor(new FileEditor((StringProperty) prop, true));
+ list.add(prop);
+
+ // Add the documentation URL property.
+ prop = new BooleanStringPropertyImpl(
+ "User documentation",
+ true,
+ "http://download.forge.objectweb.org/oscar/oscar-doc-"
+ + FelixConstants.FELIX_VERSION_VALUE + ".jar");
+ list.add(prop);
+
+ // Add the documentation URL property.
+ prop = new BooleanStringPropertyImpl(
+ "API documentation",
+ true,
+ "http://download.forge.objectweb.org/oscar/oscar-api-"
+ + FelixConstants.FELIX_VERSION_VALUE + ".jar");
+ list.add(prop);
+
+ return list;
+ }
+
+ public java.util.List loadArtifactList() throws Exception
+ {
+ // Eventually I will changed these to be read from a file.
+ java.util.List list = new ArrayList();
+ list.add(
+ new ArtifactHolder(
+ (BooleanProperty) getProperty("User documentation"),
+ new URLJarArtifact(
+ (StringProperty) getProperty("User documentation"))));
+ list.add(
+ new ArtifactHolder(
+ (BooleanProperty) getProperty("API documentation"),
+ new URLJarArtifact(
+ (StringProperty) getProperty("API documentation"))));
+ list.add(
+ new ArtifactHolder(
+ new ResourceJarArtifact(
+ new StringPropertyImpl("sourceName", "package.jar"))));
+ list.add(
+ new ArtifactHolder(
+ new ResourceFileArtifact(
+ new StringPropertyImpl("sourceName", "src.jar"))));
+ list.add(
+ new ArtifactHolder(
+ new ResourceFileArtifact(
+ new StringPropertyImpl("sourceName", "LICENSE.txt"))));
+ list.add(
+ new ArtifactHolder(
+ (BooleanProperty) getProperty("Shell"),
+ new ResourceFileArtifact(
+ new StringPropertyImpl("sourceName", "config.properties.text"),
+ new StringPropertyImpl("destName", "config.properties"),
+ new StringPropertyImpl("destDir", "lib"))));
+ list.add(
+ new ArtifactHolder(
+ new NotBooleanPropertyImpl((BooleanProperty) getProperty("Shell")),
+ new ResourceFileArtifact(
+ new StringPropertyImpl("sourceName", "config.properties.gui"),
+ new StringPropertyImpl("destName", "config.properties"),
+ new StringPropertyImpl("destDir", "lib"))));
+ list.add(
+ new ArtifactHolder(
+ new ResourceFileArtifact(
+ new StringPropertyImpl("sourceName", "example.policy"))));
+ list.add(
+ new ArtifactHolder(
+ new ResourceFileArtifact(
+ new StringPropertyImpl("sourceName", "felix.bat"),
+ new StringPropertyImpl("destName" , "felix.bat"),
+ new StringPropertyImpl("destDir", ""),
+ true)));
+ list.add(
+ new ArtifactHolder(
+ new ResourceFileArtifact(
+ new StringPropertyImpl("sourceName", "felix.sh"),
+ new StringPropertyImpl("destName" , "felix.sh"),
+ new StringPropertyImpl("destDir", ""),
+ true)));
+
+ return list;
+ }
+
+ private Property getProperty(String name)
+ {
+ for (int i = 0; i < m_propList.size(); i++)
+ {
+ Property prop = (Property) m_propList.get(i);
+ if (prop.getName().equals(name))
+ {
+ return prop;
+ }
+ }
+ return null;
+ }
+
+ protected void doOkay()
+ {
+ m_propPanel.setEnabled(false);
+ m_okayButton.setEnabled(false);
+ m_cancelButton.setEnabled(false);
+ new Thread(new InstallRunnable()).start();
+ }
+
+ protected void doCancel()
+ {
+ System.exit(0);
+ }
+
+ protected JPanel createButtonPanel()
+ {
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
+
+ // Create and set layout.
+ GridBagLayout grid = new GridBagLayout();
+ GridBagConstraints c = new GridBagConstraints();
+
+ buttonPanel.setLayout(grid);
+
+ // Create labels and fields.
+ c.insets = new Insets(2, 2, 2, 2);
+
+ // Okay button.
+ c.gridx = 0;
+ c.gridy = 0;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.anchor = GridBagConstraints.EAST;
+ grid.setConstraints(m_okayButton = new JButton("OK"), c);
+ buttonPanel.add(m_okayButton);
+ m_okayButton.setDefaultCapable(true);
+ getRootPane().setDefaultButton(m_okayButton);
+
+ // Cancel button.
+ c.gridx = 1;
+ c.gridy = 0;
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.anchor = GridBagConstraints.WEST;
+ grid.setConstraints(m_cancelButton = new JButton("Cancel"), c);
+ buttonPanel.add(m_cancelButton);
+
+ // Add action listeners.
+ m_okayButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event)
+ {
+ doOkay();
+ }
+ });
+
+ m_cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event)
+ {
+ doCancel();
+ }
+ });
+
+ // Status label.
+ m_statusLabel = new JLabel("Felix installation");
+ m_statusLabel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
+
+ // Complete panel.
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.add(buttonPanel, BorderLayout.CENTER);
+ panel.add(m_statusLabel, BorderLayout.SOUTH);
+ return panel;
+ }
+
+ public static void centerWindow(Component window)
+ {
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ Dimension dim = toolkit.getScreenSize();
+ int screenWidth = dim.width;
+ int screenHeight = dim.height;
+ int x = (screenWidth - window.getSize().width) / 2;
+ int y = (screenHeight - window.getSize().height) / 2;
+ window.setLocation(x, y);
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ String msg = "<html>"
+ + "<center><h1>Felix " + FelixConstants.FELIX_VERSION_VALUE + "</h1></center>"
+ + "You can download example bundles at the Felix shell prompt by<br>"
+ + "using the <b><tt>obr</tt></b> command to access the OSGi Bundle Repository;<br>"
+ + "type <b><tt>obr help</tt></b> at the Felix shell prompt for details."
+ + "</html>";
+ JLabel label = new JLabel(msg);
+ label.setFont(new Font("SansSerif", Font.PLAIN, 11));
+ final JDialog dlg = new JDialog((Frame) null, "Felix Install", true);
+ dlg.getContentPane().setLayout(new BorderLayout(10, 10));
+ dlg.getContentPane().add(label, BorderLayout.CENTER);
+ JPanel panel = new JPanel();
+ JButton button = new JButton("OK");
+ button.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event)
+ {
+ dlg.hide();
+ }
+ });
+ panel.add(button);
+ dlg.getContentPane().add(panel, BorderLayout.SOUTH);
+ // For spacing purposes...
+ dlg.getContentPane().add(new JPanel(), BorderLayout.NORTH);
+ dlg.getContentPane().add(new JPanel(), BorderLayout.EAST);
+ dlg.getContentPane().add(new JPanel(), BorderLayout.WEST);
+ dlg.pack();
+ centerWindow(dlg);
+ dlg.show();
+
+ Install obj = new Install();
+ obj.setVisible(true);
+ }
+
+ class InstallRunnable implements Runnable
+ {
+ public void run()
+ {
+ Map propMap = new HashMap();
+ for (int i = 0; i < m_propList.size(); i++)
+ {
+ Property prop = (Property) m_propList.get(i);
+ propMap.put(prop.getName(), prop);
+ }
+
+ String installDir = ((StringProperty) propMap.get(INSTALL_DIR)).getStringValue();
+
+ // Make sure the install directory ends with separator char.
+ if (!installDir.endsWith(File.separator))
+ {
+ installDir = installDir + File.separator;
+ }
+
+ // Make sure the install directory exists and
+ // that is actually a directory.
+ File file = new File(installDir);
+ if (!file.exists())
+ {
+ if (!file.mkdirs())
+ {
+ JOptionPane.showMessageDialog(Install.this,
+ "Unable to create install directory.",
+ "Error", JOptionPane.ERROR_MESSAGE);
+ System.exit(-1);
+ }
+ }
+ else if (!file.isDirectory())
+ {
+ JOptionPane.showMessageDialog(Install.this,
+ "The selected install location is not a directory.",
+ "Error", JOptionPane.ERROR_MESSAGE);
+ System.exit(-1);
+ }
+
+ // Status updater runnable.
+ StatusRunnable sr = new StatusRunnable();
+
+ // Loop through and process resources.
+ for (int i = 0; i < m_artifactList.size(); i++)
+ {
+ ArtifactHolder ah = (ArtifactHolder) m_artifactList.get(i);
+ if (ah.isIncluded())
+ {
+ if (!ah.getArtifact().process(sr, propMap))
+ {
+ JOptionPane.showMessageDialog(Install.this,
+ "An error occurred while processing the resources.",
+ "Error", JOptionPane.ERROR_MESSAGE);
+ System.exit(-1);
+ }
+ }
+ }
+
+ System.exit(0);
+ }
+ }
+
+ class StatusRunnable implements Status, Runnable
+ {
+ private String text = null;
+
+ public void setText(String s)
+ {
+ text = s;
+ try {
+ SwingUtilities.invokeAndWait(this);
+ } catch (Exception ex) {
+ // Ignore.
+ }
+ }
+
+ public void run()
+ {
+ m_statusLabel.setText(text);
+ }
+ }
+
+ // Re-usable static member for ResourceHolder inner class.
+ private static BooleanProperty m_trueProp =
+ new BooleanPropertyImpl("mandatory", true);
+
+ class ArtifactHolder
+ {
+ private BooleanProperty m_isIncluded = null;
+ private Artifact m_artifact = null;
+
+ public ArtifactHolder(Artifact artifact)
+ {
+ this(m_trueProp, artifact);
+ }
+
+ public ArtifactHolder(BooleanProperty isIncluded, Artifact artifact)
+ {
+ m_isIncluded = isIncluded;
+ m_artifact = artifact;
+ }
+
+ public boolean isIncluded()
+ {
+ return m_isIncluded.getBooleanValue();
+ }
+
+ public Artifact getArtifact()
+ {
+ return m_artifact;
+ }
+ }
+}
diff --git a/src/org/apache/osgi/framework/installer/Property.java b/src/org/apache/osgi/framework/installer/Property.java
new file mode 100644
index 0000000..7c52c46
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/Property.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer;
+
+import javax.swing.JComponent;
+
+public interface Property
+{
+ public String getName();
+ public JComponent getEditor();
+ public void setEditor(JComponent comp);
+ public String toString();
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/PropertyPanel.java b/src/org/apache/osgi/framework/installer/PropertyPanel.java
new file mode 100644
index 0000000..46dd4a1
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/PropertyPanel.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.*;
+
+public class PropertyPanel extends JPanel
+{
+ private List m_propList = null;
+ private Map m_propToCompMap = null;
+
+ public PropertyPanel(List paramList)
+ {
+ super();
+ m_propList = paramList;
+ m_propToCompMap = new HashMap();
+ layoutComponents();
+ }
+
+ public void setEnabled(boolean b)
+ {
+ for (int i = 0; i < m_propList.size(); i++)
+ {
+ Property prop = (Property) m_propList.get(i);
+ JComponent comp = (JComponent) m_propToCompMap.get(prop.getName());
+ comp.setEnabled(b);
+ }
+ }
+
+ public List getProperties()
+ {
+ return m_propList;
+ }
+
+ protected void layoutComponents()
+ {
+ // Create the field panel for entering query variables.
+ GridBagLayout grid = new GridBagLayout();
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.insets = new Insets(2, 2, 2, 2);
+ setLayout(grid);
+
+ for (int i = 0; i < m_propList.size(); i++)
+ {
+ Property prop = (Property) m_propList.get(i);
+ JLabel label = null;
+ JComponent component = null;
+
+ // Add label.
+ gbc.gridx = 0;
+ gbc.gridy = i;
+ gbc.gridheight = 1;
+ gbc.gridwidth = 1;
+ gbc.anchor = GridBagConstraints.EAST;
+ grid.setConstraints(label = new JLabel(prop.getName()), gbc);
+ add(label);
+
+ gbc.gridx = 1;
+ gbc.gridy = i;
+ gbc.gridheight = 1;
+ gbc.gridwidth = 3;
+ gbc.anchor = GridBagConstraints.WEST;
+ grid.setConstraints(component = prop.getEditor(), gbc);
+ add(component);
+
+ m_propToCompMap.put(prop.getName(), component);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/Status.java b/src/org/apache/osgi/framework/installer/Status.java
new file mode 100644
index 0000000..7070422
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/Status.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer;
+
+public interface Status
+{
+ public void setText(String s);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/StringProperty.java b/src/org/apache/osgi/framework/installer/StringProperty.java
new file mode 100644
index 0000000..2597d47
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/StringProperty.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer;
+
+public interface StringProperty extends Property
+{
+ public String getStringValue();
+ public void setStringValue(String s);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/AbstractArtifact.java b/src/org/apache/osgi/framework/installer/artifact/AbstractArtifact.java
new file mode 100644
index 0000000..c3ee33b
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/AbstractArtifact.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.artifact;
+
+import java.io.*;
+import java.util.Map;
+
+import org.apache.osgi.framework.installer.*;
+import org.apache.osgi.framework.installer.property.StringPropertyImpl;
+
+public abstract class AbstractArtifact implements Artifact
+{
+ private StringProperty m_sourceName = null;
+ private StringProperty m_destDir = null;
+ private boolean m_localize = false;
+
+ // This following shared buffer assumes that there is
+ // no concurrency when processing resources.
+ protected static byte[] s_buffer = new byte[2048];
+
+ public AbstractArtifact(
+ StringProperty sourceName, StringProperty destDir, boolean localize)
+ {
+ if (destDir == null)
+ {
+ destDir = new StringPropertyImpl("empty", "");
+ }
+ m_sourceName = sourceName;
+ m_destDir = destDir;
+ m_localize = localize;
+ }
+
+ public StringProperty getSourceName()
+ {
+ return m_sourceName;
+ }
+
+ public StringProperty getDestinationDirectory()
+ {
+ return m_destDir;
+ }
+
+ public boolean localize()
+ {
+ return m_localize;
+ }
+
+ protected static void copy(
+ InputStream is, String installDir, String destName, String destDir)
+ throws IOException
+ {
+ if (destDir == null)
+ {
+ destDir = "";
+ }
+
+ // Make sure the target directory exists and
+ // that is actually a directory.
+ File targetDir = new File(installDir, destDir);
+ if (!targetDir.exists())
+ {
+ if (!targetDir.mkdirs())
+ {
+ throw new IOException("Unable to create target directory: "
+ + targetDir);
+ }
+ }
+ else if (!targetDir.isDirectory())
+ {
+ throw new IOException("Target is not a directory: "
+ + targetDir);
+ }
+
+ BufferedOutputStream bos = new BufferedOutputStream(
+ new FileOutputStream(new File(targetDir, destName)));
+ int count = 0;
+ while ((count = is.read(s_buffer)) > 0)
+ {
+ bos.write(s_buffer, 0, count);
+ }
+ bos.close();
+ }
+
+ protected static void copyAndLocalize(
+ InputStream is, String installDir, String destName,
+ String destDir, Map propMap)
+ throws IOException
+ {
+ if (destDir == null)
+ {
+ destDir = "";
+ }
+
+ // Make sure the target directory exists and
+ // that is actually a directory.
+ File targetDir = new File(installDir, destDir);
+ if (!targetDir.exists())
+ {
+ if (!targetDir.mkdirs())
+ {
+ throw new IOException("Unable to create target directory: "
+ + targetDir);
+ }
+ }
+ else if (!targetDir.isDirectory())
+ {
+ throw new IOException("Target is not a directory: "
+ + targetDir);
+ }
+
+ BufferedOutputStream bos = new BufferedOutputStream(
+ new FileOutputStream(new File(targetDir, destName)));
+ int i = 0;
+ while ((i = is.read()) > 0)
+ {
+ // Parameters start with "%%", so check to see if
+ // we have a parameter.
+ if ((char)i == '%')
+ {
+ // One of three possibilities, we have a parameter,
+ // we have an end of file, or we don't have a parameter.
+ int i2 = is.read();
+ if ((char) i2 == '%')
+ {
+ Object obj = readParameter(is);
+
+ // If the byte sequence was not a parameter afterall,
+ // then a byte array is returned, otherwise a string
+ // containing the parameter m_name is returned.
+ if (obj instanceof byte[])
+ {
+ bos.write(i);
+ bos.write(i2);
+ bos.write((byte[]) obj);
+ }
+ else
+ {
+ Property prop = (Property) propMap.get(obj);
+ String value = (prop == null) ? "" : prop.toString();
+ bos.write(value.getBytes());
+ }
+ }
+ else if (i2 == -1)
+ {
+ bos.write(i);
+ }
+ else
+ {
+ bos.write(i);
+ bos.write(i2);
+ }
+ }
+ else
+ {
+ bos.write(i);
+ }
+ }
+ bos.close();
+ }
+
+ protected static Object readParameter(InputStream is)
+ throws IOException
+ {
+ int count = 0;
+ int i = 0;
+ while ((count < s_buffer.length) && ((i = is.read()) > 0))
+ {
+ if ((char) i == '%')
+ {
+ // One of three possibilities, we have the end of
+ // the parameter, we have an end of file, or we
+ // don't have the parameter end.
+ int i2 = is.read();
+ if ((char) i2 == '%')
+ {
+ return new String(s_buffer, 0, count);
+ }
+ else if (i2 == -1)
+ {
+ s_buffer[count] = (byte) i;
+ byte[] b = new byte[count];
+ for (int j = 0; j < count; j++)
+ b[j] = s_buffer[j];
+ return b;
+ }
+ else
+ {
+ s_buffer[count++] = (byte) i;
+ s_buffer[count++] = (byte) i2;
+ }
+ }
+ else
+ {
+ s_buffer[count++] = (byte) i;
+ }
+ }
+
+ byte[] b = new byte[count - 1];
+ for (int j = 0; j < (count - 1); j++)
+ b[j] = s_buffer[j];
+
+ return b;
+ }
+
+ public static String getPath(String s, char separator)
+ {
+ return (s.lastIndexOf(separator) < 0)
+ ? "" : s.substring(0, s.lastIndexOf(separator));
+ }
+
+ public static String getPathHead(String s, char separator)
+ {
+ return (s.lastIndexOf(separator) < 0)
+ ? s : s.substring(s.lastIndexOf(separator) + 1);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/AbstractFileArtifact.java b/src/org/apache/osgi/framework/installer/artifact/AbstractFileArtifact.java
new file mode 100644
index 0000000..4b46338
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/AbstractFileArtifact.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.artifact;
+
+import java.io.InputStream;
+import java.util.Map;
+
+import org.apache.osgi.framework.installer.*;
+
+public abstract class AbstractFileArtifact extends AbstractArtifact
+{
+ private StringProperty m_destName = null;
+
+ public AbstractFileArtifact(StringProperty sourceName)
+ {
+ this(sourceName, sourceName);
+ }
+
+ public AbstractFileArtifact(StringProperty sourceName, StringProperty destName)
+ {
+ this(sourceName, destName, null);
+ }
+
+ public AbstractFileArtifact(
+ StringProperty sourceName, StringProperty destName, StringProperty destDir)
+ {
+ this(sourceName, destName, destDir, false);
+ }
+
+ public AbstractFileArtifact(
+ StringProperty sourceName, StringProperty destName,
+ StringProperty destDir, boolean localize)
+ {
+ super(sourceName, destDir, localize);
+ m_destName = destName;
+ }
+
+ public StringProperty getDestinationName()
+ {
+ return m_destName;
+ }
+
+ public boolean process(Status status, Map propMap)
+ {
+ String installDir =
+ ((StringProperty) propMap.get(Install.INSTALL_DIR)).getStringValue();
+
+ try
+ {
+ InputStream is = getInputStream(status);
+
+ if (is == null)
+ {
+ return true;
+ }
+
+ if (localize())
+ {
+ status.setText("Copying and configuring "
+ + getSourceName().getStringValue());
+ copyAndLocalize(
+ is,
+ installDir,
+ getDestinationName().getStringValue(),
+ getDestinationDirectory().getStringValue(),
+ propMap);
+ }
+ else
+ {
+ status.setText("Copying " + getSourceName().getStringValue());
+ copy(
+ is,
+ installDir,
+ getDestinationName().getStringValue(),
+ getDestinationDirectory().getStringValue());
+ }
+
+ is.close();
+
+ }
+ catch (Exception ex)
+ {
+ System.err.println(ex);
+ return false;
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/AbstractJarArtifact.java b/src/org/apache/osgi/framework/installer/artifact/AbstractJarArtifact.java
new file mode 100644
index 0000000..fef3316
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/AbstractJarArtifact.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.artifact;
+
+import java.io.*;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+import org.apache.osgi.framework.installer.*;
+
+public abstract class AbstractJarArtifact extends AbstractArtifact
+{
+ public AbstractJarArtifact(StringProperty sourceName)
+ {
+ this(sourceName, null);
+ }
+
+ public AbstractJarArtifact(StringProperty sourceName, StringProperty destDir)
+ {
+ this(sourceName, destDir, false);
+ }
+
+ public AbstractJarArtifact(
+ StringProperty sourceName, StringProperty destDir, boolean localize)
+ {
+ super(sourceName, destDir, localize);
+ }
+
+ public boolean process(Status status, Map propMap)
+ {
+ try
+ {
+ InputStream is = getInputStream(status);
+
+ if (is == null)
+ {
+ return true;
+ }
+
+ JarInputStream jis = new JarInputStream(is);
+ status.setText("Extracting...");
+ unjar(jis, propMap);
+ jis.close();
+ }
+ catch (Exception ex)
+ {
+ System.err.println(this);
+ System.err.println(ex);
+ return false;
+ }
+
+ return true;
+ }
+
+ protected void unjar(JarInputStream jis, Map propMap)
+ throws IOException
+ {
+ String installDir =
+ ((StringProperty) propMap.get(Install.INSTALL_DIR)).getStringValue();
+
+ // Loop through JAR entries.
+ for (JarEntry je = jis.getNextJarEntry();
+ je != null;
+ je = jis.getNextJarEntry())
+ {
+ if (je.getName().startsWith("/"))
+ {
+ throw new IOException("JAR resource cannot contain absolute paths.");
+ }
+
+ File target =
+ new File(installDir, getDestinationDirectory().getStringValue());
+ target = new File(target, je.getName());
+
+ // Check to see if the JAR entry is a directory.
+ if (je.isDirectory())
+ {
+ if (!target.exists())
+ {
+ if (!target.mkdirs())
+ {
+ throw new IOException("Unable to create target directory: "
+ + target);
+ }
+ }
+ // Just continue since directories do not have content to copy.
+ continue;
+ }
+
+ int lastIndex = je.getName().lastIndexOf('/');
+ String name = (lastIndex >= 0) ?
+ je.getName().substring(lastIndex + 1) : je.getName();
+ String destination = (lastIndex >= 0) ?
+ je.getName().substring(0, lastIndex) : "";
+
+ // JAR files use '/', so convert it to platform separator.
+ destination = destination.replace('/', File.separatorChar);
+
+ if (localize())
+ {
+ copyAndLocalize(jis, installDir, name, destination, propMap);
+ }
+ else
+ {
+ copy(jis, installDir, name, destination);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/ResourceFileArtifact.java b/src/org/apache/osgi/framework/installer/artifact/ResourceFileArtifact.java
new file mode 100644
index 0000000..599df964
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/ResourceFileArtifact.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.artifact;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.osgi.framework.installer.Status;
+import org.apache.osgi.framework.installer.StringProperty;
+import org.apache.osgi.framework.installer.resource.ResourceLoader;
+
+public class ResourceFileArtifact extends AbstractFileArtifact
+{
+ public ResourceFileArtifact(StringProperty sourceName)
+ {
+ this(sourceName, sourceName);
+ }
+
+ public ResourceFileArtifact(StringProperty sourceName, StringProperty destName)
+ {
+ this(sourceName, destName, null);
+ }
+
+ public ResourceFileArtifact(
+ StringProperty sourceName, StringProperty destName, StringProperty destDir)
+ {
+ this(sourceName, destName, destDir, false);
+ }
+
+ public ResourceFileArtifact(
+ StringProperty sourceName, StringProperty destName,
+ StringProperty destDir, boolean localize)
+ {
+ super(sourceName, destName, destDir, localize);
+ }
+
+ public InputStream getInputStream(Status status)
+ throws IOException
+ {
+ return ResourceLoader.getResourceAsStream(getSourceName().getStringValue());
+ }
+
+ public String toString()
+ {
+ return "RESOURCE FILE: " + getSourceName().getStringValue();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/ResourceJarArtifact.java b/src/org/apache/osgi/framework/installer/artifact/ResourceJarArtifact.java
new file mode 100644
index 0000000..59faea3
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/ResourceJarArtifact.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.artifact;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.osgi.framework.installer.Status;
+import org.apache.osgi.framework.installer.StringProperty;
+import org.apache.osgi.framework.installer.resource.ResourceLoader;
+
+public class ResourceJarArtifact extends AbstractJarArtifact
+{
+ public ResourceJarArtifact(StringProperty sourceName)
+ {
+ this(sourceName, null);
+ }
+
+ public ResourceJarArtifact(StringProperty sourceName, StringProperty destDir)
+ {
+ this(sourceName, destDir, false);
+ }
+
+ public ResourceJarArtifact(
+ StringProperty sourceName, StringProperty destDir, boolean localize)
+ {
+ super(sourceName, destDir, localize);
+ }
+
+ public InputStream getInputStream(Status status)
+ throws IOException
+ {
+ return ResourceLoader.getResourceAsStream(getSourceName().getStringValue());
+ }
+
+ public String toString()
+ {
+ return "RESOURCE JAR: " + getSourceName().getStringValue();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/URLFileArtifact.java b/src/org/apache/osgi/framework/installer/artifact/URLFileArtifact.java
new file mode 100644
index 0000000..b3729f0
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/URLFileArtifact.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.artifact;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.apache.osgi.framework.installer.Status;
+import org.apache.osgi.framework.installer.StringProperty;
+
+public class URLFileArtifact extends AbstractFileArtifact
+{
+ public URLFileArtifact(StringProperty sourceName, StringProperty destName)
+ {
+ this(sourceName, destName, null);
+ }
+
+ public URLFileArtifact(
+ StringProperty sourceName, StringProperty destName, StringProperty destDir)
+ {
+ this(sourceName, destName, destDir, false);
+ }
+
+ public URLFileArtifact(
+ StringProperty sourceName, StringProperty destName,
+ StringProperty destDir, boolean localize)
+ {
+ super(sourceName, destName, destDir, localize);
+ }
+
+ public InputStream getInputStream(Status status)
+ throws IOException
+ {
+ String fileName = getSourceName().getStringValue();
+ fileName = (fileName.lastIndexOf('/') > 0)
+ ? fileName.substring(fileName.lastIndexOf('/') + 1)
+ : fileName;
+
+ status.setText("Connecting...");
+
+ File file = File.createTempFile("felix-install.tmp", null);
+ file.deleteOnExit();
+
+ OutputStream os = new FileOutputStream(file);
+ URLConnection conn = new URL(getSourceName().getStringValue()).openConnection();
+ int total = conn.getContentLength();
+ InputStream is = conn.getInputStream();
+
+ int count = 0;
+ for (int len = is.read(s_buffer); len > 0; len = is.read(s_buffer))
+ {
+ count += len;
+ os.write(s_buffer, 0, len);
+ if (total > 0)
+ {
+ status.setText("Downloading " + fileName
+ + " ( " + count + " bytes of " + total + " ).");
+ }
+ else
+ {
+ status.setText("Downloading " + fileName + " ( " + count + " bytes ).");
+ }
+ }
+
+ os.close();
+ is.close();
+
+ return new FileInputStream(file);
+ }
+
+ public String toString()
+ {
+ return "URL FILE: " + getSourceName().getStringValue();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/artifact/URLJarArtifact.java b/src/org/apache/osgi/framework/installer/artifact/URLJarArtifact.java
new file mode 100644
index 0000000..21e4751
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/artifact/URLJarArtifact.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.artifact;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.apache.osgi.framework.installer.Status;
+import org.apache.osgi.framework.installer.StringProperty;
+
+public class URLJarArtifact extends AbstractJarArtifact
+{
+ public URLJarArtifact(StringProperty sourceName)
+ {
+ this(sourceName, null);
+ }
+
+ public URLJarArtifact(StringProperty sourceName, StringProperty destDir)
+ {
+ this(sourceName, destDir, false);
+ }
+
+ public URLJarArtifact(
+ StringProperty sourceName, StringProperty destDir, boolean localize)
+ {
+ super(sourceName, destDir, localize);
+ }
+
+ public InputStream getInputStream(Status status)
+ throws IOException
+ {
+ String fileName = getSourceName().getStringValue();
+ fileName = (fileName.lastIndexOf('/') > 0)
+ ? fileName.substring(fileName.lastIndexOf('/') + 1)
+ : fileName;
+
+ status.setText("Connecting...");
+
+ File file = File.createTempFile("felix-install.tmp", null);
+ file.deleteOnExit();
+
+ OutputStream os = new FileOutputStream(file);
+ URLConnection conn = new URL(getSourceName().getStringValue()).openConnection();
+ int total = conn.getContentLength();
+ InputStream is = conn.getInputStream();
+
+ int count = 0;
+ for (int len = is.read(s_buffer); len > 0; len = is.read(s_buffer))
+ {
+ count += len;
+ os.write(s_buffer, 0, len);
+ if (total > 0)
+ {
+ status.setText("Downloading " + fileName
+ + " ( " + count + " bytes of " + total + " ).");
+ }
+ else
+ {
+ status.setText("Downloading " + fileName + " ( " + count + " bytes ).");
+ }
+ }
+
+ os.close();
+ is.close();
+
+ return new FileInputStream(file);
+ }
+
+ public String toString()
+ {
+ return "URL JAR: " + getSourceName().getStringValue();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/editor/BooleanEditor.java b/src/org/apache/osgi/framework/installer/editor/BooleanEditor.java
new file mode 100644
index 0000000..e11a654
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/editor/BooleanEditor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.editor;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.*;
+
+import org.apache.osgi.framework.installer.BooleanProperty;
+import org.apache.osgi.framework.installer.Property;
+
+public class BooleanEditor extends JPanel
+{
+ private BooleanProperty m_prop = null;
+ private JRadioButton m_trueButton = null;
+ private JRadioButton m_falseButton = null;
+ private String m_trueString = null;
+ private String m_falseString = null;
+
+ public BooleanEditor(BooleanProperty prop)
+ {
+ this(prop, "true", "false");
+ }
+
+ public BooleanEditor(BooleanProperty prop, String trueString, String falseString)
+ {
+ m_prop = prop;
+ m_trueString = trueString;
+ m_falseString = falseString;
+ init();
+ }
+
+ public Property getProperty()
+ {
+ return m_prop;
+ }
+
+ public void setEnabled(boolean b)
+ {
+ m_trueButton.setEnabled(b);
+ m_falseButton.setEnabled(b);
+ }
+
+ protected void init()
+ {
+ add(m_trueButton = new JRadioButton(m_trueString));
+ add(m_falseButton = new JRadioButton(m_falseString));
+ ButtonGroup group = new ButtonGroup();
+ group.add(m_trueButton);
+ group.add(m_falseButton);
+ if (m_prop.getBooleanValue())
+ {
+ m_trueButton.setSelected(true);
+ }
+ else
+ {
+ m_falseButton.setSelected(true);
+ }
+
+ // Add action listeners.
+ m_trueButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event)
+ {
+ m_prop.setBooleanValue(true);
+ }
+ });
+ m_falseButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event)
+ {
+ m_prop.setBooleanValue(false);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/editor/BooleanStringEditor.java b/src/org/apache/osgi/framework/installer/editor/BooleanStringEditor.java
new file mode 100644
index 0000000..f64a96e
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/editor/BooleanStringEditor.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.editor;
+
+import java.awt.*;
+import java.awt.event.*;
+
+import javax.swing.*;
+
+import org.apache.osgi.framework.installer.*;
+
+public class BooleanStringEditor extends JPanel
+{
+ private Property m_prop = null;
+ private JCheckBox m_includeButton = null;
+ private JTextField m_textField = null;
+
+ public BooleanStringEditor(Property prop)
+ {
+ if ((prop instanceof BooleanProperty) && (prop instanceof StringProperty))
+ {
+ m_prop = prop;
+ }
+ else
+ {
+ throw new IllegalArgumentException(
+ "Property must implement both boolean and string property interfaces.");
+ }
+ init();
+ }
+
+ public Property getProperty()
+ {
+ return m_prop;
+ }
+
+ public void setEnabled(boolean b)
+ {
+ m_includeButton.setEnabled(b);
+ m_textField.setEnabled(b && m_includeButton.isSelected());
+ }
+
+ protected void init()
+ {
+ // Set layout.
+ GridBagLayout grid = new GridBagLayout();
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.insets = new Insets(0, 2, 0, 2);
+ setLayout(grid);
+
+ // Add button.
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.gridheight = 1;
+ gbc.gridwidth = 1;
+ gbc.anchor = GridBagConstraints.WEST;
+ m_includeButton = new JCheckBox("");
+ grid.setConstraints(m_includeButton, gbc);
+ add(m_includeButton);
+
+ // Add field.
+ gbc.gridx = 1;
+ gbc.gridy = 0;
+ gbc.gridheight = 1;
+ gbc.gridwidth = 3;
+ gbc.anchor = GridBagConstraints.WEST;
+ m_textField = new JTextField(30);
+ m_textField.setText(((StringProperty) m_prop).getStringValue());
+ grid.setConstraints(m_textField, gbc);
+ add(m_textField);
+ m_textField.setEnabled(false);
+
+ // Add action listener.
+ m_includeButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event)
+ {
+ if (m_includeButton.isSelected())
+ {
+ ((BooleanProperty) m_prop).setBooleanValue(true);
+ m_textField.setEnabled(true);
+ }
+ else
+ {
+ ((BooleanProperty) m_prop).setBooleanValue(false);
+ m_textField.setEnabled(false);
+ }
+ }
+ });
+
+ // Add focus listener.
+ m_textField.addFocusListener(new FocusListener() {
+ public void focusGained(FocusEvent event)
+ {
+ }
+ public void focusLost(FocusEvent event)
+ {
+ if (!event.isTemporary())
+ {
+ ((StringProperty) m_prop).setStringValue(m_textField.getText());
+ }
+ }
+ });
+
+ // Currently, the button is not selected. If the property
+ // is true, then click once to select button.
+ if (((BooleanProperty) m_prop).getBooleanValue())
+ {
+ m_includeButton.doClick();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/editor/FileEditor.java b/src/org/apache/osgi/framework/installer/editor/FileEditor.java
new file mode 100644
index 0000000..04a7bde
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/editor/FileEditor.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.editor;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+
+import javax.swing.*;
+
+import org.apache.osgi.framework.installer.Property;
+import org.apache.osgi.framework.installer.StringProperty;
+
+public class FileEditor extends JPanel
+{
+ private StringProperty m_prop = null;
+ private JTextField m_textField = null;
+ private JButton m_browseButton = null;
+ private boolean m_isDirectory = false;
+
+ public FileEditor(StringProperty prop, boolean isDirectory)
+ {
+ super();
+ m_prop = prop;
+ m_isDirectory = isDirectory;
+ init();
+ }
+
+ public Property getProperty()
+ {
+ return m_prop;
+ }
+
+ public void setEnabled(boolean b)
+ {
+ m_textField.setEnabled(b);
+ m_browseButton.setEnabled(b);
+ }
+
+ protected void init()
+ {
+ // Set layout.
+ GridBagLayout grid = new GridBagLayout();
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.insets = new Insets(0, 2, 0, 2);
+ setLayout(grid);
+
+ // Add field.
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.gridheight = 1;
+ gbc.gridwidth = 2;
+ gbc.anchor = GridBagConstraints.WEST;
+ m_textField = new JTextField(30);
+ m_textField.setText(m_prop.getStringValue());
+ grid.setConstraints(m_textField, gbc);
+ add(m_textField);
+
+ // Add button.
+ gbc.gridx = 2;
+ gbc.gridy = 0;
+ gbc.gridheight = 1;
+ gbc.gridwidth = 1;
+ gbc.anchor = GridBagConstraints.EAST;
+ m_browseButton = new JButton("Browse...");
+ m_browseButton.setMargin(new Insets(1, 1, 1, 1));
+ grid.setConstraints(m_browseButton, gbc);
+ add(m_browseButton);
+
+ // Add focus listener.
+ m_textField.addFocusListener(new FocusListener() {
+ public void focusGained(FocusEvent event)
+ {
+ }
+ public void focusLost(FocusEvent event)
+ {
+ if (!event.isTemporary())
+ {
+ // Set the new value.
+ m_prop.setStringValue(normalizeValue(m_textField.getText()));
+
+ }
+ }
+ });
+
+ // Add action listener.
+ m_browseButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event)
+ {
+ JFileChooser fileDlg = new JFileChooser();
+ if (m_isDirectory)
+ {
+ fileDlg.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ fileDlg.setDialogTitle("Please select a directory...");
+ }
+ else
+ {
+ fileDlg.setFileSelectionMode(JFileChooser.FILES_ONLY);
+ fileDlg.setDialogTitle("Please select a file...");
+ }
+ fileDlg.setApproveButtonText("Select");
+ if (fileDlg.showOpenDialog(FileEditor.this) ==
+ JFileChooser.APPROVE_OPTION)
+ {
+ m_textField.setText(fileDlg.getSelectedFile().getAbsolutePath());
+ m_prop.setStringValue(normalizeValue(m_textField.getText()));
+ }
+ }
+ });
+ }
+
+ private String normalizeValue(String value)
+ {
+ // Make sure that directories never end with a slash,
+ // for consistency.
+ if (m_isDirectory)
+ {
+ if (value.endsWith(File.separator))
+ {
+ value = value.substring(0, value.length() - 1);
+ }
+ }
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/editor/StringEditor.java b/src/org/apache/osgi/framework/installer/editor/StringEditor.java
new file mode 100644
index 0000000..8d0c162
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/editor/StringEditor.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.editor;
+
+import java.awt.*;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import org.apache.osgi.framework.installer.Property;
+import org.apache.osgi.framework.installer.StringProperty;
+
+public class StringEditor extends JPanel
+{
+ private StringProperty m_prop = null;
+ private JTextField m_textField = null;
+
+ public StringEditor(StringProperty prop)
+ {
+ m_prop = prop;
+ init();
+ }
+
+ public Property getProperty()
+ {
+ return m_prop;
+ }
+
+ public void setEnabled(boolean b)
+ {
+ m_textField.setEnabled(b);
+ }
+
+ protected void init()
+ {
+ // Set layout.
+ GridBagLayout grid = new GridBagLayout();
+ GridBagConstraints gbc = new GridBagConstraints();
+ gbc.insets = new Insets(0, 2, 0, 2);
+ setLayout(grid);
+
+ // Add field.
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.gridheight = 1;
+ gbc.gridwidth = 2;
+ gbc.anchor = GridBagConstraints.WEST;
+ m_textField = new JTextField(20);
+ m_textField.setText(m_prop.getStringValue());
+ grid.setConstraints(m_textField, gbc);
+ add(m_textField);
+
+ // Add focus listener.
+ m_textField.addFocusListener(new FocusListener() {
+ public void focusGained(FocusEvent event)
+ {
+ }
+ public void focusLost(FocusEvent event)
+ {
+ if (!event.isTemporary())
+ {
+ m_prop.setStringValue(m_textField.getText());
+ }
+ }
+ });
+ }
+}
diff --git a/src/org/apache/osgi/framework/installer/manifest.mf b/src/org/apache/osgi/framework/installer/manifest.mf
new file mode 100644
index 0000000..f25e0aa
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/manifest.mf
@@ -0,0 +1 @@
+Main-Class: org.apache.osgi.framework.installer.Install
diff --git a/src/org/apache/osgi/framework/installer/property/BooleanPropertyImpl.java b/src/org/apache/osgi/framework/installer/property/BooleanPropertyImpl.java
new file mode 100644
index 0000000..cde63c1
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/property/BooleanPropertyImpl.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.property;
+
+import javax.swing.JComponent;
+
+import org.apache.osgi.framework.installer.BooleanProperty;
+import org.apache.osgi.framework.installer.editor.BooleanEditor;
+
+public class BooleanPropertyImpl implements BooleanProperty
+{
+ private String m_name = null;
+ private boolean m_value = false;
+ private JComponent m_editor = null;
+
+ public BooleanPropertyImpl(String name, boolean value)
+ {
+ m_name = name;
+ m_value = value;
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public boolean getBooleanValue()
+ {
+ return m_value;
+ }
+
+ public void setBooleanValue(boolean b)
+ {
+ m_value = b;
+ }
+
+ public JComponent getEditor()
+ {
+ if (m_editor == null)
+ {
+ m_editor = new BooleanEditor(this);
+ }
+ return m_editor;
+ }
+
+ public void setEditor(JComponent comp)
+ {
+ m_editor = comp;
+ }
+
+ public String toString()
+ {
+ return (m_value) ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/property/BooleanStringPropertyImpl.java b/src/org/apache/osgi/framework/installer/property/BooleanStringPropertyImpl.java
new file mode 100644
index 0000000..da8ed1d
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/property/BooleanStringPropertyImpl.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.property;
+
+import javax.swing.JComponent;
+
+import org.apache.osgi.framework.installer.BooleanProperty;
+import org.apache.osgi.framework.installer.StringProperty;
+import org.apache.osgi.framework.installer.editor.BooleanStringEditor;
+
+public class BooleanStringPropertyImpl implements BooleanProperty, StringProperty
+{
+ private String m_name = null;
+ private boolean m_boolean = false;
+ private String m_string = "";
+ private JComponent m_editor = null;
+
+ public BooleanStringPropertyImpl(String name, boolean b, String s)
+ {
+ m_name = name;
+ m_boolean = b;
+ m_string = s;
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public boolean getBooleanValue()
+ {
+ return m_boolean;
+ }
+
+ public void setBooleanValue(boolean b)
+ {
+ m_boolean = b;
+ }
+
+ public String getStringValue()
+ {
+ return m_string;
+ }
+
+ public void setStringValue(String s)
+ {
+ m_string = s;
+ }
+
+ public JComponent getEditor()
+ {
+ if (m_editor == null)
+ {
+ m_editor = new BooleanStringEditor(this);
+ }
+ return m_editor;
+ }
+
+ public void setEditor(JComponent comp)
+ {
+ m_editor = comp;
+ }
+
+ public String toString()
+ {
+ return m_string;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/property/NotBooleanPropertyImpl.java b/src/org/apache/osgi/framework/installer/property/NotBooleanPropertyImpl.java
new file mode 100644
index 0000000..e242552
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/property/NotBooleanPropertyImpl.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.property;
+
+import javax.swing.JComponent;
+
+import org.apache.osgi.framework.installer.BooleanProperty;
+import org.apache.osgi.framework.installer.editor.BooleanEditor;
+
+public class NotBooleanPropertyImpl implements BooleanProperty
+{
+ private BooleanProperty m_prop = null;
+ private JComponent m_editor = null;
+
+ public NotBooleanPropertyImpl(BooleanProperty prop)
+ {
+ m_prop = prop;
+ }
+
+ public String getName()
+ {
+ return "NOT " + m_prop.getName();
+ }
+
+ public boolean getBooleanValue()
+ {
+ return !m_prop.getBooleanValue();
+ }
+
+ public void setBooleanValue(boolean b)
+ {
+ m_prop.setBooleanValue(!b);
+ }
+
+ public JComponent getEditor()
+ {
+ if (m_editor == null)
+ {
+ m_editor = new BooleanEditor(this);
+ }
+ return m_editor;
+ }
+
+ public void setEditor(JComponent comp)
+ {
+ m_editor = comp;
+ }
+
+ public String toString()
+ {
+ return (getBooleanValue()) ? Boolean.TRUE.toString() : Boolean.FALSE.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/property/StringPropertyImpl.java b/src/org/apache/osgi/framework/installer/property/StringPropertyImpl.java
new file mode 100644
index 0000000..47f8db1
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/property/StringPropertyImpl.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.property;
+
+import javax.swing.JComponent;
+
+import org.apache.osgi.framework.installer.StringProperty;
+import org.apache.osgi.framework.installer.editor.StringEditor;
+
+public class StringPropertyImpl implements StringProperty
+{
+ private String m_name = null;
+ private String m_value = "";
+ private JComponent m_editor = null;
+
+ public StringPropertyImpl(String name, String value)
+ {
+ m_name = name;
+ m_value = value;
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public String getStringValue()
+ {
+ return m_value;
+ }
+
+ public void setStringValue(String s)
+ {
+ m_value = s;
+ }
+
+ public JComponent getEditor()
+ {
+ if (m_editor == null)
+ {
+ m_editor = new StringEditor(this);
+ }
+ return m_editor;
+ }
+
+ public void setEditor(JComponent comp)
+ {
+ m_editor = comp;
+ }
+
+ public String toString()
+ {
+ return m_value;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/installer/resource/ResourceLoader.java b/src/org/apache/osgi/framework/installer/resource/ResourceLoader.java
new file mode 100644
index 0000000..ae7d675
--- /dev/null
+++ b/src/org/apache/osgi/framework/installer/resource/ResourceLoader.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.installer.resource;
+
+import java.io.InputStream;
+import java.net.URL;
+
+public class ResourceLoader
+{
+ public static URL getResource(String name)
+ {
+ return ResourceLoader.class.getResource(name);
+ }
+
+ public static InputStream getResourceAsStream(String name)
+ {
+ return ResourceLoader.class.getResourceAsStream(name);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/manifest.mf b/src/org/apache/osgi/framework/manifest.mf
new file mode 100644
index 0000000..d9208d9
--- /dev/null
+++ b/src/org/apache/osgi/framework/manifest.mf
@@ -0,0 +1,2 @@
+Main-Class: org.apache.osgi.framework.Main
+Class-Path: osgi.jar moduleloader.jar
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4Attribute.java b/src/org/apache/osgi/framework/searchpolicy/R4Attribute.java
new file mode 100644
index 0000000..a3161b9
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4Attribute.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.searchpolicy;
+
+public class R4Attribute
+{
+ private String m_name = "";
+ private String m_value = "";
+ private boolean m_isMandatory = false;
+
+ public R4Attribute(String name, String value, boolean isMandatory)
+ {
+ m_name = name;
+ m_value = value;
+ m_isMandatory = isMandatory;
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public String getValue()
+ {
+ return m_value;
+ }
+
+ public boolean isMandatory()
+ {
+ return m_isMandatory;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4Directive.java b/src/org/apache/osgi/framework/searchpolicy/R4Directive.java
new file mode 100644
index 0000000..12de924
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4Directive.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.searchpolicy;
+
+public class R4Directive
+{
+ private String m_name = "";
+ private String m_value = "";
+
+ public R4Directive(String name, String value)
+ {
+ m_name = name;
+ m_value = value;
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public String getValue()
+ {
+ return m_value;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4Package.java b/src/org/apache/osgi/framework/searchpolicy/R4Package.java
new file mode 100755
index 0000000..380c5f0
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4Package.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.searchpolicy;
+
+import java.util.*;
+
+import org.apache.osgi.framework.util.FelixConstants;
+import org.apache.osgi.framework.util.Util;
+
+public class R4Package
+{
+ private String m_id = "";
+ private R4Directive[] m_directives = null;
+ private R4Attribute[] m_attrs = null;
+ private R4Version m_versionLow = null;
+ private R4Version m_versionHigh = null;
+ private String[] m_uses = null;
+ private boolean m_isOptional = false;
+ private String[][] m_includeFilter = null;
+ private String[][] m_excludeFilter = null;
+
+ protected R4Package(R4Package pkg)
+ {
+ m_id = pkg.m_id;
+ m_directives = pkg.m_directives;
+ m_attrs = pkg.m_attrs;
+ m_versionLow = pkg.m_versionLow;
+ m_versionHigh = pkg.m_versionHigh;
+ m_uses = pkg.m_uses;
+ m_isOptional = pkg.m_isOptional;
+ m_includeFilter = pkg.m_includeFilter;
+ m_excludeFilter = pkg.m_excludeFilter;
+ }
+
+ public R4Package(String id, R4Directive[] directives, R4Attribute[] attrs)
+ {
+ m_id = id;
+ m_directives = (directives == null) ? new R4Directive[0] : directives;
+ m_attrs = (attrs == null) ? new R4Attribute[0] : attrs;
+
+ // Find all directives: uses, mandatory, resolution, include, and exclude.
+ String mandatory = "", uses = "";
+ for (int i = 0; i < m_directives.length; i++)
+ {
+ if (m_directives[i].getName().equals(FelixConstants.USES_DIRECTIVE))
+ {
+ uses = m_directives[i].getValue();
+ }
+ else if (m_directives[i].getName().equals(FelixConstants.MANDATORY_DIRECTIVE))
+ {
+ mandatory = m_directives[i].getValue();
+ }
+ else if (m_directives[i].getName().equals(FelixConstants.RESOLUTION_DIRECTIVE))
+ {
+ m_isOptional = m_directives[i].getValue().equals(FelixConstants.RESOLUTION_OPTIONAL);
+ }
+ else if (m_directives[i].getName().equals(FelixConstants.INCLUDE_DIRECTIVE))
+ {
+ String[] ss = Util.parseDelimitedString(m_directives[i].getValue(), ",");
+ m_includeFilter = new String[ss.length][];
+ for (int filterIdx = 0; filterIdx < ss.length; filterIdx++)
+ {
+ m_includeFilter[filterIdx] = parseSubstring(ss[filterIdx]);
+ }
+ }
+ else if (m_directives[i].getName().equals(FelixConstants.EXCLUDE_DIRECTIVE))
+ {
+ String[] ss = Util.parseDelimitedString(m_directives[i].getValue(), ",");
+ m_excludeFilter = new String[ss.length][];
+ for (int filterIdx = 0; filterIdx < ss.length; filterIdx++)
+ {
+ m_excludeFilter[filterIdx] = parseSubstring(ss[filterIdx]);
+ }
+ }
+ }
+
+ // Parse these uses directive.
+ StringTokenizer tok = new StringTokenizer(uses, ",");
+ m_uses = new String[tok.countTokens()];
+ for (int i = 0; i < m_uses.length; i++)
+ {
+ m_uses[i] = tok.nextToken().trim();
+ }
+
+ // Parse mandatory directive and mark specified
+ // attributes as mandatory.
+ tok = new StringTokenizer(mandatory, ",");
+ while (tok.hasMoreTokens())
+ {
+ // Get attribute name.
+ String attrName = tok.nextToken().trim();
+ // Find attribute and mark it as mandatory.
+ boolean found = false;
+ for (int i = 0; (!found) && (i < m_attrs.length); i++)
+ {
+ if (m_attrs[i].getName().equals(attrName))
+ {
+ m_attrs[i] = new R4Attribute(
+ m_attrs[i].getName(), m_attrs[i].getValue(), true);
+ found = true;
+ }
+ }
+ // If a specified mandatory attribute was not found,
+ // then error.
+ if (!found)
+ {
+ throw new IllegalArgumentException(
+ "Mandatory attribute '" + attrName + "' does not exist.");
+ }
+ }
+
+ // Find and parse version attribute, if present.
+ String versionInterval = "0.0.0";
+ for (int i = 0; i < m_attrs.length; i++)
+ {
+ if (m_attrs[i].getName().equals(FelixConstants.VERSION_ATTRIBUTE) ||
+ m_attrs[i].getName().equals(FelixConstants.PACKAGE_SPECIFICATION_VERSION))
+ {
+ // Normalize version attribute name.
+ m_attrs[i] = new R4Attribute(
+ FelixConstants.VERSION_ATTRIBUTE, m_attrs[i].getValue(),
+ m_attrs[i].isMandatory());
+ versionInterval = m_attrs[i].getValue();
+ break;
+ }
+ }
+
+ R4Version[] versions = parseVersionInterval(versionInterval);
+ m_versionLow = versions[0];
+ if (versions.length == 2)
+ {
+ m_versionHigh = versions[1];
+ }
+ }
+
+ public String getId()
+ {
+ return m_id;
+ }
+
+ public R4Directive[] getDirectives()
+ {
+ return m_directives;
+ }
+
+ public R4Attribute[] getAttributes()
+ {
+ return m_attrs;
+ }
+
+ public R4Version getVersionLow()
+ {
+ return m_versionLow;
+ }
+
+ public R4Version getVersionHigh()
+ {
+ return m_versionHigh;
+ }
+
+ public String[] getUses()
+ {
+ return m_uses;
+ }
+
+ public boolean isOptional()
+ {
+ return m_isOptional;
+ }
+
+ public boolean isIncluded(String name)
+ {
+ if ((m_includeFilter == null) && (m_excludeFilter == null))
+ {
+ return true;
+ }
+
+ // Get the class name portion of the target class.
+ String className = org.apache.osgi.moduleloader.Util.getClassName(name);
+
+ // If there are no include filters then all classes are included
+ // by default, otherwise try to find one match.
+ boolean included = (m_includeFilter == null);
+ for (int i = 0;
+ (!included) && (m_includeFilter != null) && (i < m_includeFilter.length);
+ i++)
+ {
+ included = checkSubstring(m_includeFilter[i], className);
+ }
+
+ // If there are no exclude filters then no classes are excluded
+ // by default, otherwise try to find one match.
+ boolean excluded = false;
+ for (int i = 0;
+ (!excluded) && (m_excludeFilter != null) && (i < m_excludeFilter.length);
+ i++)
+ {
+ excluded = checkSubstring(m_excludeFilter[i], className);
+ }
+ return included && !excluded;
+ }
+
+ // PREVIOUSLY PART OF COMPATIBILITY POLICY.
+ public boolean doesSatisfy(R4Package pkg)
+ {
+ // For packages to be compatible, they must have the
+ // same name.
+ if (!m_id.equals(pkg.m_id))
+ {
+ return false;
+ }
+
+ return isVersionInRange(m_versionLow, pkg.m_versionLow, pkg.m_versionHigh)
+ && doAttributesMatch(pkg);
+ }
+
+ // PREVIOUSLY PART OF COMPATIBILITY POLICY.
+ public static boolean isVersionInRange(R4Version version, R4Version low, R4Version high)
+ {
+ // We might not have an upper end to the range.
+ if (high == null)
+ {
+ return (version.compareTo(low) >= 0);
+ }
+ else if (low.isInclusive() && high.isInclusive())
+ {
+ return (version.compareTo(low) >= 0) && (version.compareTo(high) <= 0);
+ }
+ else if (high.isInclusive())
+ {
+ return (version.compareTo(low) > 0) && (version.compareTo(high) <= 0);
+ }
+ else if (low.isInclusive())
+ {
+ return (version.compareTo(low) >= 0) && (version.compareTo(high) < 0);
+ }
+
+ return (version.compareTo(low) > 0) && (version.compareTo(high) < 0);
+ }
+
+ private boolean doAttributesMatch(R4Package pkg)
+ {
+ // Cycle through all attributes of the specified package
+ // and make sure their values match the attribute values
+ // of this package.
+ for (int attrIdx = 0; attrIdx < pkg.m_attrs.length; attrIdx++)
+ {
+ // Get current attribute from specified package.
+ R4Attribute attr = pkg.m_attrs[attrIdx];
+
+ // Ignore version attribute, since it is a special case that
+ // has already been compared using isVersionInRange() before
+ // the call to this method was made.
+ if (attr.getName().equals(FelixConstants.VERSION_ATTRIBUTE))
+ {
+ continue;
+ }
+
+ // Check if this package has the same attribute.
+ boolean found = false;
+ for (int thisAttrIdx = 0;
+ (!found) && (thisAttrIdx < m_attrs.length);
+ thisAttrIdx++)
+ {
+ // Get current attribute for this package.
+ R4Attribute thisAttr = m_attrs[thisAttrIdx];
+ // Check if the attribute names are equal.
+ if (attr.getName().equals(thisAttr.getName()))
+ {
+ // If the values are not equal, then return false immediately.
+ // We should not compare version values here, since they are
+ // a special case and have already been compared by a call to
+ // isVersionInRange() before getting here; however, it is
+ // possible for version to be mandatory, so make sure it is
+ // present below.
+ if (!attr.getValue().equals(thisAttr.getValue()))
+ {
+ return false;
+ }
+ found = true;
+ }
+ }
+ // If the attribute was not found, then return false.
+ if (!found)
+ {
+ return false;
+ }
+ }
+
+ // Now, cycle through all attributes of this package and verify that
+ // all mandatory attributes are present in the speceified package.
+ for (int thisAttrIdx = 0; thisAttrIdx < m_attrs.length; thisAttrIdx++)
+ {
+ // Get current attribute for this package.
+ R4Attribute thisAttr = m_attrs[thisAttrIdx];
+
+ // If the attribute is mandatory, then make sure
+ // the specified package has the attribute.
+ if (thisAttr.isMandatory())
+ {
+ boolean found = false;
+ for (int attrIdx = 0;
+ (!found) && (attrIdx < pkg.m_attrs.length);
+ attrIdx++)
+ {
+ // Get current attribute from specified package.
+ R4Attribute attr = pkg.m_attrs[attrIdx];
+
+ // Check if the attribute names are equal
+ // and set found flag.
+ if (thisAttr.getName().equals(attr.getName()))
+ {
+ found = true;
+ }
+ }
+ // If not found, then return false.
+ if (!found)
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public String toString()
+ {
+ String msg = getId();
+ for (int i = 0; (m_directives != null) && (i < m_directives.length); i++)
+ {
+ msg = msg + " [" + m_directives[i].getName() + ":="+ m_directives[i].getValue() + "]";
+ }
+ for (int i = 0; (m_attrs != null) && (i < m_attrs.length); i++)
+ {
+ msg = msg + " [" + m_attrs[i].getName() + "="+ m_attrs[i].getValue() + "]";
+ }
+ return msg;
+ }
+
+ // Like this: pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2,
+ // pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+ public static R4Package[] parseImportOrExportHeader(String s)
+ {
+ R4Package[] pkgs = null;
+ if (s != null)
+ {
+ if (s.length() == 0)
+ {
+ throw new IllegalArgumentException(
+ "The import and export headers cannot be an empty string.");
+ }
+ String[] ss = Util.parseDelimitedString(
+ s, FelixConstants.CLASS_PATH_SEPARATOR);
+ pkgs = parsePackageStrings(ss);
+ }
+ return (pkgs == null) ? new R4Package[0] : pkgs;
+ }
+
+ // Like this: pkg1; pkg2; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
+ public static R4Package[] parsePackageStrings(String[] ss)
+ throws IllegalArgumentException
+ {
+ if (ss == null)
+ {
+ return null;
+ }
+
+ List completeList = new ArrayList();
+ for (int ssIdx = 0; ssIdx < ss.length; ssIdx++)
+ {
+ // Break string into semi-colon delimited pieces.
+ String[] pieces = Util.parseDelimitedString(
+ ss[ssIdx], FelixConstants.PACKAGE_SEPARATOR);
+
+ // Count the number of different packages; packages
+ // will not have an '=' in their string. This assumes
+ // that packages come first, before directives and
+ // attributes.
+ int pkgCount = 0;
+ for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++)
+ {
+ if (pieces[pieceIdx].indexOf('=') >= 0)
+ {
+ break;
+ }
+ pkgCount++;
+ }
+
+ // Error if no packages were specified.
+ if (pkgCount == 0)
+ {
+ throw new IllegalArgumentException(
+ "No packages specified on import: " + ss[ssIdx]);
+ }
+
+ // Parse the directives/attributes.
+ R4Directive[] dirs = new R4Directive[pieces.length - pkgCount];
+ R4Attribute[] attrs = new R4Attribute[pieces.length - pkgCount];
+ int dirCount = 0, attrCount = 0;
+ int idx = -1;
+ String sep = null;
+ for (int pieceIdx = pkgCount; pieceIdx < pieces.length; pieceIdx++)
+ {
+ // Check if it is a directive.
+ if ((idx = pieces[pieceIdx].indexOf(FelixConstants.DIRECTIVE_SEPARATOR)) >= 0)
+ {
+ sep = FelixConstants.DIRECTIVE_SEPARATOR;
+ }
+ // Check if it is an attribute.
+ else if ((idx = pieces[pieceIdx].indexOf(FelixConstants.ATTRIBUTE_SEPARATOR)) >= 0)
+ {
+ sep = FelixConstants.ATTRIBUTE_SEPARATOR;
+ }
+ // It is an error.
+ else
+ {
+ throw new IllegalArgumentException(
+ "Not a directive/attribute: " + ss[ssIdx]);
+ }
+
+ String key = pieces[pieceIdx].substring(0, idx).trim();
+ String value = pieces[pieceIdx].substring(idx + sep.length()).trim();
+
+ // Remove quotes, if value is quoted.
+ if (value.startsWith("\"") && value.endsWith("\""))
+ {
+ value = value.substring(1, value.length() - 1);
+ }
+
+ // Save the directive/attribute in the appropriate array.
+ if (sep.equals(FelixConstants.DIRECTIVE_SEPARATOR))
+ {
+ dirs[dirCount++] = new R4Directive(key, value);
+ }
+ else
+ {
+ attrs[attrCount++] = new R4Attribute(key, value, false);
+ }
+ }
+
+ // Shrink directive array.
+ R4Directive[] dirsFinal = new R4Directive[dirCount];
+ System.arraycopy(dirs, 0, dirsFinal, 0, dirCount);
+ // Shrink attribute array.
+ R4Attribute[] attrsFinal = new R4Attribute[attrCount];
+ System.arraycopy(attrs, 0, attrsFinal, 0, attrCount);
+
+ // Create package attributes for each package and
+ // set directives/attributes. Add each package to
+ // completel list of packages.
+ R4Package[] pkgs = new R4Package[pkgCount];
+ for (int pkgIdx = 0; pkgIdx < pkgCount; pkgIdx++)
+ {
+ pkgs[pkgIdx] = new R4Package(pieces[pkgIdx], dirsFinal, attrsFinal);
+ completeList.add(pkgs[pkgIdx]);
+ }
+ }
+
+ R4Package[] ips = (R4Package[])
+ completeList.toArray(new R4Package[completeList.size()]);
+ return ips;
+ }
+
+ public static R4Version[] parseVersionInterval(String interval)
+ {
+ // Check if the version is an interval.
+ if (interval.indexOf(',') >= 0)
+ {
+ String s = interval.substring(1, interval.length() - 1);
+ String vlo = s.substring(0, s.indexOf(','));
+ String vhi = s.substring(s.indexOf(',') + 1, s.length());
+ return new R4Version[] {
+ new R4Version(vlo, (interval.charAt(0) == '[')),
+ new R4Version(vhi, (interval.charAt(interval.length() - 1) == ']'))
+ };
+ }
+ else
+ {
+ return new R4Version[] { new R4Version(interval, true) };
+ }
+ }
+
+ //
+ // The following substring-related code was lifted and modified
+ // from the LDAP parser code.
+ //
+
+ private static String[] parseSubstring(String target)
+ {
+ List pieces = new ArrayList();
+ StringBuffer ss = new StringBuffer();
+ // int kind = SIMPLE; // assume until proven otherwise
+ boolean wasStar = false; // indicates last piece was a star
+ boolean leftstar = false; // track if the initial piece is a star
+ boolean rightstar = false; // track if the final piece is a star
+
+ int idx = 0;
+
+ // We assume (sub)strings can contain leading and trailing blanks
+loop: for (;;)
+ {
+ if (idx >= target.length())
+ {
+ if (wasStar)
+ {
+ // insert last piece as "" to handle trailing star
+ rightstar = true;
+ }
+ else
+ {
+ pieces.add(ss.toString());
+ // accumulate the last piece
+ // note that in the case of
+ // (cn=); this might be
+ // the string "" (!=null)
+ }
+ ss.setLength(0);
+ break loop;
+ }
+
+ char c = target.charAt(idx++);
+ if (c == '*')
+ {
+ if (wasStar)
+ {
+ // encountered two successive stars;
+ // I assume this is illegal
+ throw new IllegalArgumentException("Invalid filter string: " + target);
+ }
+ if (ss.length() > 0)
+ {
+ pieces.add(ss.toString()); // accumulate the pieces
+ // between '*' occurrences
+ }
+ ss.setLength(0);
+ // if this is a leading star, then track it
+ if (pieces.size() == 0)
+ {
+ leftstar = true;
+ }
+ ss.setLength(0);
+ wasStar = true;
+ }
+ else
+ {
+ wasStar = false;
+ ss.append(c);
+ }
+ }
+ if (leftstar || rightstar || pieces.size() > 1)
+ {
+ // insert leading and/or trailing "" to anchor ends
+ if (rightstar)
+ {
+ pieces.add("");
+ }
+ if (leftstar)
+ {
+ pieces.add(0, "");
+ }
+ }
+ return (String[]) pieces.toArray(new String[pieces.size()]);
+ }
+
+ private static boolean checkSubstring(String[] pieces, String s)
+ {
+ // Walk the pieces to match the string
+ // There are implicit stars between each piece,
+ // and the first and last pieces might be "" to anchor the match.
+ // assert (pieces.length > 1)
+ // minimal case is <string>*<string>
+
+ boolean result = false;
+ int len = pieces.length;
+
+loop: for (int i = 0; i < len; i++)
+ {
+ String piece = (String) pieces[i];
+ int index = 0;
+ if (i == len - 1)
+ {
+ // this is the last piece
+ if (s.endsWith(piece))
+ {
+ result = true;
+ }
+ else
+ {
+ result = false;
+ }
+ break loop;
+ }
+ // initial non-star; assert index == 0
+ else if (i == 0)
+ {
+ if (!s.startsWith(piece))
+ {
+ result = false;
+ break loop;
+ }
+ }
+ // assert i > 0 && i < len-1
+ else
+ {
+ // Sure wish stringbuffer supported e.g. indexOf
+ index = s.indexOf(piece, index);
+ if (index < 0)
+ {
+ result = false;
+ break loop;
+ }
+ }
+ // start beyond the matching piece
+ index += piece.length();
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4SearchPolicy.java b/src/org/apache/osgi/framework/searchpolicy/R4SearchPolicy.java
new file mode 100755
index 0000000..5855c06
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4SearchPolicy.java
@@ -0,0 +1,1638 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.searchpolicy;
+
+import java.net.URL;
+import java.util.*;
+
+import org.apache.osgi.framework.LogWrapper;
+import org.apache.osgi.moduleloader.*;
+import org.apache.osgi.moduleloader.search.ResolveException;
+import org.apache.osgi.moduleloader.search.ResolveListener;
+
+public class R4SearchPolicy implements SearchPolicy, ModuleListener
+{
+ // Array of R4Package.
+ public static final String EXPORTS_ATTR = "exports";
+ // Array of R4Package.
+ public static final String IMPORTS_ATTR = "imports";
+ // Array of R4Package.
+ public static final String DYNAMICIMPORTS_ATTR = "dynamicimports";
+ // Array of R4Wire.
+ public static final String WIRING_ATTR = "wiring";
+ // Boolean.
+ public static final String RESOLVED_ATTR = "resolved";
+
+ private LogWrapper m_logger = null;
+ private ModuleManager m_mgr = null;
+ private Map m_availPkgMap = new HashMap();
+ private Map m_inUsePkgMap = new HashMap();
+
+ // Listener-related instance variables.
+ private static final ResolveListener[] m_emptyListeners = new ResolveListener[0];
+ private ResolveListener[] m_listeners = m_emptyListeners;
+
+ // Reusable empty arrays.
+ public static final Module[] m_emptyModules = new Module[0];
+ public static final R4Package[] m_emptyPackages = new R4Package[0];
+ public static final R4Wire[] m_emptyWires = new R4Wire[0];
+
+ // Re-usable security manager for accessing class context.
+ private static SecurityManagerX m_sm = new SecurityManagerX();
+
+ public R4SearchPolicy(LogWrapper logger)
+ {
+ m_logger = logger;
+ }
+
+ public void setModuleManager(ModuleManager mgr)
+ throws IllegalStateException
+ {
+ if (m_mgr == null)
+ {
+ m_mgr = mgr;
+ m_mgr.addModuleListener(this);
+ }
+ else
+ {
+ throw new IllegalStateException("Module manager is already initialized");
+ }
+ }
+
+ public Object[] definePackage(Module module, String pkgName)
+ {
+ R4Package pkg = R4SearchPolicy.getExportPackage(module, pkgName);
+ if (pkg != null)
+ {
+ return new Object[] {
+ pkgName, // Spec title.
+ pkg.getVersionLow().toString(), // Spec version.
+ "", // Spec vendor.
+ "", // Impl title.
+ "", // Impl version.
+ "" // Impl vendor.
+ };
+ }
+ return null;
+ }
+
+ public Class findClassBeforeModule(ClassLoader parent, Module module, String name)
+ throws ClassNotFoundException
+ {
+ // First, try to resolve the originating module.
+ try
+ {
+ resolve(module);
+ }
+ catch (ResolveException ex)
+ {
+ throw new ClassNotFoundException(name);
+ }
+
+ // Get the package of the target class.
+ String pkgName = Util.getClassPackage(name);
+
+ // Load all "java.*" classes from parent class loader;
+ // these packages cannot be provided by other bundles.
+ if (pkgName.startsWith("java."))
+ {
+ return (parent == null) ? null : parent.loadClass(name);
+ }
+
+ // We delegate to the module's wires to find the class.
+ R4Wire[] wires = getWiringAttr(module);
+ for (int i = 0; i < wires.length; i++)
+ {
+ // Only check when the package of the class is
+ // the same as the import package.
+ if (wires[i].m_pkg.getId().equals(pkgName))
+ {
+ // Before delegating to the module class loader to satisfy
+ // the class load, we must check the include/exclude filters
+ // from the target package to make sure that the class is
+ // actually visible. If the exporting module is the same as
+ // the requesting module, then filtering is not performed
+ // since a module has complete access to itself.
+// TODO: Determine if it is possible to modify Module Loader somehow
+// so that this check is done within the target module itself; it
+// doesn't totally make sense to do this check in the importing module.
+ if (wires[i].m_module != module)
+ {
+ if (!wires[i].m_pkg.isIncluded(name))
+ {
+ throw new ClassNotFoundException(name);
+ }
+ }
+
+ // Since the class is included, delegate to the exporting module.
+ try
+ {
+ Class clazz = wires[i].m_module.getClassLoader().loadClassFromModule(name);
+ if (clazz != null)
+ {
+ return clazz;
+ }
+ }
+ catch (Throwable th)
+ {
+ // Not much we can do here.
+ }
+ throw new ClassNotFoundException(name);
+ }
+ }
+
+ return null;
+ }
+
+ public Class findClassAfterModule(ClassLoader parent, Module module, String name)
+ throws ClassNotFoundException
+ {
+ // At this point, the module's imports were searched and so was the
+ // the module's own resources. Now we make an attempt to load the
+ // class via a dynamic import, if possible.
+ String pkgName = Util.getClassPackage(name);
+ Module candidate = attemptDynamicImport(module, pkgName);
+ // If the dynamic import was successful, then this initial
+ // time we must directly return the result from dynamically
+ // selected candidate's class loader, but for subsequent
+ // requests for classes in the associated package will be
+ // processed as part of normal static imports.
+ if (candidate != null)
+ {
+ return candidate.getClassLoader().loadClass(name);
+ }
+
+ // At this point, the class could not be found by the bundle's static
+ // or dynamic imports, nor its own resources. Before we throw
+ // an exception, we will try to determine if the instigator of the
+ // class load was a class from a bundle or not. This is necessary
+ // because the specification mandates that classes on the class path
+ // should be hidden (except for java.*), but it does allow for these
+ // classes to be exposed by the system bundle as an export. However,
+ // in some situations classes on the class path make the faulty
+ // assumption that they can access everything on the class path from
+ // every other class loader that they come in contact with. This is
+ // not true if the class loader in question is from a bundle. Thus,
+ // this code tries to detect that situation. If the class
+ // instigating the class load was NOT from a bundle, then we will
+ // make the assumption that the caller actually wanted to use the
+ // parent class loader and we will delegate to it. If the class was
+ // from a bundle, then we will enforce strict class loading rules
+ // for the bundle and throw a class not found exception.
+
+ // Get the class context to see the classes on the stack.
+ Class[] classes = m_sm.getClassContext();
+ // Start from 1 to skip inner class.
+ for (int i = 1; i < classes.length; i++)
+ {
+ // Find the first class on the call stack that is neither
+ // a class loader or Class itself, because we want to ignore
+ // the calls to ClassLoader.loadClass() and Class.forName().
+ if (!ClassLoader.class.isAssignableFrom(classes[i]) &&
+ !Class.class.isAssignableFrom(classes[i]))
+ {
+ // If the instigating class was not from a bundle, then
+ // delegate to the parent class loader. Otherwise, break
+ // out of loop and throw an exception.
+ if (!ModuleClassLoader.class.isInstance(classes[i].getClassLoader()))
+ {
+ return parent.loadClass(name);
+ }
+ break;
+ }
+ }
+
+ throw new ClassNotFoundException(name);
+ }
+
+ public URL findResource(ClassLoader parent, Module module, String name)
+ throws ResourceNotFoundException
+ {
+ // First, try to resolve the originating module.
+ try
+ {
+ resolve(module);
+ }
+ catch (ResolveException ex)
+ {
+ return null;
+ }
+
+ // Get the package of the target resource.
+ String pkgName = Util.getResourcePackage(name);
+
+ // Load all "java.*" resources from parent class loader;
+ // these packages cannot be provided by other bundles.
+ if (pkgName.startsWith("java."))
+ {
+ return (parent == null) ? null : parent.getResource(name);
+ }
+
+ // We delegate to the module's wires to find the resource.
+ R4Wire[] wires = getWiringAttr(module);
+ for (int i = 0; i < wires.length; i++)
+ {
+ // Only check when the package of the resource is
+ // the same as the import package.
+ if (wires[i].m_pkg.getId().equals(pkgName))
+ {
+ try
+ {
+ URL url = wires[i].m_module.getClassLoader().getResourceFromModule(name);
+ if (url != null)
+ {
+ return url;
+ }
+ }
+ catch (Throwable th)
+ {
+ // Not much we can do here.
+ }
+ throw new ResourceNotFoundException(name);
+ }
+ }
+
+ // Check dynamic imports.
+// TODO: Dynamic imports should be searched after local sources.
+ Module candidate = attemptDynamicImport(module, pkgName);
+ // This initial time we must directly return the result from
+ // the candidate's class loaders, but since the candidate was
+ // added to the module's wiring attribute, subsequent class
+ // loads from the same package will be handled in the normal
+ // fashion for statically imported packaes.
+ return (candidate == null)
+ ? null : candidate.getClassLoader().getResource(name);
+ }
+
+ private Module attemptDynamicImport(Module module, String pkgName)
+ {
+ Module candidate = null;
+
+ // There is an overriding assumption here that a package is
+ // never split across bundles. If a package can be split
+ // across bundles, then this will fail.
+
+ try
+ {
+ // Check the dynamic import specs for a match of
+ // the target package.
+ R4Package[] dynamics = getDynamicImportsAttr(module);
+ R4Package pkgMatch = null;
+ for (int i = 0; (pkgMatch == null) && (i < dynamics.length); i++)
+ {
+ // Star matches everything.
+ if (dynamics[i].getId().equals("*"))
+ {
+ // Create a package instance without wildcard.
+ pkgMatch = new R4Package(
+ pkgName,
+ dynamics[i].getDirectives(),
+ dynamics[i].getAttributes());
+ }
+ // Packages ending in ".*" must match starting strings.
+ else if (dynamics[i].getId().endsWith(".*"))
+ {
+ if (pkgName.regionMatches(
+ 0, dynamics[i].getId(), 0, dynamics[i].getId().length() - 2))
+ {
+ // Create a package instance without wildcard.
+ pkgMatch = new R4Package(
+ pkgName,
+ dynamics[i].getDirectives(),
+ dynamics[i].getAttributes());
+ }
+ }
+ // Or we can have a precise match.
+ else
+ {
+ if (pkgName.equals(dynamics[i].getId()))
+ {
+ pkgMatch = dynamics[i];
+ }
+ }
+ }
+
+ // If the target package does not match any dynamically imported
+ // packages or if the module is already wired for the target package,
+ // then just return null. The module may be already wired to the target
+ // package if the class being searched for does not actually exist.
+ if ((pkgMatch == null) || (getWire(module, pkgMatch.getId()) != null))
+ {
+ return null;
+ }
+
+ // At this point, the target package has matched a dynamically
+ // imported package spec. Now we must try to find a candidate
+ // exporter for target package and add it to the module's set
+ // of wires.
+
+ // Lock module manager instance to ensure that nothing changes.
+ synchronized (m_mgr)
+ {
+ // Try to add a new entry to the module's import attribute.
+ // Select the first candidate that successfully resolves.
+
+ // First check already resolved exports for a match.
+ Module[] candidates = getCompatibleExporters(
+ (Module[]) m_inUsePkgMap.get(pkgMatch.getId()), pkgMatch);
+ // If there is an "in use" candidate, just take the first one.
+ if (candidates.length > 0)
+ {
+ candidate = candidates[0];
+ }
+
+ // If there were no "in use" candidates, then try "available"
+ // candidates.
+ if (candidate == null)
+ {
+ candidates = getCompatibleExporters(
+ (Module[]) m_availPkgMap.get(pkgMatch.getId()), pkgMatch);
+ for (int candIdx = 0;
+ (candidate == null) && (candIdx < candidates.length);
+ candIdx++)
+ {
+ try
+ {
+ resolve(module);
+ candidate = candidates[candIdx];
+ }
+ catch (ResolveException ex)
+ {
+ }
+ }
+ }
+
+ // If we found a candidate, then add it to the module's
+ // wiring attribute.
+ if (candidate != null)
+ {
+ R4Wire[] wires = getWiringAttr(module);
+ R4Wire[] newWires = new R4Wire[wires.length + 1];
+ System.arraycopy(wires, 0, newWires, 0, wires.length);
+ // Find the candidate's export package object and
+ // use that for creating the wire; this is necessary
+ // since it contains "uses" dependency information.
+ newWires[wires.length] = new R4Wire(
+ getExportPackage(candidate, pkgMatch.getId()), candidate);
+ module.setAttribute(WIRING_ATTR, newWires);
+m_logger.log(LogWrapper.LOG_DEBUG, "WIRE: [" + module + "] " + newWires[wires.length]);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ m_logger.log(LogWrapper.LOG_ERROR, "Unable to dynamically import package.", ex);
+ }
+
+ return candidate;
+ }
+
+ public Module[] getAvailableExporters(R4Package pkg)
+ {
+ // Synchronized on the module manager to make sure that no
+ // modules are added, removed, or resolved.
+ synchronized (m_mgr)
+ {
+ return getCompatibleExporters((Module[]) m_availPkgMap.get(pkg.getId()), pkg);
+ }
+ }
+
+ public Module[] getInUseExporters(R4Package pkg)
+ {
+ // Synchronized on the module manager to make sure that no
+ // modules are added, removed, or resolved.
+ synchronized (m_mgr)
+ {
+ return getCompatibleExporters((Module[]) m_inUsePkgMap.get(pkg.getId()), pkg);
+ }
+ }
+
+ public void resolve(Module rootModule)
+ throws ResolveException
+ {
+ // If the module is already resolved, then we can just return.
+ if (getResolvedAttr(rootModule).booleanValue())
+ {
+ return;
+ }
+
+ // This variable maps an unresolved module to a list of resolver
+ // nodes, where there is one resolver node for each import that
+ // must be resolved. A resolver node contains the potential
+ // candidates to resolve the import and the current selected
+ // candidate index.
+ Map resolverMap = new HashMap();
+
+ // This map will be used to hold the final wires for all
+ // resolved modules, which can then be used to fire resolved
+ // events outside of the synchronized block.
+ Map resolvedModuleWireMap = null;
+
+ // Synchronize on the module manager, because we don't want
+ // any modules being added or removed while we are in the
+ // middle of this operation.
+ synchronized (m_mgr)
+ {
+ // The first step is to populate the resolver map. This
+ // will use the target module to populate the resolver map
+ // with all potential modules that need to be resolved as a
+ // result of resolving the target module. The key of the
+ // map is a potential module to be resolved and the value is
+ // a list of resolver nodes, one for each of the module's
+ // imports, where each resolver node contains the potential
+ // candidates for resolving the import. Not all modules in
+ // this map will be resolved, only the target module and
+ // any candidates selected to resolve its imports and the
+ // transitive imports this implies.
+ populateResolverMap(resolverMap, rootModule);
+
+//dumpResolverMap();
+
+ // The next step is to use the resolver map to determine if
+ // the class space for the root module is consistent. This
+ // is an iterative process that transitively walks the "uses"
+ // relationships of all currently selected potential candidates
+ // for resolving import packages checking for conflicts. If a
+ // conflict is found, it "increments" the configuration of
+ // currently selected potential candidates and tests them again.
+ // If this method returns, then it has found a consistent set
+ // of candidates; otherwise, a resolve exception is thrown if
+ // it exhausts all possible combinations and could not find a
+ // consistent class space.
+ findConsistentClassSpace(resolverMap, rootModule);
+
+ // The final step is to create the wires for the root module and
+ // transitively all modules that are to be resolved from the
+ // selected candidates for resolving the root module's imports.
+ // When this call returns, each module's wiring and resolved
+ // attributes are set. The resulting wiring map is used below
+ // to fire resolved events outside of the synchronized block.
+ // The resolved module wire map maps a module to its array of
+ // wires.
+ resolvedModuleWireMap = createWires(resolverMap, rootModule);
+
+//dumpAvailablePackages();
+//dumpUsedPackages();
+
+ } // End of synchronized block on module manager.
+
+ // Fire resolved events for all resolved modules;
+ // the resolved modules array will only be set if the resolve
+ // was successful after the root module was resolved.
+ if (resolvedModuleWireMap != null)
+ {
+ Iterator iter = resolvedModuleWireMap.entrySet().iterator();
+ while (iter.hasNext())
+ {
+ fireModuleResolved((Module) ((Map.Entry) iter.next()).getKey());
+ }
+ }
+ }
+
+ private void populateResolverMap(Map resolverMap, Module module)
+ throws ResolveException
+ {
+ // Detect cycles.
+ if (resolverMap.get(module) != null)
+ {
+ return;
+ }
+
+ // Map to hold the bundle's import packages
+ // and their respective resolving candidates.
+ List nodeList = new ArrayList();
+
+ // Even though the node list is currently emptry, we
+ // record it in the resolver map early so we can use
+ // it to detect cycles.
+ resolverMap.put(module, nodeList);
+
+ // Loop through each import and calculate its resolving
+ // set of candidates.
+ R4Package[] imports = getImportsAttr(module);
+ for (int impIdx = 0; impIdx < imports.length; impIdx++)
+ {
+ // Get the candidates from the "in use" and "available"
+ // package maps. Candidates "in use" have higher priority
+ // than "available" ones, so put the "in use" candidates
+ // at the front of the list of candidates.
+ Module[] inuse = getCompatibleExporters(
+ (Module[]) m_inUsePkgMap.get(
+ imports[impIdx].getId()), imports[impIdx]);
+ Module[] available = getCompatibleExporters(
+ (Module[]) m_availPkgMap.get(
+ imports[impIdx].getId()), imports[impIdx]);
+ Module[] candidates = new Module[inuse.length + available.length];
+ System.arraycopy(inuse, 0, candidates, 0, inuse.length);
+ System.arraycopy(available, 0, candidates, inuse.length, available.length);
+
+ // If we have candidates, then we need to recursively populate
+ // the resolver map with each of them.
+ ResolveException rethrow = null;
+ if (candidates.length > 0)
+ {
+ for (int candIdx = 0; candIdx < candidates.length; candIdx++)
+ {
+ try
+ {
+ // Only populate the resolver map with modules that
+ // are not already resolved.
+ if (!getResolvedAttr(candidates[candIdx]).booleanValue())
+ {
+ populateResolverMap(resolverMap, candidates[candIdx]);
+ }
+ }
+ catch (ResolveException ex)
+ {
+ // If we received a resolve exception, then the
+ // current candidate is not resolvable for some
+ // reason and should be removed from the list of
+ // candidates. For now, just null it.
+ candidates[candIdx] = null;
+ rethrow = ex;
+ }
+ }
+
+ // Remove any nulled candidates to create the final list
+ // of available candidates.
+ candidates = shrinkModuleArray(candidates);
+ }
+
+ // If no candidates exist at this point, then throw a
+ // resolve exception unless the import is optional.
+ if ((candidates.length == 0) && !imports[impIdx].isOptional())
+ {
+ // If we have received an exception while trying to populate
+ // the resolver map, rethrow that exception since it might
+ // be useful. NOTE: This is not necessarily the "only"
+ // correct exception, since it is possible that multiple
+ // candidates were not resolvable, but it is better than
+ // nothing.
+ if (rethrow != null)
+ {
+ throw rethrow;
+ }
+ else
+ {
+ throw new ResolveException(
+ "Unable to resolve.", module, imports[impIdx]);
+ }
+ }
+ else if (candidates.length > 0)
+ {
+ nodeList.add(
+ new ResolverNode(module, imports[impIdx], candidates));
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * This method searches the resolver solution space for a consistent
+ * set of modules to resolve all transitive imports that must be resolved
+ * as a result of resolving the root module. A consistent set of modules
+ * is one where the "uses" relationships of the exported packages for
+ * the selected provider modules do not conflict with each other. A
+ * conflict can occur when the constraints on two or more modules result
+ * in multiple providers for the same package in the same class space.
+ * </p>
+ * @param resolverMap a map containing all potential modules that may need
+ * to be resolved and the candidates to resolve them.
+ * @param rootModule the module that is the root of the resolve operation.
+ * @throws ResolveException if no consistent set of candidates can be
+ * found to resolve the root module.
+ **/
+ private void findConsistentClassSpace(Map resolverMap, Module rootModule)
+ throws ResolveException
+ {
+ List resolverList = null;
+
+ // Test the current set of candidates to determine if they
+ // are consistent. Keep looping until we find a consistent
+ // set or an exception is thrown.
+ Map cycleMap = new HashMap();
+ while (!isClassSpaceConsistent(resolverMap, rootModule, cycleMap))
+ {
+m_logger.log(
+ LogWrapper.LOG_DEBUG,
+ "Constraint violation detected, will try to repair.");
+
+ // The incrementCandidateConfiguration() method requires a
+ // ordered access to the resolver map, so we will create
+ // a reusable list once right here.
+ if (resolverList == null)
+ {
+ resolverList = new ArrayList();
+ for (Iterator iter = resolverMap.entrySet().iterator();
+ iter.hasNext(); )
+ {
+ resolverList.add((List) ((Map.Entry) iter.next()).getValue());
+ }
+ }
+
+ // Increment the candidate configuration so we can test again.
+ incrementCandidateConfiguration(resolverList);
+
+ // Clear the cycle map.
+ cycleMap.clear();
+ }
+ }
+
+ private boolean isClassSpaceConsistent(
+ Map resolverMap, Module rootModule, Map cycleMap)
+ {
+ // We do not need to verify that already resolved modules
+ // have consistent class spaces because they should be
+ // consistent by definition. Also, if the root module is
+ // part of a cycle, then just assume it is true.
+ if (getResolvedAttr(rootModule).booleanValue() ||
+ (cycleMap.get(rootModule) != null))
+ {
+ return true;
+ }
+
+ // Add to cycle map for future reference.
+ cycleMap.put(rootModule, rootModule);
+
+ // Create an implicit "uses" constraint for every exported package
+ // of the root module that is not also imported; uses constraints
+ // for exported packages that are also imported will be taken
+ // care of as part of normal import package processing.
+ R4Package[] exports = (R4Package[]) getExportsAttr(rootModule);
+ Map usesMap = new HashMap();
+ for (int i = 0; i < exports.length; i++)
+ {
+ // Ignore exports that are also imported, since they
+ // will be taken care of when verifying import constraints.
+ if (getImportPackage(rootModule, exports[i].getId()) == null)
+ {
+ usesMap.put(exports[i].getId(), rootModule);
+ }
+ }
+
+ // Loop through the current candidates for the module's imports
+ // (available in the resolver node list of the resolver map) and
+ // calculate the uses constraints for each of the currently
+ // selected candidates for resolving the imports. Compare each
+ // candidate's constraints to the existing constraints to check
+ // for conflicts.
+ List nodeList = (List) resolverMap.get(rootModule);
+ for (int nodeIdx = 0; nodeIdx < nodeList.size(); nodeIdx++)
+ {
+ // Verify that the current candidate does not violate
+ // any "uses" constraints of existing candidates by
+ // calculating the candidate's transitive "uses" constraints
+ // for the provided package and testing whether they
+ // overlap with existing constraints.
+
+ // First, get the resolver node.
+ ResolverNode node = (ResolverNode) nodeList.get(nodeIdx);
+
+ // Verify that the current candidate itself has a consistent
+ // class space.
+ if (!isClassSpaceConsistent(
+ resolverMap, node.m_candidates[node.m_idx], cycleMap))
+ {
+ return false;
+ }
+
+ // Get the exported package from the current candidate that
+ // will be used to resolve the root module's import.
+ R4Package candidatePkg = getExportPackage(
+ node.m_candidates[node.m_idx], node.m_pkg.getId());
+
+ // Calculate the "uses" dependencies implied by the candidate's
+ // exported package with respect to the currently selected
+ // candidates in the resolver map.
+ Map candUsesMap = calculateUsesDependencies(
+ resolverMap,
+ node.m_candidates[node.m_idx],
+ candidatePkg,
+ new HashMap());
+//System.out.println("MODULE " + rootModule + " USES " + usesMap);
+//System.out.println("CANDIDATE " + node.m_candidates[node.m_idx] + " USES " + candUsesMap);
+
+ // Iterate through the root module's current set of transitive
+ // "uses" constraints and compare them with the candidate's
+ // transitive set of constraints.
+ Iterator usesIter = candUsesMap.entrySet().iterator();
+ while (usesIter.hasNext())
+ {
+ // If the candidate's uses constraints overlap with
+ // the existing uses constraints, but refer to a
+ // different provider, then the class space is not
+ // consistent; thus, return false.
+ Map.Entry entry = (Map.Entry) usesIter.next();
+ if ((usesMap.get(entry.getKey()) != null) &&
+ (usesMap.get(entry.getKey()) != entry.getValue()))
+ {
+ return false;
+ }
+ }
+
+ // Since the current candidate's uses constraints did not
+ // conflict with existing constraints, merge all constraints
+ // and keep testing the remaining candidates for the other
+ // imports of the root module.
+ usesMap.putAll(candUsesMap);
+ }
+
+ return true;
+ }
+
+ private Map calculateUsesDependencies(
+ Map resolverMap, Module module, R4Package exportPkg, Map usesMap)
+ {
+// TODO: CAN THIS BE OPTIMIZED?
+// TODO: IS THIS CYCLE CHECK CORRECT??
+// TODO: WHAT HAPPENS THERE ARE OVERLAPS WHEN CALCULATING USES??
+// MAKE AN EXAMPLE WHERE TWO DEPENDENCIES PROVIDE SAME PACKAGE.
+ // Make sure we are not in a cycle.
+ if (usesMap.get(exportPkg.getId()) != null)
+ {
+ return usesMap;
+ }
+
+ // The target package at least uses itself,
+ // so add it to the uses map.
+ usesMap.put(exportPkg.getId(), module);
+
+ // Get the "uses" constraints for the target export
+ // package and calculate the transitive uses constraints
+ // of any used packages.
+ String[] uses = exportPkg.getUses();
+ List nodeList = (List) resolverMap.get(module);
+
+ // We need to walk the transitive closure of "uses" relationships
+ // for the current export package to calculate the entire set of
+ // "uses" constraints.
+ for (int usesIdx = 0; usesIdx < uses.length; usesIdx++)
+ {
+ // There are two possibilities at this point: 1) we are dealing
+ // with an already resolved bundle or 2) we are dealing with a
+ // bundle that has not yet been resolved. In case 1, there will
+ // be no resolver node in the resolver map, so we just need to
+ // examine the bundle directly to determine its exact constraints.
+ // In case 2, there will be a resolver node in the resolver map,
+ // so we will use that to determine the potential constraints of
+ // potential candidate for resolving the import.
+
+ // This is case 1, described in the comment above.
+ if (nodeList == null)
+ {
+ // Get the actual exporter from the wire or if there
+ // is no wire, then get the export is from the module
+ // itself.
+ R4Wire wire = getWire(module, uses[usesIdx]);
+ if (wire != null)
+ {
+ usesMap = calculateUsesDependencies(
+ resolverMap, wire.m_module, wire.m_pkg, usesMap);
+ }
+ else
+ {
+ exportPkg = getExportPackage(module, uses[usesIdx]);
+ if (exportPkg != null)
+ {
+ usesMap = calculateUsesDependencies(
+ resolverMap, module, exportPkg, usesMap);
+ }
+ }
+ }
+ // This is case 2, described in the comment above.
+ else
+ {
+ // First, get the resolver node for the "used" package.
+ ResolverNode node = null;
+ for (int nodeIdx = 0;
+ (node == null) && (nodeIdx < nodeList.size());
+ nodeIdx++)
+ {
+ node = (ResolverNode) nodeList.get(nodeIdx);
+ if (!node.m_pkg.getId().equals(uses[usesIdx]))
+ {
+ node = null;
+ }
+ }
+
+ // If there is a resolver node for the "used" package,
+ // then this means that the module imports the package
+ // and we need to recursively add the constraints of
+ // the potential exporting module.
+ if (node != null)
+ {
+ usesMap = calculateUsesDependencies(
+ resolverMap,
+ node.m_candidates[node.m_idx],
+ getExportPackage(node.m_candidates[node.m_idx], node.m_pkg.getId()),
+ usesMap);
+ }
+ // If there was no resolver node for the "used" package,
+ // then this means that the module exports the package
+ // and we need to recursively add the constraints of this
+ // other exported package of this module.
+ else if (getExportPackage(module, uses[usesIdx]) != null)
+ {
+ usesMap = calculateUsesDependencies(
+ resolverMap,
+ module,
+ getExportPackage(module, uses[usesIdx]),
+ usesMap);
+ }
+ }
+ }
+
+ return usesMap;
+ }
+
+ /**
+ * <p>
+ * This method <i>increments</i> the current candidate configuration
+ * in the specified resolver list, which contains resolver node lists
+ * for all of the candidates for all of the imports that need to be
+ * resolved. This method performs its function by treating the current
+ * candidate index variable in each resolver node as part of a big
+ * counter. In other words, it increments the least significant index.
+ * If the index overflows it sets it back to zero and carries the
+ * overflow to the next significant index and so on. Using this approach
+ * it checks every possible combination for a solution.
+ * </p>
+ * <p>
+ * This method is inefficient and a better approach is necessary. For
+ * example, it does not take into account which imports are actually
+ * being used, it just increments starting at the beginning of the list.
+ * This means that it could be modifying candidates that are not relevant
+ * to the current configuration and re-testing even though nothing has
+ * really changed. It needs to be smarter.
+ * </p>
+ * @param resolverList an ordered list of resolver node lists for all
+ * the candidates of the potential imports that need to be
+ * resolved.
+ * @throws ResolveException if the increment overflows the entire list,
+ * signifying no consistent configurations exist.
+ **/
+ private void incrementCandidateConfiguration(List resolverList)
+ throws ResolveException
+ {
+ for (int i = 0; i < resolverList.size(); i++)
+ {
+ List nodeList = (List) resolverList.get(i);
+ for (int j = 0; j < nodeList.size(); j++)
+ {
+ ResolverNode node = (ResolverNode) nodeList.get(j);
+ // See if we can increment the node, without overflowing
+ // the candidate array bounds.
+ if ((node.m_idx + 1) < node.m_candidates.length)
+ {
+ node.m_idx++;
+ return;
+ }
+ // If the index will overflow the candidate array bounds,
+ // then set the index back to zero and try to increment
+ // the next candidate.
+ else
+ {
+ node.m_idx = 0;
+ }
+ }
+ }
+ throw new ResolveException(
+ "Unable to resolve due to constraint violation.", null, null);
+ }
+
+ private Map createWires(Map resolverMap, Module rootModule)
+ {
+ Map resolvedModuleWireMap =
+ populateWireMap(resolverMap, rootModule, new HashMap());
+ Iterator iter = resolvedModuleWireMap.entrySet().iterator();
+ while (iter.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ Module module = (Module) entry.getKey();
+ R4Wire[] wires = (R4Wire[]) entry.getValue();
+
+ // Set the module's resolved and wiring attribute.
+ module.setAttribute(RESOLVED_ATTR, Boolean.TRUE);
+ // Only add wires attribute if some exist; export
+ // only modules may not have wires.
+ if (wires.length > 0)
+ {
+ module.setAttribute(WIRING_ATTR, wires);
+ }
+
+ // Remove the wire's exporting module from the "available"
+ // package map and put it into the "in use" package map;
+ // these steps may be a no-op.
+ for (int wireIdx = 0;
+ (wires != null) && (wireIdx < wires.length);
+ wireIdx++)
+ {
+m_logger.log(LogWrapper.LOG_DEBUG, "WIRE: [" + module + "] " + wires[wireIdx]);
+ // First remove the wire module from "available" package map.
+ Module[] modules = (Module[]) m_availPkgMap.get(wires[wireIdx].m_pkg.getId());
+ modules = removeModuleFromArray(modules, wires[wireIdx].m_module);
+ m_availPkgMap.put(wires[wireIdx].m_pkg.getId(), modules);
+
+ // Also remove any exported packages from the "available"
+ // package map that are from the module associated with
+ // the current wires where the exported packages were not
+ // actually exported; an export may not be exported if
+ // the module also imports the same package and was wired
+ // to a different module. If the exported package is not
+ // actually exported, then we just want to remove it
+ // completely, since it cannot be used.
+ if (wires[wireIdx].m_module != module)
+ {
+ modules = (Module[]) m_availPkgMap.get(wires[wireIdx].m_pkg.getId());
+ modules = removeModuleFromArray(modules, module);
+ m_availPkgMap.put(wires[wireIdx].m_pkg.getId(), modules);
+ }
+
+ // Add the module of the wire to the "in use" package map.
+ modules = (Module[]) m_inUsePkgMap.get(wires[wireIdx].m_pkg.getId());
+ modules = addModuleToArray(modules, wires[wireIdx].m_module);
+ m_inUsePkgMap.put(wires[wireIdx].m_pkg.getId(), modules);
+ }
+ }
+ return resolvedModuleWireMap;
+ }
+
+ private Map populateWireMap(Map resolverMap, Module module, Map wireMap)
+ {
+ // If the module is already resolved or it is part of
+ // a cycle, then just return the wire map.
+ if (getResolvedAttr(module).booleanValue() ||
+ (wireMap.get(module) != null))
+ {
+ return wireMap;
+ }
+
+ List nodeList = (List) resolverMap.get(module);
+ R4Wire[] wires = new R4Wire[nodeList.size()];
+
+ // Put the module in the wireMap with an empty wire array;
+ // we do this early so we can use it to detect cycles.
+ wireMap.put(module, wires);
+
+ // Loop through each resolver node and create a wire
+ // for the selected candidate for the associated import.
+ for (int nodeIdx = 0; nodeIdx < nodeList.size(); nodeIdx++)
+ {
+ // Get the import's associated resolver node.
+ ResolverNode node = (ResolverNode) nodeList.get(nodeIdx);
+
+ // Add the candidate to the list of wires.
+ R4Package exportPkg =
+ getExportPackage(node.m_candidates[node.m_idx], node.m_pkg.getId());
+ wires[nodeIdx] = new R4Wire(exportPkg, node.m_candidates[node.m_idx]);
+
+ // Create the wires for the selected candidate module.
+ wireMap = populateWireMap(resolverMap, node.m_candidates[node.m_idx], wireMap);
+ }
+
+ return wireMap;
+ }
+
+// TODO: REMOVE THESE DEBUG METHODS.
+ private void dumpResolverMap(Map resolverMap)
+ {
+ Iterator iter = resolverMap.entrySet().iterator();
+ while (iter.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ ResolverNode node = (ResolverNode) entry.getValue();
+ System.out.println("MODULE " + node.m_module + " IMPORT " + node.m_pkg);
+ for (int i = 0; i < node.m_candidates.length; i++)
+ {
+ System.out.println("--> " + node.m_candidates[i]);
+ }
+ }
+ }
+
+ private void dumpAvailablePackages()
+ {
+ synchronized (m_mgr)
+ {
+ System.out.println("AVAILABLE PACKAGES:");
+ for (Iterator i = m_availPkgMap.entrySet().iterator(); i.hasNext(); )
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+ System.out.println(" " + entry.getKey());
+ Module[] modules = (Module[]) entry.getValue();
+ for (int j = 0; j < modules.length; j++)
+ {
+ System.out.println(" " + modules[j]);
+ }
+ }
+ }
+ }
+
+ private void dumpUsedPackages()
+ {
+ synchronized (m_mgr)
+ {
+ System.out.println("USED PACKAGES:");
+ for (Iterator i = m_inUsePkgMap.entrySet().iterator(); i.hasNext(); )
+ {
+ Map.Entry entry = (Map.Entry) i.next();
+ System.out.println(" " + entry.getKey());
+ Module[] modules = (Module[]) entry.getValue();
+ for (int j = 0; j < modules.length; j++)
+ {
+ System.out.println(" " + modules[j]);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method returns a list of modules that have an export
+ * that is compatible with the given import identifier and version.
+ * @param pkgMap a map of export packages to exporting modules.
+ * @param target the target import package.
+ * @return an array of modules that have compatible exports or <tt>null</tt>
+ * if none are found.
+ **/
+ protected Module[] getCompatibleExporters(Module[] modules, R4Package target)
+ {
+ // Create list of compatible exporters.
+ Module[] candidates = null;
+ for (int modIdx = 0; (modules != null) && (modIdx < modules.length); modIdx++)
+ {
+ // Get the modules export package for the target package.
+ R4Package exportPkg = getExportPackage(modules[modIdx], target.getId());
+ // If compatible, then add the candidate to the list.
+ if ((exportPkg != null) && (exportPkg.doesSatisfy(target)))
+ {
+ candidates = addModuleToArray(candidates, modules[modIdx]);
+ }
+ }
+
+ if (candidates == null)
+ {
+ return m_emptyModules;
+ }
+
+ return candidates;
+ }
+
+ public void moduleAdded(ModuleEvent event)
+ {
+ // When a module is added to the system, we need to initialize
+ // its resolved and wiring attributes and add its exports to
+ // the map of available exports.
+
+ // Synchronize on the module manager, since we don't want any
+ // bundles to be installed or removed.
+ synchronized (m_mgr)
+ {
+ // Add wiring attribute.
+ event.getModule().setAttribute(WIRING_ATTR, null);
+ // Add resolved attribute.
+ event.getModule().setAttribute(RESOLVED_ATTR, Boolean.FALSE);
+ // Add exports to available package map.
+ R4Package[] exports = getExportsAttr(event.getModule());
+ for (int i = 0; i < exports.length; i++)
+ {
+ Module[] modules = (Module[]) m_availPkgMap.get(exports[i].getId());
+
+ // We want to add the module into the list of available
+ // exporters in sorted order (descending version and
+ // ascending bundle identifier). Insert using a simple
+ // binary search algorithm.
+ if (modules == null)
+ {
+ modules = new Module[] { event.getModule() };
+ }
+ else
+ {
+ int top = 0, bottom = modules.length - 1, middle = 0;
+ R4Version middleVersion = null;
+ while (top <= bottom)
+ {
+ middle = (bottom - top) / 2 + top;
+ middleVersion = getExportPackage(
+ modules[middle], exports[i].getId()).getVersionLow();
+ // Sort in reverse version order.
+ int cmp = middleVersion.compareTo(exports[i].getVersionLow());
+ if (cmp < 0)
+ {
+ bottom = middle - 1;
+ }
+ else if (cmp == 0)
+ {
+ // Sort further by ascending bundle ID.
+ long middleId = getBundleIdFromModuleId(modules[middle].getId());
+ long exportId = getBundleIdFromModuleId(event.getModule().getId());
+ if (middleId < exportId)
+ {
+ top = middle + 1;
+ }
+ else
+ {
+ bottom = middle - 1;
+ }
+ }
+ else
+ {
+ top = middle + 1;
+ }
+ }
+
+ Module[] newMods = new Module[modules.length + 1];
+ System.arraycopy(modules, 0, newMods, 0, top);
+ System.arraycopy(modules, top, newMods, top + 1, modules.length - top);
+ newMods[top] = event.getModule();
+ modules = newMods;
+ }
+
+ m_availPkgMap.put(exports[i].getId(), modules);
+ }
+ }
+ }
+
+ public void moduleReset(ModuleEvent event)
+ {
+ moduleRemoved(event);
+ }
+
+ public void moduleRemoved(ModuleEvent event)
+ {
+ // When a module is removed from the system, we need remove
+ // its exports from the "in use" and "available" package maps.
+
+ // Synchronize on the module manager, since we don't want any
+ // bundles to be installed or removed.
+ synchronized (m_mgr)
+ {
+ // Remove exports from package maps.
+ R4Package[] pkgs = getExportsAttr(event.getModule());
+ for (int i = 0; i < pkgs.length; i++)
+ {
+ // Remove from "available" package map.
+ Module[] modules = (Module[]) m_availPkgMap.get(pkgs[i].getId());
+ if (modules != null)
+ {
+ modules = removeModuleFromArray(modules, event.getModule());
+ m_availPkgMap.put(pkgs[i].getId(), modules);
+ }
+ // Remove from "in use" package map.
+ modules = (Module[]) m_inUsePkgMap.get(pkgs[i].getId());
+ if (modules != null)
+ {
+ modules = removeModuleFromArray(modules, event.getModule());
+ m_inUsePkgMap.put(pkgs[i].getId(), modules);
+ }
+ }
+ }
+ }
+
+ // This is duplicated from BundleInfo and probably shouldn't be,
+ // but its functionality is needed by the moduleAdded() callback.
+ protected static long getBundleIdFromModuleId(String id)
+ {
+ try
+ {
+ String bundleId = (id.indexOf('.') >= 0)
+ ? id.substring(0, id.indexOf('.')) : id;
+ return Long.parseLong(bundleId);
+ }
+ catch (NumberFormatException ex)
+ {
+ return -1;
+ }
+ }
+
+ //
+ // Event handling methods for validation events.
+ //
+
+ /**
+ * Adds a resolver listener to the search policy. Resolver
+ * listeners are notified when a module is resolve and/or unresolved
+ * by the search policy.
+ * @param l the resolver listener to add.
+ **/
+ public void addResolverListener(ResolveListener l)
+ {
+ // Verify listener.
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener is null");
+ }
+
+ // Use the m_noListeners object as a lock.
+ synchronized (m_emptyListeners)
+ {
+ // If we have no listeners, then just add the new listener.
+ if (m_listeners == m_emptyListeners)
+ {
+ m_listeners = new ResolveListener[] { l };
+ }
+ // Otherwise, we need to do some array copying.
+ // Notice, the old array is always valid, so if
+ // the dispatch thread is in the middle of a dispatch,
+ // then it has a reference to the old listener array
+ // and is not affected by the new value.
+ else
+ {
+ ResolveListener[] newList = new ResolveListener[m_listeners.length + 1];
+ System.arraycopy(m_listeners, 0, newList, 0, m_listeners.length);
+ newList[m_listeners.length] = l;
+ m_listeners = newList;
+ }
+ }
+ }
+
+ /**
+ * Removes a resolver listener to this search policy.
+ * @param l the resolver listener to remove.
+ **/
+ public void removeResolverListener(ResolveListener l)
+ {
+ // Verify listener.
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener is null");
+ }
+
+ // Use the m_emptyListeners object as a lock.
+ synchronized (m_emptyListeners)
+ {
+ // Try to find the instance in our list.
+ int idx = -1;
+ for (int i = 0; i < m_listeners.length; i++)
+ {
+ if (m_listeners[i].equals(l))
+ {
+ idx = i;
+ break;
+ }
+ }
+
+ // If we have the instance, then remove it.
+ if (idx >= 0)
+ {
+ // If this is the last listener, then point to empty list.
+ if (m_listeners.length == 1)
+ {
+ m_listeners = m_emptyListeners;
+ }
+ // Otherwise, we need to do some array copying.
+ // Notice, the old array is always valid, so if
+ // the dispatch thread is in the middle of a dispatch,
+ // then it has a reference to the old listener array
+ // and is not affected by the new value.
+ else
+ {
+ ResolveListener[] newList = new ResolveListener[m_listeners.length - 1];
+ System.arraycopy(m_listeners, 0, newList, 0, idx);
+ if (idx < newList.length)
+ {
+ System.arraycopy(m_listeners, idx + 1, newList, idx,
+ newList.length - idx);
+ }
+ m_listeners = newList;
+ }
+ }
+ }
+ }
+
+ /**
+ * Fires a validation event for the specified module.
+ * @param module the module that was resolved.
+ **/
+ protected void fireModuleResolved(Module module)
+ {
+ // Event holder.
+ ModuleEvent event = null;
+
+ // Get a copy of the listener array, which is guaranteed
+ // to not be null.
+ ResolveListener[] listeners = m_listeners;
+
+ // Loop through listeners and fire events.
+ for (int i = 0; i < listeners.length; i++)
+ {
+ // Lazily create event.
+ if (event == null)
+ {
+ event = new ModuleEvent(m_mgr, module);
+ }
+ listeners[i].moduleResolved(event);
+ }
+ }
+
+ /**
+ * Fires an unresolved event for the specified module.
+ * @param module the module that was unresolved.
+ **/
+ protected void fireModuleUnresolved(Module module)
+ {
+ // Event holder.
+ ModuleEvent event = null;
+
+ // Get a copy of the listener array, which is guaranteed
+ // to not be null.
+ ResolveListener[] listeners = m_listeners;
+
+ // Loop through listeners and fire events.
+ for (int i = 0; i < listeners.length; i++)
+ {
+ // Lazily create event.
+ if (event == null)
+ {
+ event = new ModuleEvent(m_mgr, module);
+ }
+ listeners[i].moduleUnresolved(event);
+ }
+ }
+
+ //
+ // Static utility methods.
+ //
+
+ public static Boolean getResolvedAttr(Module m)
+ {
+ Boolean b =
+ (Boolean) m.getAttribute(RESOLVED_ATTR);
+ if (b == null)
+ {
+ b = Boolean.FALSE;
+ }
+ return b;
+ }
+
+ public static R4Package[] getExportsAttr(Module m)
+ {
+ R4Package[] attr =
+ (R4Package[]) m.getAttribute(EXPORTS_ATTR);
+ return (attr == null) ? m_emptyPackages : attr;
+ }
+
+ public static R4Package getExportPackage(Module m, String id)
+ {
+ R4Package[] pkgs = getExportsAttr(m);
+ for (int i = 0; (pkgs != null) && (i < pkgs.length); i++)
+ {
+ if (pkgs[i].getId().equals(id))
+ {
+ return pkgs[i];
+ }
+ }
+ return null;
+ }
+
+ public static R4Package[] getImportsAttr(Module m)
+ {
+ R4Package[] attr =
+ (R4Package[]) m.getAttribute(IMPORTS_ATTR);
+ return (attr == null) ? m_emptyPackages: attr;
+ }
+
+ public static R4Package getImportPackage(Module m, String id)
+ {
+ R4Package[] pkgs = getImportsAttr(m);
+ for (int i = 0; (pkgs != null) && (i < pkgs.length); i++)
+ {
+ if (pkgs[i].getId().equals(id))
+ {
+ return pkgs[i];
+ }
+ }
+ return null;
+ }
+
+ public static R4Package[] getDynamicImportsAttr(Module m)
+ {
+ R4Package[] attr =
+ (R4Package[]) m.getAttribute(DYNAMICIMPORTS_ATTR);
+ return (attr == null) ? m_emptyPackages: attr;
+ }
+
+ public static R4Package getDynamicImportPackage(Module m, String id)
+ {
+ R4Package[] pkgs = getDynamicImportsAttr(m);
+ for (int i = 0; (pkgs != null) && (i < pkgs.length); i++)
+ {
+ if (pkgs[i].getId().equals(id))
+ {
+ return pkgs[i];
+ }
+ }
+ return null;
+ }
+
+ public static R4Wire[] getWiringAttr(Module m)
+ {
+ R4Wire[] attr =
+ (R4Wire[]) m.getAttribute(WIRING_ATTR);
+ if (attr == null)
+ {
+ attr = m_emptyWires;
+ }
+ return attr;
+ }
+
+ public static R4Wire getWire(Module m, String id)
+ {
+ R4Wire[] wires = getWiringAttr(m);
+ for (int i = 0; (wires != null) && (i < wires.length); i++)
+ {
+ if (wires[i].m_pkg.getId().equals(id))
+ {
+ return wires[i];
+ }
+ }
+ return null;
+ }
+
+ public static boolean isModuleInArray(Module[] modules, Module m)
+ {
+ // Verify that the module is not already in the array.
+ for (int i = 0; (modules != null) && (i < modules.length); i++)
+ {
+ if (modules[i] == m)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static Module[] addModuleToArray(Module[] modules, Module m)
+ {
+ // Verify that the module is not already in the array.
+ for (int i = 0; (modules != null) && (i < modules.length); i++)
+ {
+ if (modules[i] == m)
+ {
+ return modules;
+ }
+ }
+
+ if (modules != null)
+ {
+ Module[] newModules = new Module[modules.length + 1];
+ System.arraycopy(modules, 0, newModules, 0, modules.length);
+ newModules[modules.length] = m;
+ modules = newModules;
+ }
+ else
+ {
+ modules = new Module[] { m };
+ }
+
+ return modules;
+ }
+
+ public static Module[] removeModuleFromArray(Module[] modules, Module m)
+ {
+ if (modules == null)
+ {
+ return m_emptyModules;
+ }
+
+ int idx = -1;
+ for (int i = 0; i < modules.length; i++)
+ {
+ if (modules[i] == m)
+ {
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx >= 0)
+ {
+ // If this is the module, then point to empty list.
+ if ((modules.length - 1) == 0)
+ {
+ modules = m_emptyModules;
+ }
+ // Otherwise, we need to do some array copying.
+ else
+ {
+ Module[] newModules= new Module[modules.length - 1];
+ System.arraycopy(modules, 0, newModules, 0, idx);
+ if (idx < newModules.length)
+ {
+ System.arraycopy(
+ modules, idx + 1, newModules, idx, newModules.length - idx);
+ }
+ modules = newModules;
+ }
+ }
+ return modules;
+ }
+
+// TODO: INVESTIGATE GENERIC ARRAY GROWING/SHRINKING.
+ private static R4Wire[] shrinkWireArray(R4Wire[] wires)
+ {
+ if (wires == null)
+ {
+ return m_emptyWires;
+ }
+
+ int count = 0;
+ for (int i = 0; i < wires.length; i++)
+ {
+ if (wires[i] == null)
+ {
+ count++;
+ }
+ }
+
+ if (count > 0)
+ {
+ R4Wire[] newWires = new R4Wire[wires.length - count];
+ count = 0;
+ for (int i = 0; i < wires.length; i++)
+ {
+ if (wires[i] != null)
+ {
+ newWires[count++] = wires[i];
+ }
+ }
+ wires = newWires;
+ }
+
+ return wires;
+ }
+
+ private static Module[] shrinkModuleArray(Module[] modules)
+ {
+ if (modules == null)
+ {
+ return m_emptyModules;
+ }
+
+ int count = 0;
+ for (int i = 0; i < modules.length; i++)
+ {
+ if (modules[i] == null)
+ {
+ count++;
+ }
+ }
+
+ if (count > 0)
+ {
+ Module[] newModules = new Module[modules.length - count];
+ count = 0;
+ for (int i = 0; i < modules.length; i++)
+ {
+ if (modules[i] != null)
+ {
+ newModules[count++] = modules[i];
+ }
+ }
+ modules = newModules;
+ }
+
+ return modules;
+ }
+
+ private static class ResolverNode
+ {
+ public Module m_module = null;
+ public R4Package m_pkg = null;
+ public Module[] m_candidates = null;
+ public int m_idx = 0;
+ public boolean m_visited = false;
+ public ResolverNode(Module module, R4Package pkg, Module[] candidates)
+ {
+ m_module = module;
+ m_pkg = pkg;
+ m_candidates = candidates;
+ if (getResolvedAttr(m_module).booleanValue())
+ {
+ m_visited = true;
+ }
+ }
+ }
+
+ // Utility class to get the class context from the security manager.
+ private static class SecurityManagerX extends SecurityManager
+ {
+ public Class[] getClassContext()
+ {
+ return super.getClassContext();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4Version.java b/src/org/apache/osgi/framework/searchpolicy/R4Version.java
new file mode 100644
index 0000000..e7f11fc
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4Version.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.searchpolicy;
+
+import java.util.StringTokenizer;
+
+public class R4Version implements Comparable
+{
+ private int m_major = 0;
+ private int m_minor = 0;
+ private int m_micro = 0;
+ private String m_qualifier = "";
+ private boolean m_isInclusive = true;
+
+ private static final String SEPARATOR = ".";
+
+ public R4Version(String versionString)
+ {
+ this(versionString, true);
+ }
+
+ public R4Version(String versionString, boolean isInclusive)
+ {
+ if (versionString == null)
+ {
+ versionString = "0.0.0";
+ }
+ Object[] objs = parseVersion(versionString);
+ m_major = ((Integer) objs[0]).intValue();
+ m_minor = ((Integer) objs[1]).intValue();
+ m_micro = ((Integer) objs[2]).intValue();
+ m_qualifier = (String) objs[3];
+ m_isInclusive = isInclusive;
+ }
+
+ private static Object[] parseVersion(String versionString)
+ {
+ String s = versionString.trim();
+ Object[] objs = new Object[4];
+ objs[0] = objs[1] = objs[2] = new Integer(0);
+ objs[3] = "";
+ StringTokenizer tok = new StringTokenizer(s, SEPARATOR);
+ try
+ {
+ objs[0] = Integer.valueOf(tok.nextToken());
+ if (tok.hasMoreTokens())
+ {
+ objs[1] = Integer.valueOf(tok.nextToken());
+ if (tok.hasMoreTokens())
+ {
+ objs[2] = Integer.valueOf(tok.nextToken());
+ if (tok.hasMoreTokens())
+ {
+ objs[3] = tok.nextToken();
+ }
+ }
+ }
+ }
+ catch (NumberFormatException ex)
+ {
+ throw new IllegalArgumentException("Invalid version: " + versionString);
+ }
+
+ if ((((Integer) objs[0]).intValue() < 0) ||
+ (((Integer) objs[0]).intValue() < 0) ||
+ (((Integer) objs[0]).intValue() < 0))
+ {
+ throw new IllegalArgumentException("Invalid version: " + versionString);
+ }
+
+ return objs;
+ }
+
+ public boolean equals(Object object)
+ {
+ if (!(object instanceof R4Version))
+ {
+ return false;
+ }
+ R4Version v = (R4Version) object;
+ return
+ (v.getMajorComponent() == m_major) &&
+ (v.getMinorComponent() == m_minor) &&
+ (v.getMicroComponent() == m_micro) &&
+ (v.getQualifierComponent().equals(m_qualifier));
+ }
+
+ public int getMajorComponent()
+ {
+ return m_major;
+ }
+
+ public int getMinorComponent()
+ {
+ return m_minor;
+ }
+
+ public int getMicroComponent()
+ {
+ return m_micro;
+ }
+
+ public String getQualifierComponent()
+ {
+ return m_qualifier;
+ }
+
+ public boolean isInclusive()
+ {
+ return m_isInclusive;
+ }
+
+ public int compareTo(Object o)
+ {
+ if (!(o instanceof R4Version))
+ throw new ClassCastException();
+
+ if (equals(o))
+ return 0;
+
+ if (isGreaterThan((R4Version) o))
+ return 1;
+
+ return -1;
+ }
+
+ public boolean isGreaterThan(R4Version v)
+ {
+ if (v == null)
+ {
+ return false;
+ }
+
+ if (m_major > v.getMajorComponent())
+ {
+ return true;
+ }
+ if (m_major < v.getMajorComponent())
+ {
+ return false;
+ }
+ if (m_minor > v.getMinorComponent())
+ {
+ return true;
+ }
+ if (m_minor < v.getMinorComponent())
+ {
+ return false;
+ }
+ if (m_micro > v.getMicroComponent())
+ {
+ return true;
+ }
+ if (m_micro < v.getMicroComponent())
+ {
+ return false;
+ }
+ if (m_qualifier.compareTo(v.getQualifierComponent()) > 0)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public String toString()
+ {
+ if (m_qualifier.length() == 0)
+ {
+ return m_major + "." + m_minor + "." + m_micro;
+ }
+ return m_major + "." + m_minor + "." + m_micro + "." + m_qualifier;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/searchpolicy/R4Wire.java b/src/org/apache/osgi/framework/searchpolicy/R4Wire.java
new file mode 100755
index 0000000..52ab909
--- /dev/null
+++ b/src/org/apache/osgi/framework/searchpolicy/R4Wire.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.searchpolicy;
+
+import org.apache.osgi.moduleloader.Module;
+
+public class R4Wire
+{
+ public R4Package m_pkg = null;
+ public Module m_module = null;
+
+ public R4Wire(R4Package pkg, Module module)
+ {
+ m_pkg = pkg;
+ m_module = module;
+ }
+
+ public String toString()
+ {
+ return m_pkg.getId() + " -> " + m_module;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/BundleListenerWrapper.java b/src/org/apache/osgi/framework/util/BundleListenerWrapper.java
new file mode 100644
index 0000000..033bac6
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/BundleListenerWrapper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.osgi.framework.*;
+
+public class BundleListenerWrapper extends ListenerWrapper implements BundleListener
+{
+ public BundleListenerWrapper(Bundle bundle, BundleListener l)
+ {
+ super(bundle,
+ (l instanceof SynchronousBundleListener)
+ ? SynchronousBundleListener.class : BundleListener.class,
+ l);
+ }
+
+ public void bundleChanged(final BundleEvent event)
+ {
+ // A bundle listener is either synchronous or asynchronous.
+ // If the bundle listener is synchronous, then deliver the
+ // event to bundles with a state of STARTING, STOPPING, or
+ // ACTIVE. If the listener is asynchronous, then deliver the
+ // event only to bundles that are STARTING or ACTIVE.
+ if (((getListenerClass() == SynchronousBundleListener.class) &&
+ ((getBundle().getState() == Bundle.STARTING) ||
+ (getBundle().getState() == Bundle.STOPPING) ||
+ (getBundle().getState() == Bundle.ACTIVE)))
+ ||
+ ((getBundle().getState() == Bundle.STARTING) ||
+ (getBundle().getState() == Bundle.ACTIVE)))
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run()
+ {
+ ((BundleListener) getListener()).bundleChanged(event);
+ return null;
+ }
+ });
+ }
+ else
+ {
+ ((BundleListener) getListener()).bundleChanged(event);
+ }
+ }
+ }
+}
diff --git a/src/org/apache/osgi/framework/util/CaseInsensitiveMap.java b/src/org/apache/osgi/framework/util/CaseInsensitiveMap.java
new file mode 100644
index 0000000..6d805aa
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/CaseInsensitiveMap.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.util.*;
+
+/**
+ * Simple utility class that creates a case-insensitive map by
+ * extending <tt>TreeMap</tt> and to use a case-insensitive
+ * comparator. Any keys put into this map will be converted to
+ * a <tt>String</tt> using the <tt>toString()</tt> method,
+ * since it is intended to compare strings.
+**/
+public class CaseInsensitiveMap extends TreeMap
+{
+ public CaseInsensitiveMap()
+ {
+ super(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+ }
+
+ public CaseInsensitiveMap(Map map)
+ {
+ this();
+ putAll(map);
+ }
+
+ public Object put(Object key, Object value)
+ {
+ return super.put(key.toString(), value);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/DispatchQueue.java b/src/org/apache/osgi/framework/util/DispatchQueue.java
new file mode 100644
index 0000000..9d70a9c
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/DispatchQueue.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.util.*;
+
+import org.apache.osgi.framework.LogWrapper;
+
+/**
+ * This class implements an event dispatching queue to simplify delivering
+ * events to a list of event listener. To use this class, simply create an
+ * instance and use it to keep track of your event listeners, much like
+ * <tt>javax.swing.event.EventListenerList</tt>. To dispatch an event,
+ * simply create an instance of a <tt>Dispatcher</tt> and pass the instance
+ * to <tt>DispatchQueue.dispatch()</tt> method, for example:
+ * <pre>
+ * Dispatcher d = new Dispatcher() {
+ * public void dispatch(EventListener l, Object eventObj)
+ * {
+ * ((FooListener) l).fooXXX((FooEvent) eventObj);
+ * }
+ * };
+ * FooEvent event = new FooEvent(this);
+ * dispatchQueue.dispatch(d, FooListener.class, event);
+ * </pre>
+ * In the above code substitute a specific listener and event for the
+ * <tt>Foo</tt> listener and event. Since <tt>Dispatcher</tt>s are
+ * reusable, it is probably a good idea to create one for each type of
+ * event to be delivered and just reuse them everytime to avoid unnecessary
+ * memory allocation.
+ * <p>
+ * Currently, the <tt>DispatchQueue</tt> creates an internal thread with
+ * which all events are delivered; this means that events are never delivered
+ * using the caller's thread.
+**/
+public class DispatchQueue
+{
+ // Representation of an empty listener list.
+ private static final Object[] m_emptyList = new Object[0];
+
+ // The event listeners for a particular queue instance.
+ private Object[] m_listeners = m_emptyList;
+
+ // A single thread is used to deliver events for all dispatchers.
+ private static Thread m_thread = null;
+ private static String m_threadLock = "thread lock";
+ private static boolean m_stopping = false;
+ private static boolean m_stopped = false;
+
+ // List of dispatch requests.
+ private static final ArrayList m_requestList = new ArrayList();
+ // Cached dispatch requests to avoid memory allocation.
+ private static final ArrayList m_requestCache = new ArrayList();
+ // The logger for dispatch queue.
+ private static LogWrapper m_logger = null;
+
+ /**
+ * Constructs a dispatch queue and starts a dispather thread if
+ * necessary.
+ **/
+ public DispatchQueue(LogWrapper logger)
+ {
+ synchronized (m_threadLock)
+ {
+ // Start event dispatching thread if necessary.
+ if (m_thread == null)
+ {
+ m_logger = logger;
+ m_thread = new Thread(new Runnable() {
+ public void run()
+ {
+ DispatchQueue.run();
+ }
+ }, "FelixDispatchQueue");
+ m_thread.start();
+ }
+ }
+ }
+
+ /**
+ * Terminates the dispatching thread for a graceful shutdown
+ * of the dispatching queue; the caller will block until the
+ * dispatching thread has completed all pending dispatches.
+ * Since there is only one thread per all instances of
+ * <tt>DispatchQueue</tt>, this method should only be called
+ * prior to exiting the JVM.
+ **/
+ public static void shutdown()
+ {
+ synchronized (m_threadLock)
+ {
+ // Return if already stopped.
+ if (m_stopped)
+ {
+ return;
+ }
+
+ // Signal dispatch thread.
+ m_stopping = true;
+ synchronized (m_requestList)
+ {
+ m_requestList.notify();
+ }
+
+ // Wait for dispatch thread to stop.
+ while (!m_stopped)
+ {
+ try {
+ m_threadLock.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ }
+
+ public static LogWrapper getLogger()
+ {
+ return m_logger;
+ }
+
+ /**
+ * Returns a pointer to the array of event listeners. The array stores pairs
+ * of associated <tt>Class</tt> and <tt>EventListener</tt> objects; a pair
+ * corresponds to the arguments passed in to the <tt>addListener()</tt> method.
+ * Even numbered array elements are the class object and odd numbered elements
+ * are the corresponding event listener instance.
+ *
+ * @return guaranteed to return a non-null object array.
+ **/
+ public Object[] getListeners()
+ {
+ return m_listeners;
+ }
+
+ /**
+ * Returns the listener if it is already in the dispatch queue.
+ * @param clazz the class of the listener to find.
+ * @param l the listener instance to find.
+ * @return the listener instance or <tt>null</tt> if the listener was
+ * not found.
+ **/
+ public EventListener getListener(Class clazz, EventListener l)
+ {
+ // Verify listener.
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener is null");
+ }
+ else if (!clazz.isInstance(l))
+ {
+ throw new IllegalArgumentException(
+ "Listener not of type " + clazz.getName());
+ }
+
+ // Lock the object.
+ synchronized (this)
+ {
+ // Try to find the instance in our list.
+ for (int i = 0; i < m_listeners.length; i += 2)
+ {
+ if ((m_listeners[i] == clazz) &&
+ (m_listeners[i + 1].equals(l)))
+ {
+ return (EventListener) m_listeners[i + 1];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Adds a listener to the dispatch queue's listener list; the listener
+ * is then able to receive events.
+ *
+ * @param clazz the class object associated with the event listener type.
+ * @param l the instance of the event listener to add.
+ **/
+ public void addListener(Class clazz, EventListener l)
+ {
+ // Verify listener.
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener is null");
+ }
+ else if (!clazz.isInstance(l))
+ {
+ throw new IllegalArgumentException(
+ "Listener not of type " + clazz.getName());
+ }
+
+ // Lock the object.
+ synchronized (this)
+ {
+ // If we have no listeners, then just add the new listener.
+ if (m_listeners == m_emptyList)
+ {
+ m_listeners = new Object[] { clazz, l };
+ }
+ // Otherwise, we need to do some array copying.
+ // Notice, the old array is always valid, so if
+ // the dispatch thread is in the middle of a dispatch,
+ // then it has a reference to the old listener array
+ // and is not affected by the new value.
+ else
+ {
+ Object[] newList = new Object[m_listeners.length + 2];
+ System.arraycopy(m_listeners, 0, newList, 0, m_listeners.length);
+ newList[m_listeners.length] = clazz;
+ newList[m_listeners.length + 1] = l;
+ m_listeners = newList;
+ }
+ }
+ }
+
+ /**
+ * Removes a listener from the dispatch queue's listener list; the listener
+ * is no longer able to receive events.
+ *
+ * @param clazz the class object associated with the event listener type.
+ * @param l the instance of the event listener to remove.
+ **/
+ public void removeListener(Class clazz, EventListener l)
+ {
+ // Verify listener.
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener is null");
+ }
+ else if (!clazz.isInstance(l))
+ {
+ throw new IllegalArgumentException(
+ "Listener not of type " + clazz.getName());
+ }
+
+ // Lock the object.
+ synchronized (this)
+ {
+ // Try to find the instance in our list.
+ int idx = -1;
+ for (int i = 0; i < m_listeners.length; i += 2)
+ {
+ if ((m_listeners[i] == clazz) &&
+ (m_listeners[i + 1].equals(l)))
+ {
+ idx = i;
+ break;
+ }
+ }
+
+ // If we have the instance, then remove it.
+ if (idx >= 0)
+ {
+ // If this is the last listener, then point to empty list.
+ if ((m_listeners.length - 2) == 0)
+ {
+ m_listeners = m_emptyList;
+ }
+ // Otherwise, we need to do some array copying.
+ // Notice, the old array is always valid, so if
+ // the dispatch thread is in the middle of a dispatch,
+ // then it has a reference to the old listener array
+ // and is not affected by the new value.
+ else
+ {
+ Object[] newList = new Object[m_listeners.length - 2];
+ System.arraycopy(m_listeners, 0, newList, 0, idx);
+ if (idx < newList.length)
+ {
+ System.arraycopy(m_listeners, idx + 2, newList, idx,
+ newList.length - idx);
+ }
+ m_listeners = newList;
+ }
+ }
+ }
+ }
+
+ /**
+ * Dispatches an event to a set of event listeners using a specified
+ * dispatcher object.
+ *
+ * @param d the dispatcher used to actually dispatch the event; this
+ * varies according to the type of event listener.
+ * @param clazz the class associated with the target event listener type;
+ * only event listeners of this type will receive the event.
+ * @param eventObj the actual event object to dispatch.
+ **/
+ public void dispatch(Dispatcher d, Class clazz, EventObject eventObj)
+ {
+ dispatch(m_listeners, d, clazz, eventObj);
+ }
+
+ protected void dispatch(
+ Object[] listeners, Dispatcher d, Class clazz, EventObject eventObj)
+ {
+ // If dispatch thread is stopped, then ignore dispatch request.
+ if (m_stopped)
+ {
+ return;
+ }
+
+ // First get a dispatch request from the cache or
+ // create one if necessary.
+ DispatchRequest dr = null;
+ synchronized (m_requestCache)
+ {
+ if (m_requestCache.size() > 0)
+ dr = (DispatchRequest) m_requestCache.remove(0);
+ else
+ dr = new DispatchRequest();
+ }
+
+ // Initialize dispatch request.
+ dr.m_listeners = listeners;
+ dr.m_dispatcher = d;
+ dr.m_clazz = clazz;
+ dr.m_eventObj = eventObj;
+
+ // Lock the request list.
+ synchronized (m_requestList)
+ {
+ // Add our request to the list.
+ m_requestList.add(dr);
+ // Notify the dispatch thread that there is
+ // work to do.
+ m_requestList.notify();
+ }
+ }
+
+ private static void run()
+ {
+ DispatchRequest dr = null;
+ while (true)
+ {
+ // Lock the request list so we can try to get a
+ // dispatch request from it.
+ synchronized (m_requestList)
+ {
+ // Wait while there are no requests to dispatch. If the
+ // dispatcher thread is supposed to stop, then let the
+ // dispatcher thread exit the loop and stop.
+ while ((m_requestList.size() == 0) && !m_stopping)
+ {
+ // Wait until some signals us for work.
+ try {
+ m_requestList.wait();
+ } catch (InterruptedException ex) {
+ m_logger.log(LogWrapper.LOG_ERROR, "DispatchQueue: Thread interrupted.", ex);
+ }
+ }
+
+ // If there are no events to dispatch and shutdown
+ // has been called then exit, otherwise dispatch event.
+ if ((m_requestList.size() == 0) && (m_stopping))
+ {
+ synchronized (m_threadLock)
+ {
+ m_stopped = true;
+ m_threadLock.notifyAll();
+ }
+ return;
+ }
+
+ // Get the dispatch request.
+ dr = (DispatchRequest) m_requestList.remove(0);
+ }
+
+ // Deliver event outside of synchronized block
+ // so that we don't block other requests from being
+ // queued during event processing.
+
+ // Try to dispatch to the listeners.
+ if (dr.m_listeners.length > 0)
+ {
+ // Notify appropriate listeners.
+ for (int i = dr.m_listeners.length - 2; i >= 0; i -= 2)
+ {
+ if (dr.m_listeners[i] == dr.m_clazz)
+ {
+ try {
+ dr.m_dispatcher.dispatch(
+ (EventListener) dr.m_listeners[i + 1], dr.m_eventObj);
+ } catch (Throwable th) {
+ m_logger.log(LogWrapper.LOG_ERROR, "DispatchQueue: Error during dispatch.", th);
+ }
+ }
+ }
+ }
+
+ // Put dispatch request in cache.
+ synchronized (m_requestCache)
+ {
+ m_requestCache.add(dr);
+ }
+ }
+ }
+
+ private static class DispatchRequest
+ {
+ public Object[] m_listeners = null;
+ public Dispatcher m_dispatcher = null;
+ public Class m_clazz = null;
+ public EventObject m_eventObj = null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/Dispatcher.java b/src/org/apache/osgi/framework/util/Dispatcher.java
new file mode 100644
index 0000000..a3c0192
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/Dispatcher.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.util.EventListener;
+import java.util.EventObject;
+
+/**
+ * This interface is used by <tt>DispatchQueue</tt> to dispatch events.
+ * Generally speaking, each type of event to dispatch will have an instance
+ * of a <tt>Dispatcher</tt> so that the dispatch queue can dispatch to
+ * the appropriate listener method for the specific listener type,
+ * for example:
+ * <pre>
+ * Dispatcher d = new Dispatcher() {
+ * public void dispatch(EventListener l, EventObject eventObj)
+ * {
+ * ((FooListener) l).fooXXX((FooEvent) eventObj);
+ * }
+ * };
+ * FooEvent event = new FooEvent(this);
+ * dispatchQueue.dispatch(d, FooListener.class, event);
+ * </pre>
+ * In the above code substitute a specific listener and event for the
+ * <tt>Foo</tt> listener and event. <tt>Dispatcher</tt>s can be reused, so
+ * it is a good idea to cache them to avoid unnecessary memory allocations.
+**/
+public interface Dispatcher
+{
+ /**
+ * Dispatch an event to a specified event listener.
+ *
+ * @param l the event listener to receive the event.
+ * @param eventObj the event to dispatch.
+ **/
+ public void dispatch(EventListener l, EventObject eventObj);
+}
diff --git a/src/org/apache/osgi/framework/util/FelixConstants.java b/src/org/apache/osgi/framework/util/FelixConstants.java
new file mode 100644
index 0000000..a98e4e3
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/FelixConstants.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+public interface FelixConstants extends org.osgi.framework.Constants
+{
+ // Framework constants and values.
+ public static final String FRAMEWORK_VERSION_VALUE = "4.0";
+ public static final String FRAMEWORK_VENDOR_VALUE = "Apache";
+
+ // Framework constants and values.
+ public static final String FELIX_VERSION_PROPERTY = "felix.version";
+ public static final String FELIX_VERSION_VALUE = "2.0.0.alpha8";
+
+ // Miscellaneous manifest constants.
+ public static final String DIRECTIVE_SEPARATOR = ":=";
+ public static final String ATTRIBUTE_SEPARATOR = "=";
+ public static final String CLASS_PATH_SEPARATOR = ",";
+ public static final String CLASS_PATH_DOT = ".";
+ public static final String PACKAGE_SEPARATOR = ";";
+ public static final String VERSION_SEGMENT_SEPARATOR = ".";
+ public static final int VERSION_SEGMENT_COUNT = 3;
+
+ // Miscellaneous OSGi constants.
+ public static final String BUNDLE_URL_PROTOCOL = "bundle";
+
+ // Miscellaneous framework configuration property names.
+ public static final String AUTO_INSTALL_PROP = "felix.auto.install";
+ public static final String AUTO_START_PROP = "felix.auto.start";
+ public static final String EMBEDDED_EXECUTION_PROP = "felix.embedded.execution";
+ public static final String STRICT_OSGI_PROP = "felix.strict.osgi";
+ public static final String CACHE_CLASS_PROP = "felix.cache.class";
+ public static final String FRAMEWORK_STARTLEVEL_PROP
+ = "felix.startlevel.framework";
+ public static final String BUNDLE_STARTLEVEL_PROP
+ = "felix.startlevel.bundle";
+
+ // Start level-related constants.
+ public static final int FRAMEWORK_INACTIVE_STARTLEVEL = 0;
+ public static final int FRAMEWORK_DEFAULT_STARTLEVEL = 1;
+ public static final int SYSTEMBUNDLE_DEFAULT_STARTLEVEL = 0;
+ public static final int BUNDLE_DEFAULT_STARTLEVEL = 1;
+
+ // Miscellaneous properties values.
+ public static final String FAKE_URL_PROTOCOL_VALUE = "location:";
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/FelixDispatchQueue.java b/src/org/apache/osgi/framework/util/FelixDispatchQueue.java
new file mode 100644
index 0000000..bed03a4
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/FelixDispatchQueue.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.util.EventListener;
+import java.util.EventObject;
+
+import org.apache.osgi.framework.LogWrapper;
+import org.osgi.framework.*;
+
+/**
+ * This is a subclass of <tt>DispatchQueue</tt> that specifically adds support
+ * for <tt>SynchronousBundleListener</tt>s; the OSGi specification
+ * says that synchronous bundle listeners should receive their events
+ * immediately, i.e., they should not be delivered on a separate thread
+ * like is the case with the <tt>DispatchQueue</tt>. To achieve this
+ * functionality, this class overrides the dispatch method to automatically
+ * fire any bundle events to synchronous bundle listeners using the
+ * calling thread. In order to ensure that synchronous bundle listeners
+ * do not receive an event twice, it wraps the passed in <tt>Dipatcher</tt>
+ * instance so that it filters synchronous bundle listeners.
+**/
+public class FelixDispatchQueue extends DispatchQueue
+{
+ public FelixDispatchQueue(LogWrapper logger)
+ {
+ super(logger);
+ }
+
+ /**
+ * Dispatches an event to a set of event listeners using a specified
+ * dispatcher object. This overrides the definition of the super class
+ * to deliver events to <tt>ServiceListener</tt>s and
+ * <tt>SynchronousBundleListener</tt>s using
+ * the calling thread instead of the event dispatching thread. All
+ * other events are still delivered asynchronously.
+ *
+ * @param dispatcher the dispatcher used to actually dispatch the event; this
+ * varies according to the type of event listener.
+ * @param clazz the class associated with the target event listener type;
+ * only event listeners of this type will receive the event.
+ * @param eventObj the actual event object to dispatch.
+ **/
+ public void dispatch(Dispatcher dispatcher, Class clazz, EventObject eventObj)
+ {
+ Object[] listeners = getListeners();
+
+ // If this is an event for service listeners, then dispatch it
+ // immediately since service events are never asynchronous.
+ if ((clazz == ServiceListener.class) && (listeners.length > 0))
+ {
+ // Notify appropriate listeners.
+ for (int i = listeners.length - 2; i >= 0; i -= 2)
+ {
+ // If the original listener is a synchronous bundle listener
+ // or a service listener, then dispatch event immediately
+ // per the specification.
+ ListenerWrapper lw = (ListenerWrapper) listeners[i + 1];
+ if (lw.getListenerClass() == ServiceListener.class)
+ {
+ try {
+ dispatcher.dispatch(
+ (EventListener) lw, eventObj);
+ } catch (Throwable th) {
+ getLogger().log(
+ LogWrapper.LOG_ERROR,
+ "FelixDispatchQueue: Error during dispatch.", th);
+ }
+ }
+ }
+ }
+ // Dispatch bundle events to synchronous bundle listeners immediately,
+ // but deliver to standard bundle listeners asynchronously.
+ else if ((clazz == BundleListener.class) && (listeners.length > 0))
+ {
+ // Notify appropriate listeners.
+ for (int i = listeners.length - 2; i >= 0; i -= 2)
+ {
+ // If the original listener is a synchronous bundle listener,
+ // then dispatch event immediately per the specification.
+ ListenerWrapper lw = (ListenerWrapper) listeners[i + 1];
+ if (lw.getListenerClass() == SynchronousBundleListener.class)
+ {
+ try {
+ dispatcher.dispatch(
+ (EventListener) lw, eventObj);
+ } catch (Throwable th) {
+ getLogger().log(
+ LogWrapper.LOG_ERROR,
+ "FelixDispatchQueue: Error during dispatch.", th);
+ }
+ }
+ }
+
+ // Wrap the dispatcher so that it ignores synchronous
+ // bundle listeners since they have already been dispatched.
+ IgnoreSynchronousDispatcher ignoreDispatcher = new IgnoreSynchronousDispatcher();
+ ignoreDispatcher.setDispatcher(dispatcher);
+
+ // Dispatch the bundle listener asynchronously.
+ dispatch(listeners, ignoreDispatcher, clazz, eventObj);
+ }
+ // All other events are dispatched asynchronously.
+ else
+ {
+ dispatch(listeners, dispatcher, clazz, eventObj);
+ }
+ }
+
+ private static class IgnoreSynchronousDispatcher implements Dispatcher
+ {
+ private Dispatcher m_dispatcher = null;
+
+ public void setDispatcher(Dispatcher dispatcher)
+ {
+ m_dispatcher = dispatcher;
+ }
+
+ public void dispatch(EventListener l, EventObject eventObj)
+ {
+ if (l instanceof ListenerWrapper)
+ {
+ ListenerWrapper lw = (ListenerWrapper) l;
+ // Do not dispatch events to synchronous listeners,
+ // since they are dispatched immediately above.
+ if (!(lw.getListenerClass() == SynchronousBundleListener.class))
+ {
+ m_dispatcher.dispatch(l, eventObj);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/FrameworkListenerWrapper.java b/src/org/apache/osgi/framework/util/FrameworkListenerWrapper.java
new file mode 100644
index 0000000..630c78c
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/FrameworkListenerWrapper.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.osgi.framework.*;
+
+public class FrameworkListenerWrapper extends ListenerWrapper implements FrameworkListener
+{
+ public FrameworkListenerWrapper(Bundle bundle, FrameworkListener l)
+ {
+ super(bundle, FrameworkListener.class, l);
+ }
+
+ public void frameworkEvent(final FrameworkEvent event)
+ {
+ // The spec says only active bundles receive asynchronous events,
+ // but we will include starting bundles too otherwise
+ // it is impossible to see everything.
+ if ((getBundle().getState() == Bundle.STARTING) ||
+ (getBundle().getState() == Bundle.ACTIVE))
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run()
+ {
+ ((FrameworkListener) getListener()).frameworkEvent(event);
+ return null;
+ }
+ });
+ }
+ else
+ {
+ ((FrameworkListener) getListener()).frameworkEvent(event);
+ }
+ }
+ }
+}
diff --git a/src/org/apache/osgi/framework/util/IteratorToEnumeration.java b/src/org/apache/osgi/framework/util/IteratorToEnumeration.java
new file mode 100644
index 0000000..c457827
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/IteratorToEnumeration.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+
+public class IteratorToEnumeration implements Enumeration
+{
+ private Iterator m_iter = null;
+
+ public IteratorToEnumeration(Iterator iter)
+ {
+ m_iter = iter;
+ }
+
+ public boolean hasMoreElements()
+ {
+ if (m_iter == null)
+ return false;
+ return m_iter.hasNext();
+ }
+
+ public Object nextElement()
+ {
+ if (m_iter == null)
+ return null;
+ return m_iter.next();
+ }
+}
diff --git a/src/org/apache/osgi/framework/util/LibraryInfo.java b/src/org/apache/osgi/framework/util/LibraryInfo.java
new file mode 100644
index 0000000..f444ef9
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/LibraryInfo.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.util.*;
+
+import org.osgi.framework.Constants;
+
+public class LibraryInfo
+{
+ private String m_name = null;
+ private String[] m_osnames = null;
+ private String[] m_osversions = null;
+ private String[] m_processors = null;
+ private String[] m_languages = null;
+
+ public LibraryInfo(String name, String[] osnames, String[] osversions,
+ String[] processors, String[] languages)
+ {
+ m_name = name;
+ m_osnames = osnames;
+ m_osversions = osversions;
+ m_processors = processors;
+ m_languages = languages;
+ }
+
+ public LibraryInfo(LibraryInfo library)
+ {
+ m_name = library.m_name;
+ m_osnames = library.m_osnames;
+ m_osversions = library.m_osversions;
+ m_processors = library.m_processors;
+ m_languages = library.m_languages;
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public String[] getOSNames()
+ {
+ return m_osnames;
+ }
+
+ public String[] getOSVersions()
+ {
+ return m_osversions;
+ }
+
+ public String[] getProcessors()
+ {
+ return m_processors;
+ }
+
+ public static LibraryInfo[] parse(String s)
+ {
+ try
+ {
+ if ((s == null) || (s.length() == 0))
+ {
+ return null;
+ }
+
+ // The tokens are separated by semicolons and may include
+ // any number of libraries (whose name starts with a "/")
+ // along with one set of associated properties.
+ StringTokenizer st = new StringTokenizer(s, ";");
+ String[] libs = new String[st.countTokens()];
+ List osNameList = new ArrayList();
+ List osVersionList = new ArrayList();
+ List processorList = new ArrayList();
+ List languageList = new ArrayList();
+ int libCount = 0;
+ while (st.hasMoreTokens())
+ {
+ String token = st.nextToken().trim();
+ if (token.indexOf('=') < 0)
+ {
+ // Remove the slash, if necessary.
+ libs[libCount] = (token.charAt(0) == '/')
+ ? token.substring(1)
+ : token;
+ libCount++;
+ }
+ else
+ {
+ // Check for valid native library properties; defined as
+ // a property name, an equal sign, and a value.
+ StringTokenizer stProp = new StringTokenizer(token, "=");
+ if (stProp.countTokens() != 2)
+ {
+ throw new IllegalArgumentException(
+ "Bundle manifest native library entry malformed: " + token);
+ }
+ String property = stProp.nextToken().trim().toLowerCase();
+ String value = stProp.nextToken().trim();
+
+ // Values may be quoted, so remove quotes if present.
+ if (value.charAt(0) == '"')
+ {
+ // This should always be true, otherwise the
+ // value wouldn't be properly quoted, but we
+ // will check for safety.
+ if (value.charAt(value.length() - 1) == '"')
+ {
+ value = value.substring(1, value.length() - 1);
+ }
+ else
+ {
+ value = value.substring(1);
+ }
+ }
+ // Add the value to its corresponding property list.
+ if (property.equals(Constants.BUNDLE_NATIVECODE_OSNAME))
+ {
+ osNameList.add(value);
+ }
+ else if (property.equals(Constants.BUNDLE_NATIVECODE_OSVERSION))
+ {
+ osVersionList.add(value);
+ }
+ else if (property.equals(Constants.BUNDLE_NATIVECODE_PROCESSOR))
+ {
+ processorList.add(value);
+ }
+ else if (property.equals(Constants.BUNDLE_NATIVECODE_LANGUAGE))
+ {
+ languageList.add(value);
+ }
+ }
+ }
+
+ if (libCount == 0)
+ {
+ return null;
+ }
+
+ LibraryInfo[] libraries = new LibraryInfo[libCount];
+ for (int i = 0; i < libCount; i++)
+ {
+ libraries[i] =
+ new LibraryInfo(
+ libs[i],
+ (String[]) osNameList.toArray(new String[0]),
+ (String[]) osVersionList.toArray(new String[0]),
+ (String[]) processorList.toArray(new String[0]),
+ (String[]) languageList.toArray(new String[0]));
+ }
+
+ return libraries;
+
+ }
+ catch (RuntimeException ex)
+ {
+ ex.printStackTrace();
+ throw ex;
+ }
+ }
+
+ public String toString()
+ {
+ return m_name;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ListenerWrapper.java b/src/org/apache/osgi/framework/util/ListenerWrapper.java
new file mode 100644
index 0000000..0265e8d
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ListenerWrapper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.util.EventListener;
+
+import org.osgi.framework.Bundle;
+
+public class ListenerWrapper
+{
+ // The bundle associated with the listener.
+ private Bundle m_bundle = null;
+ // Listener class.
+ private Class m_class = null;
+ // The original listener.
+ private EventListener m_listener = null;
+
+ public ListenerWrapper(Bundle bundle, Class clazz, EventListener l)
+ {
+ m_bundle = bundle;
+ m_class = clazz;
+ m_listener = l;
+ }
+
+ public Bundle getBundle()
+ {
+ return m_bundle;
+ }
+
+ protected Class getListenerClass()
+ {
+ return m_class;
+ }
+
+ protected EventListener getListener()
+ {
+ return m_listener;
+ }
+
+ public boolean equals(Object obj)
+ {
+ if (obj instanceof ListenerWrapper)
+ {
+ return (((ListenerWrapper) obj).m_listener == m_listener);
+ }
+ else if (obj instanceof EventListener)
+ {
+ return (obj == m_listener);
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/MapToDictionary.java b/src/org/apache/osgi/framework/util/MapToDictionary.java
new file mode 100644
index 0000000..0bdd510
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/MapToDictionary.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.util.*;
+
+
+/**
+ * This is a simple class that implements a <tt>Dictionary</tt>
+ * from a <tt>Map</tt>. The resulting dictionary is immutatable.
+**/
+public class MapToDictionary extends Dictionary
+{
+ /**
+ * Map source.
+ **/
+ private Map m_map = null;
+
+ public MapToDictionary(Map map)
+ {
+ m_map = map;
+ }
+
+ public Enumeration elements()
+ {
+ if (m_map == null)
+ {
+ return null;
+ }
+ return new IteratorToEnumeration(m_map.values().iterator());
+ }
+
+ public Object get(Object key)
+ {
+ if (m_map == null)
+ {
+ return null;
+ }
+ return m_map.get(key);
+ }
+
+ public boolean isEmpty()
+ {
+ if (m_map == null)
+ {
+ return true;
+ }
+ return m_map.isEmpty();
+ }
+
+ public Enumeration keys()
+ {
+ if (m_map == null)
+ {
+ return null;
+ }
+ return new IteratorToEnumeration(m_map.keySet().iterator());
+ }
+
+ public Object put(Object key, Object value)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object remove(Object key)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size()
+ {
+ if (m_map == null)
+ {
+ return 0;
+ }
+ return m_map.size();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/MutablePropertyResolver.java b/src/org/apache/osgi/framework/util/MutablePropertyResolver.java
new file mode 100644
index 0000000..f5aaf84
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/MutablePropertyResolver.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+public interface MutablePropertyResolver extends PropertyResolver
+{
+ public String put(String key, String value);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/MutablePropertyResolverImpl.java b/src/org/apache/osgi/framework/util/MutablePropertyResolverImpl.java
new file mode 100644
index 0000000..3be8c37
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/MutablePropertyResolverImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.util.Map;
+
+public class MutablePropertyResolverImpl implements MutablePropertyResolver
+{
+ private Map m_props = null;
+
+ public MutablePropertyResolverImpl(Map props)
+ {
+ m_props = props;
+ }
+
+ public synchronized String put(String key, String value)
+ {
+ return (String) m_props.put(key, value);
+ }
+
+ public synchronized String get(String key)
+ {
+ return (String) m_props.get(key);
+ }
+
+ public synchronized String[] getKeys()
+ {
+ return (String[]) m_props.keySet().toArray(new String[0]);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ObjectInputStreamX.java b/src/org/apache/osgi/framework/util/ObjectInputStreamX.java
new file mode 100644
index 0000000..e7b99be
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ObjectInputStreamX.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.io.*;
+
+/**
+ * The ObjectInputStreamX class is a simple extension to the ObjectInputStream
+ * class. The purpose of ObjectInputStreamX is to allow objects to be deserialized
+ * when their classes are not in the CLASSPATH (e.g., in a JAR file).
+ */
+public class ObjectInputStreamX extends ObjectInputStream
+{
+ private ClassLoader m_loader = null;
+
+ /**
+ * Construct an ObjectInputStreamX for the specified InputStream and the specified
+ * ClassLoader.
+ * @param in the input stream to read.
+ * @param loader the class loader used to resolve classes.
+ */
+ public ObjectInputStreamX(InputStream in, ClassLoader loader)
+ throws IOException, StreamCorruptedException
+ {
+ super(in);
+ this.m_loader = loader;
+ }
+
+ /**
+ * By overloading this method, the ObjectInputStreamX can resolve a class
+ * from the class loader that was passed into it at construction time.
+ */
+ protected Class resolveClass(ObjectStreamClass v)
+ throws IOException, ClassNotFoundException
+ {
+ Class clazz = m_loader.loadClass(v.getName());
+ return clazz;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/PropertyResolver.java b/src/org/apache/osgi/framework/util/PropertyResolver.java
new file mode 100644
index 0000000..cd8f994
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/PropertyResolver.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+public interface PropertyResolver
+{
+ public String get(String key);
+ public String[] getKeys();
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ServiceListenerWrapper.java b/src/org/apache/osgi/framework/util/ServiceListenerWrapper.java
new file mode 100644
index 0000000..0eba299
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ServiceListenerWrapper.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.security.*;
+
+import org.osgi.framework.*;
+
+public class ServiceListenerWrapper extends ListenerWrapper implements ServiceListener
+{
+ // LDAP query filter.
+ private Filter m_filter = null;
+ // Remember the security context.
+ private AccessControlContext m_acc = null;
+
+ public ServiceListenerWrapper(Bundle bundle, ServiceListener l, Filter filter)
+ {
+ super(bundle, ServiceListener.class, l);
+ m_filter = filter;
+
+ // Remember security context for filtering
+ // events based on security.
+ if (System.getSecurityManager() != null)
+ {
+ m_acc = AccessController.getContext();
+ }
+ }
+
+ public void setFilter(Filter filter)
+ {
+ m_filter = filter;
+ }
+
+ public void serviceChanged(final ServiceEvent event)
+ {
+ // Service events should be delivered to STARTING,
+ // STOPPING, and ACTIVE bundles.
+ if ((getBundle().getState() != Bundle.STARTING) &&
+ (getBundle().getState() != Bundle.STOPPING) &&
+ (getBundle().getState() != Bundle.ACTIVE))
+ {
+ return;
+ }
+
+ // Check that the bundle has permission to get at least
+ // one of the service interfaces; the objectClass property
+ // of the service stores its service interfaces.
+ ServiceReference ref = event.getServiceReference();
+ String[] objectClass = (String[]) ref.getProperty(Constants.OBJECTCLASS);
+
+ // On the safe side, if there is no objectClass property
+ // then ignore event altogether.
+ if (objectClass != null)
+ {
+ boolean hasPermission = false;
+ if (m_acc != null)
+ {
+ for (int i = 0;
+ !hasPermission && (i < objectClass.length);
+ i++)
+ {
+ try {
+ ServicePermission perm =
+ new ServicePermission(
+ objectClass[i], ServicePermission.GET);
+ m_acc.checkPermission(perm);
+ hasPermission = true;
+ } catch (Exception ex) {
+ }
+ }
+ }
+ else
+ {
+ hasPermission = true;
+ }
+
+ if (hasPermission)
+ {
+ // Dispatch according to the filter.
+ if ((m_filter == null) || m_filter.match(event.getServiceReference()))
+ {
+ if (System.getSecurityManager() != null)
+ {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run()
+ {
+ ((ServiceListener) getListener()).serviceChanged(event);
+ return null;
+ }
+ });
+ }
+ else
+ {
+ ((ServiceListener) getListener()).serviceChanged(event);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/Util.java b/src/org/apache/osgi/framework/util/Util.java
new file mode 100644
index 0000000..89591ab
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/Util.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Util
+{
+ /**
+ * Parses delimited string and returns an array containing the tokens. This
+ * parser obeys quotes, so the delimiter character will be ignored if it is
+ * inside of a quote. This method assumes that the quote character is not
+ * included in the set of delimiter characters.
+ * @param value the delimited string to parse.
+ * @param delim the characters delimiting the tokens.
+ * @return an array of string tokens or null if there were no tokens.
+ **/
+ public static String[] parseDelimitedString(String value, String delim)
+ {
+ if (value == null)
+ {
+ value = "";
+ }
+
+ List list = new ArrayList();
+
+ int CHAR = 1;
+ int DELIMITER = 2;
+ int STARTQUOTE = 4;
+ int ENDQUOTE = 8;
+
+ StringBuffer sb = new StringBuffer();
+
+ int expecting = (CHAR | DELIMITER | STARTQUOTE);
+
+ for (int i = 0; i < value.length(); i++)
+ {
+ char c = value.charAt(i);
+
+ boolean isDelimiter = (delim.indexOf(c) >= 0);
+ boolean isQuote = (c == '"');
+
+ if (isDelimiter && ((expecting & DELIMITER) > 0))
+ {
+ list.add(sb.toString().trim());
+ sb.delete(0, sb.length());
+ expecting = (CHAR | DELIMITER | STARTQUOTE);
+ }
+ else if (isQuote && ((expecting & STARTQUOTE) > 0))
+ {
+ sb.append(c);
+ expecting = CHAR | ENDQUOTE;
+ }
+ else if (isQuote && ((expecting & ENDQUOTE) > 0))
+ {
+ sb.append(c);
+ expecting = (CHAR | STARTQUOTE | DELIMITER);
+ }
+ else if ((expecting & CHAR) > 0)
+ {
+ sb.append(c);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid delimited string: " + value);
+ }
+ }
+
+ if (sb.length() > 0)
+ {
+ list.add(sb.toString().trim());
+ }
+
+ return (String[]) list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * Parses native code manifest headers.
+ * @param libStrs an array of native library manifest header
+ * strings from the bundle manifest.
+ * @return an array of <tt>LibraryInfo</tt> objects for the
+ * passed in strings.
+ **/
+ public static LibraryInfo[] parseLibraryStrings(String[] libStrs)
+ throws IllegalArgumentException
+ {
+ if (libStrs == null)
+ {
+ return null;
+ }
+
+ List libList = new ArrayList();
+
+ for (int i = 0; i < libStrs.length; i++)
+ {
+ LibraryInfo[] libs = LibraryInfo.parse(libStrs[i]);
+ for (int libIdx = 0;
+ (libs != null) && (libIdx < libs.length);
+ libIdx++)
+ {
+ libList.add(libs[libIdx]);
+ }
+ }
+
+ if (libList.size() == 0)
+ {
+ return null;
+ }
+
+ return (LibraryInfo[]) libList.toArray(new LibraryInfo[libList.size()]);
+ }
+
+ private static final byte encTab[] = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
+ 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52,
+ 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64,
+ 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70,
+ 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31,
+ 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f };
+
+ private static final byte decTab[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1,
+ -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1,
+ -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, -1, -1, -1, -1, -1 };
+
+ public static String base64Encode(String s) throws IOException
+ {
+ return encode(s.getBytes(), 0);
+ }
+
+ /**
+ * Encode a raw byte array to a Base64 String.
+ *
+ * @param in Byte array to encode.
+ * @param len Length of Base64 lines. 0 means no line breaks.
+ **/
+ public static String encode(byte[] in, int len) throws IOException
+ {
+ ByteArrayOutputStream baos = null;
+ ByteArrayInputStream bais = null;
+ try
+ {
+ baos = new ByteArrayOutputStream();
+ bais = new ByteArrayInputStream(in);
+ encode(bais, baos, len);
+ // ASCII byte array to String
+ return (new String(baos.toByteArray()));
+ }
+ finally
+ {
+ if (baos != null)
+ {
+ baos.close();
+ }
+ if (bais != null)
+ {
+ bais.close();
+ }
+ }
+ }
+
+ public static void encode(InputStream in, OutputStream out, int len)
+ throws IOException
+ {
+
+ // Check that length is a multiple of 4 bytes
+ if (len % 4 != 0)
+ {
+ throw new IllegalArgumentException("Length must be a multiple of 4");
+ }
+
+ // Read input stream until end of file
+ int bits = 0;
+ int nbits = 0;
+ int nbytes = 0;
+ int b;
+
+ while ((b = in.read()) != -1)
+ {
+ bits = (bits << 8) | b;
+ nbits += 8;
+ while (nbits >= 6)
+ {
+ nbits -= 6;
+ out.write(encTab[0x3f & (bits >> nbits)]);
+ nbytes++;
+ // New line
+ if (len != 0 && nbytes >= len)
+ {
+ out.write(0x0d);
+ out.write(0x0a);
+ nbytes -= len;
+ }
+ }
+ }
+
+ switch (nbits)
+ {
+ case 2:
+ out.write(encTab[0x3f & (bits << 4)]);
+ out.write(0x3d); // 0x3d = '='
+ out.write(0x3d);
+ break;
+ case 4:
+ out.write(encTab[0x3f & (bits << 2)]);
+ out.write(0x3d);
+ break;
+ }
+
+ if (len != 0)
+ {
+ if (nbytes != 0)
+ {
+ out.write(0x0d);
+ out.write(0x0a);
+ }
+ out.write(0x0d);
+ out.write(0x0a);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/AttributeNotFoundException.java b/src/org/apache/osgi/framework/util/ldap/AttributeNotFoundException.java
new file mode 100644
index 0000000..69df531
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/AttributeNotFoundException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util.ldap;
+
+public class AttributeNotFoundException extends EvaluationException
+{
+ public AttributeNotFoundException(String msg)
+ {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/Driver.java b/src/org/apache/osgi/framework/util/ldap/Driver.java
new file mode 100644
index 0000000..f341255
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Driver.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util.ldap;
+
+import java.io.*;
+import java.util.*;
+
+public class Driver {
+
+ public static void main(String[] argv)
+ {
+ Mapper mapper = new DriverMapper();
+
+ if(argv== null || argv.length == 0) {
+ System.err.println("usage: Driver <ldap spec file>");
+ return;
+ }
+ LdapLexer lexer = new LdapLexer();
+ FileReader fr = null;
+ char[] line = null;
+ Evaluator engine = new Evaluator();
+
+ Parser parser = new Parser();
+// parser.setDebug(System.out);
+
+ try {
+ File spec = new File(argv[0]);
+ fr = new FileReader(spec);
+
+ // The basic operation of the driver is:
+ // 1. read a line from the file
+ // 2. parse that line
+ // 3. print the resulting program
+ // 4. repeat 1 until eof
+
+ for(;;) {
+ line = getLine(fr);
+ if(line == null) break;
+ System.out.println("Driver: filter: "+new String(line));
+ CharArrayReader car = new CharArrayReader(line);
+ lexer.setReader(car);
+ parser.reset(lexer);
+ boolean status = false;
+ try {
+ status = parser.start();
+ if(!status) {
+ System.err.println("parse failed");
+ printErrorLocation(line,lexer.charno());
+ }
+ } catch (ParseException pe) {
+ System.err.println(pe.toString());
+ printErrorLocation(line,lexer.charno());
+ }
+ if(status) {
+ try {
+ engine.reset(parser.getProgram());
+// System.out.println("Driver: program: "+engine.toString());
+ System.out.println("Driver: program: "+engine.toStringInfix());
+ System.out.println("Eval = " + engine.evaluate(mapper));
+ } catch (EvaluationException ee) {
+ System.err.print("Driver: ");
+ printEvaluationStack(engine.getOperands());
+ System.err.println(ee.toString());
+ }
+ }
+ }
+ } catch (Exception e) {
+ System.err.println(e.toString());
+ printErrorLocation(line,lexer.charno());
+ e.printStackTrace();
+ }
+ }
+
+ // Get a line of input at a time and return a char[] buffer
+ // containing the line
+
+ static char[] getLine(Reader reader) throws IOException
+ {
+ StringBuffer buf = new StringBuffer();
+ for(;;) {
+ int c = reader.read();
+ if(c == '\r') continue;
+ if(c < 0) {
+ if(buf.length() == 0) return null; // no more lines
+ break;
+ }
+ if(c == '\n') break;
+ buf.append((char)c);
+ }
+
+ char[] cbuf = new char[buf.length()];
+ buf.getChars(0,buf.length(),cbuf,0);
+ return cbuf;
+ }
+
+
+ static void printErrorLocation(char[] line, int charno)
+ {
+ System.err.print("|");
+ if(line != null) System.err.print(new String(line));
+ System.err.println("|");
+ for(int i=0;i<charno;i++) System.err.print(" ");
+ System.err.println("^");
+ }
+
+ // Report the final contents of the evaluation stack
+ static void printEvaluationStack(Stack stack)
+ {
+ System.err.print("Stack:");
+ // recast operands as Vector to make interior access easier
+ Vector operands = stack;
+ int len = operands.size();
+ for(int i=0;i<len;i++) System.err.print(" "+operands.elementAt(i));
+ System.err.println();
+ }
+
+}
+
+class DriverMapper implements Mapper {
+
+ Hashtable hash = new Hashtable();
+
+ public DriverMapper()
+ {
+ hash.put("cn","Babs Jensen");
+ hash.put("objectClass","Person");
+ hash.put("sn","Jensen");
+ hash.put("o","university of michigan");
+ hash.put("foo","bar");
+ }
+
+ public Object lookup(String key)
+ {
+ return hash.get(key);
+ }
+}
diff --git a/src/org/apache/osgi/framework/util/ldap/EvaluationException.java b/src/org/apache/osgi/framework/util/ldap/EvaluationException.java
new file mode 100644
index 0000000..c5e1fc9
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/EvaluationException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util.ldap;
+
+public class EvaluationException extends Exception
+{
+ private Class m_unsupportedType = null;
+
+ public EvaluationException(String msg)
+ {
+ super(msg);
+ }
+
+ public EvaluationException(String msg, Class clazz)
+ {
+ super(msg);
+ m_unsupportedType = clazz;
+ }
+
+ public boolean isUnsupportedType()
+ {
+ return (m_unsupportedType != null);
+ }
+
+ public Class getUnsupportedType()
+ {
+ return m_unsupportedType;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/Evaluator.java b/src/org/apache/osgi/framework/util/ldap/Evaluator.java
new file mode 100644
index 0000000..6b0ef58
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Evaluator.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util.ldap;
+
+import java.util.Stack;
+import java.util.Vector;
+
+public class Evaluator {
+
+ Object[] program = null;
+ Stack operands = new Stack();
+ Mapper mapper = null;
+
+ public Evaluator()
+ {
+ reset();
+ }
+
+ public Evaluator(Object[] prog)
+ {
+ reset(prog);
+ }
+
+ public void reset()
+ {
+ program = null;
+ mapper = null;
+ operands.clear();
+ }
+
+ public void reset(Object[] prog)
+ {
+ reset();
+ setProgram(prog);
+ }
+
+ public void setProgram(Object[] prog)
+ {
+ program = prog;
+ }
+
+ public void setMapper(Mapper mapper)
+ {
+ this.mapper = mapper;
+ }
+
+ public Stack getOperands()
+ {
+ return operands;
+ }
+
+ public boolean evaluate(Mapper mapper) throws EvaluationException
+ {
+ try
+ {
+ // The following code is a little complicated because it
+ // is trying to deal with evaluating a given filter expression
+ // when it contains an attribute that does not exist in the
+ // supplied mapper. In such a situation the code below
+ // catches the "attribute not found" exception and inserts
+ // an instance of Unknown, which is used as a marker for
+ // non-existent attributes. The Unknown instance forces the
+ // operator to throw an "unsupported type" exception, which
+ // the code below converts into a FALSE and this has the effect
+ // of evaluating the subexpression that contained the
+ // non-existent attribute to FALSE. The rest of the filter
+ // expression evaluates normally. Any other exceptions are
+ // rethrown.
+ setMapper(mapper);
+ for (int i = 0; i < program.length; i++)
+ {
+ try
+ {
+ Operator op = (Operator) program[i];
+ op.execute(operands, mapper);
+// printAction(op); // for debug output
+ }
+ catch (AttributeNotFoundException ex)
+ {
+ operands.push(new Unknown());
+ }
+ catch (EvaluationException ex)
+ {
+ // If the exception is for an unsupported type of
+ // type Unknown, then just push FALSE onto the
+ // operand stack because this type will only appear
+ // if an attribute was not found.
+ if (ex.isUnsupportedType() &&
+ (ex.getUnsupportedType() == Unknown.class))
+ {
+ operands.push(Boolean.FALSE);
+ }
+ // Otherwise, rethrow the exception.
+ else
+ {
+ throw ex;
+ }
+ }
+ }
+
+ if (operands.empty())
+ {
+ throw new EvaluationException(
+ "Evaluation.evalute: final stack is empty");
+ }
+
+ Object result = operands.pop();
+
+ if (!operands.empty())
+ {
+ throw new EvaluationException(
+ "Evaluation.evalute: final stack has more than one result");
+ }
+
+ if (!(result instanceof Boolean))
+ {
+ throw new EvaluationException(
+ "Evaluation.evalute: final result is not Boolean");
+ }
+
+ return ((Boolean) result).booleanValue();
+ }
+ finally
+ {
+ // Clear the operands just in case an exception was thrown,
+ // otherwise stuff will be left in the stack.
+ operands.clear();
+ }
+ }
+
+ // For debugging; Dump the operator and stack
+ void printAction(Operator op)
+ {
+ System.err.println("Operator:"+op.toString());
+ System.err.print("Stack After:");
+ // recast operands as Vector to make interior access easier
+ Vector v = operands;
+ int len = v.size();
+ for (int i = 0; i < len; i++)
+ System.err.print(" " + v.elementAt(i));
+ System.err.println();
+ }
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < program.length; i++)
+ {
+ buf.append((i==0) ? "{" : ";");
+ buf.append(((Operator) program[i]).toString());
+ }
+ buf.append("}");
+ return buf.toString();
+ }
+
+ public String toStringInfix()
+ {
+ // First, we "evaluate" the program
+ // but for the purpose of re-constructing
+ // a parsetree.
+ operands.clear();
+ for (int i = 0; i < program.length; i++)
+ {
+ ((Operator) program[i]).buildTree(operands);
+ }
+ StringBuffer b = new StringBuffer();
+ Object result = operands.pop();
+ ((Operator)result).toStringInfix(b);
+ operands.clear();
+ return b.toString();
+ }
+}
diff --git a/src/org/apache/osgi/framework/util/ldap/LdapLexer.java b/src/org/apache/osgi/framework/util/ldap/LdapLexer.java
new file mode 100644
index 0000000..1617c4d
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/LdapLexer.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util.ldap;
+
+import java.io.IOException;
+import java.io.Reader;
+
+public class LdapLexer {
+
+ static final int EOF = -1;
+ static final int NOCHAR = 0; // signal no peeked char; different from EOF
+
+ public static final String WHITESPACE = " \t\n\r";
+
+ Reader reader = null;
+
+ int nextChar = NOCHAR; // last peeked character
+
+ public LdapLexer() {}
+
+ public LdapLexer(Reader r)
+ {
+ setReader(r);
+ charno = 1;
+ }
+
+ public void setReader(Reader r)
+ {
+ reader = r;
+ }
+
+ /*
+ The procedures get(),peek(),skipwhitespace(),getnw(), and peeknw()
+ provide the essential LdapLexer interface.
+ */
+
+ public int get() throws IOException // any next char
+ {
+ if(nextChar == NOCHAR) return readChar();
+ int c = nextChar;
+ nextChar = NOCHAR;
+ return c;
+ }
+
+ public int peek() throws IOException
+ {
+ if(nextChar == NOCHAR) {
+ nextChar = readChar();
+ }
+ return nextChar;
+ }
+
+ void skipwhitespace() throws IOException
+ {
+ while(WHITESPACE.indexOf(peek()) >= 0) get();
+ }
+
+ public int getnw() throws IOException // next non-whitespace char
+ { // (note: not essential but useful)
+ skipwhitespace();
+ return get();
+ }
+
+ public int peeknw() throws IOException // next non-whitespace char
+ { // (note: not essential but useful)
+ skipwhitespace();
+ return peek();
+ }
+
+ // Following is for error reporting
+
+ // Pass all character reads thru this so we can track char count
+
+ int charno; // 1-based
+
+ public int charno() {return charno;}
+
+ int readChar() throws IOException
+ {
+ charno++;
+ return reader.read();
+ }
+
+}
diff --git a/src/org/apache/osgi/framework/util/ldap/Mapper.java b/src/org/apache/osgi/framework/util/ldap/Mapper.java
new file mode 100644
index 0000000..bf9af65
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Mapper.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util.ldap;
+
+public interface Mapper
+{
+ public Object lookup(String key);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/Operator.java b/src/org/apache/osgi/framework/util/ldap/Operator.java
new file mode 100644
index 0000000..b733238
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Operator.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util.ldap;
+
+import java.util.Stack;
+
+public abstract class Operator
+{
+ public abstract void execute(Stack operands, Mapper mapper)
+ throws EvaluationException;
+
+ public abstract String toString();
+
+ public abstract void buildTree(Stack operands); // re-build the parsetree
+ public abstract void toStringInfix(StringBuffer b); // convert to canonical string
+
+ // Place to store the reconstructed parsetree
+ // Vector -> ArrayList is using jdk1.2 or later
+ public Operator[] children = null;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/ParseException.java b/src/org/apache/osgi/framework/util/ldap/ParseException.java
new file mode 100644
index 0000000..cdc482b
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/ParseException.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util.ldap;
+
+public class ParseException extends Exception {
+ public ParseException() {super();}
+ public ParseException(String msg) {super(msg);}
+}
diff --git a/src/org/apache/osgi/framework/util/ldap/Parser.java b/src/org/apache/osgi/framework/util/ldap/Parser.java
new file mode 100644
index 0000000..d14cfe3
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Parser.java
@@ -0,0 +1,1696 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util.ldap;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.*;
+
+public class Parser
+{
+ //
+ // Parser contants.
+ //
+
+ // End of file.
+ public static final int EOF = -1;
+
+ // Special characters in parse
+ public static final char LPAREN = '(';
+ public static final char RPAREN = ')';
+ public static final char STAR = '*';
+
+ // Define the list of legal leading and trailing
+ // characters in an attribute name.
+ public static final String ATTRIBUTECHARS0 =
+ ".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
+ // Define the list of legal internal characters in an attribute name.
+ public static final String ATTRIBUTECHARS1 =
+ ".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_ ";
+
+ // Define an enum for substring procedure
+ public static final int SIMPLE = 0;
+ public static final int PRESENT = 1;
+ public static final int SUBSTRING = 2;
+
+ // different from =|>|<|~
+ public static final int NOOP = 0;
+
+ // Comparison operators.
+ public static final int EQUAL = 0;
+ public static final int GREATER_EQUAL = 1;
+ public static final int LESS_EQUAL = 2;
+ public static final int APPROX = 3;
+
+ // Criteria in % to accept something as approximate.
+ public static final int APPROX_CRITERIA = 10;
+
+ // Flag indicating presense of BigInteger/Decimal.
+ private static boolean m_hasBigNumbers = false;
+
+ static
+ {
+ try
+ {
+ Class.forName("java.math.BigDecimal");
+ m_hasBigNumbers = true;
+ }
+ catch (Exception ex)
+ {
+ // Ignore.
+ }
+ }
+ //
+ // Instance variables.
+ //
+
+ private LdapLexer lexer = null;
+ private List program;
+
+ public Parser()
+ {
+ reset();
+ }
+
+ public Parser(LdapLexer l)
+ {
+ reset(l);
+ }
+
+ public void reset()
+ {
+ lexer = null;
+ if (program == null)
+ {
+ program = new ArrayList();
+ }
+ program.clear();
+ }
+
+ public void reset(LdapLexer l)
+ {
+ reset();
+ lexer = l;
+ }
+
+ public Object[] getProgram()
+ {
+ return program.toArray(new Object[program.size()]);
+ }
+
+ // Define the recursive descent procedures
+
+ /*
+ <start>::= <filter> <EOF>
+ */
+ public boolean start() throws ParseException, IOException
+ {
+ boolean ok = filter();
+ if (!ok)
+ {
+ return ok;
+ }
+ int ch = lexer.get();
+ if (ch != EOF)
+ {
+ throw new ParseException(
+ "expected <EOF>; found '" + ((char) ch) + "'");
+ }
+ return ok;
+ }
+
+ /*
+ <filter> ::= '(' <filtercomp> ')'
+ */
+ boolean filter() throws ParseException, IOException
+ {
+ debug("filter");
+ if (lexer.peeknw() != LPAREN)
+ {
+ return false;
+ }
+ lexer.get();
+ if (!filtercomp())
+ {
+ throw new ParseException("expected filtercomp");
+ }
+ if (lexer.getnw() != RPAREN)
+ {
+ throw new ParseException("expected )");
+ }
+ return true;
+ }
+
+ /*
+ <filtercomp> ::= <and> | <or> | <not> | <item>
+ <and> ::= '&' <filterlist>
+ <or> ::= '|' <filterlist>
+ <not> ::= '!' <filter>
+ */
+ boolean filtercomp() throws ParseException, IOException
+ {
+ debug("filtercomp");
+ int c = lexer.peeknw();
+ switch (c)
+ {
+ case '&' :
+ case '|' :
+ lexer.get();
+ int cnt = filterlist();
+ if (cnt == 0)
+ {
+ return false;
+ }
+ // Code: [And|Or](cnt)
+ program.add(
+ c == '&'
+ ? (Operator) new AndOperator(cnt)
+ : (Operator) new OrOperator(cnt));
+ return true;
+ case '!' :
+ lexer.get();
+ if (!filter())
+ {
+ return false;
+ }
+ // Code: Not()
+ program.add(new NotOperator());
+ return true;
+ case EOF :
+ return false;
+ default :
+ // check for key
+ if (ATTRIBUTECHARS0.indexOf(c) <= 0)
+ {
+ return false;
+ }
+ boolean b = item();
+ return b;
+ }
+ }
+
+ /*
+ <filterlist> ::= <filter> | <filter> <filterlist>
+ */
+ int filterlist() throws ParseException, IOException
+ {
+ debug("filterlist");
+ int cnt = 0;
+ if (filter())
+ {
+ do
+ {
+ cnt++;
+ }
+ while (filter());
+ }
+ return (cnt);
+ }
+
+ /*
+ <item> ::= <simple> | <present> | <substring>
+ <simple> ::= <attr> <filtertype> <value>
+ <filtertype> ::= <equal> | <approx> | <greater> | <less>
+ <present> ::= <attr> '=*'
+ <substring> ::= <attr> '=' <initial> <any> <final>
+ */
+ boolean item() throws ParseException, IOException
+ {
+ debug("item");
+
+ StringBuffer attr = new StringBuffer();
+ if (!attribute(attr))
+ {
+ return false;
+ }
+
+ lexer.skipwhitespace(); // assume allowable before equal operator
+ // note: I treat the =* case as = followed by a special substring
+ int op = equalop();
+ if (op == NOOP)
+ {
+ String oplist = "=|~=|>=|<=";
+ throw new ParseException("expected " + oplist);
+ }
+ ArrayList pieces = new ArrayList();
+ int kind = substring(pieces);
+ // Get some of the illegal cases out of the way
+ if (op != '=' && kind != SIMPLE)
+ {
+ // We assume that only the = operator can work
+ // with right sides containing stars. If not correct
+ // then this code must change.
+ throw new ParseException("expected value|substring|*");
+ }
+
+ switch (kind)
+ {
+ case SIMPLE :
+ // Code: Push(attr); Constant(pieces.get(0)); <operator>();
+ program.add(new PushOperator(attr.toString()));
+ program.add(new ConstOperator(pieces.get(0)));
+ switch (op)
+ {
+ case '<' :
+ program.add(new LessEqualOperator());
+ break;
+ case '>' :
+ program.add(new GreaterEqualOperator());
+ break;
+ case '~' :
+ program.add(new ApproxOperator());
+ break;
+ case '=' :
+ default :
+ program.add(new EqualOperator());
+ }
+ break;
+ case PRESENT :
+ // Code: Present(attr);
+ program.add(new PresentOperator(attr.toString()));
+ break;
+ case SUBSTRING :
+ generateSubStringCode(attr.toString(), pieces);
+ break;
+ default :
+ throw new ParseException("expected value|substring|*");
+ }
+ return true;
+ }
+
+ // Generating code for substring right side is mildly
+ // complicated.
+
+ void generateSubStringCode(String attr, ArrayList pieces)
+ {
+ // Code: Push(attr)
+ program.add(new PushOperator(attr.toString()));
+
+ // Convert the pieces arraylist to a String[]
+ String[] list =
+ (String[]) pieces.toArray(new String[pieces.size()]);
+
+ // Code: SubString(list)
+ program.add(new SubStringOperator(list));
+ }
+
+ /*
+ <attr> is a string representing an attributte,
+ or key, in the properties
+ objects of the registered services. Attribute names are not case
+ sensitive; that is cn and CN both refer to the same attribute.
+ Attribute names may have embedded spaces, but not leading or
+ trailing spaces.
+ */
+ boolean attribute(StringBuffer buf) throws ParseException, IOException
+ {
+ debug("attribute");
+ lexer.skipwhitespace();
+ buf.setLength(0);
+ int c = lexer.peek(); // need to make sure there
+ // is at least one KEYCHAR
+ if (c == EOF)
+ {
+ return false;
+ }
+ if (ATTRIBUTECHARS0.indexOf(c) < 0)
+ {
+ return false;
+ }
+
+ do
+ {
+ buf.append((char) lexer.get());
+ }
+ while (ATTRIBUTECHARS1.indexOf(lexer.peek()) >= 0);
+
+ // The above may have accumulated trailing blanks that must be removed
+ int i = buf.length() - 1;
+ while (i > 0 && buf.charAt(i) == ' ')
+ {
+ i--;
+ }
+ buf.setLength(i + 1);
+ return true;
+ }
+
+ /*
+ <equal> ::= '='
+ <approx> ::= '~='
+ <greater> ::= '>='
+ <less> ::= '<='
+ <present> ::= <attr> '=*'
+ */
+ int equalop() throws ParseException, IOException
+ {
+ debug("equalop");
+ lexer.skipwhitespace();
+ int op = lexer.peek();
+ switch (op)
+ {
+ case '=' :
+ lexer.get();
+ break;
+ case '~' :
+ case '<' :
+ case '>' :
+ // skip main operator char
+ int c = lexer.get();
+ // make sure that the next char is '='
+ c = lexer.get();
+ if (c != '=')
+ {
+ throw new ParseException("expected ~=|>=|<=");
+ }
+ break;
+ default :
+ op = NOOP;
+ }
+ return op;
+ }
+
+ /*
+ <substring> ::= <attr> '=' <initial> <any> <final>
+ <initial> ::= NULL | <value>
+ <any> ::= '*' <starval>
+ <starval> ::= NULL | <value> '*' <starval>
+ <final> ::= NULL | <value>
+ <value> ::= ...
+ */
+ /*
+ This procedure handles all cases on right side of an item
+ */
+ int substring(ArrayList pieces) throws ParseException, IOException
+ {
+ debug("substring");
+
+ pieces.clear();
+ StringBuffer ss = new StringBuffer();
+ // int kind = SIMPLE; // assume until proven otherwise
+ boolean wasStar = false; // indicates last piece was a star
+ boolean leftstar = false; // track if the initial piece is a star
+ boolean rightstar = false; // track if the final piece is a star
+
+ // We assume (sub)strings can contain leading and trailing blanks
+loop: for (;;)
+ {
+ int c = lexer.peek();
+ switch (c)
+ {
+ case RPAREN :
+ if (wasStar)
+ {
+ // insert last piece as "" to handle trailing star
+ rightstar = true;
+ }
+ else
+ {
+ pieces.add(ss.toString());
+ // accumulate the last piece
+ // note that in the case of
+ // (cn=); this might be
+ // the string "" (!=null)
+ }
+ ss.setLength(0);
+ break loop;
+ case '\\' :
+ wasStar = false;
+ lexer.get();
+ c = lexer.get();
+ if (c != EOF)
+ {
+ throw new ParseException("unexpected EOF");
+ }
+ ss.append((char) c);
+ break;
+ case EOF :
+ if (pieces.size() > 0)
+ {
+ throw new ParseException("expected ')'");
+ }
+ else
+ {
+ throw new ParseException("expected value|substring");
+ }
+ case '*' :
+ if (wasStar)
+ {
+ // encountered two successive stars;
+ // I assume this is illegal
+ throw new ParseException("unexpected '**'");
+ }
+ lexer.get();
+ if (ss.length() > 0)
+ {
+ pieces.add(ss.toString()); // accumulate the pieces
+ // between '*' occurrences
+ }
+ ss.setLength(0);
+ // if this is a leading star, then track it
+ if (pieces.size() == 0)
+ {
+ leftstar = true;
+ }
+ ss.setLength(0);
+ wasStar = true;
+ break;
+ default :
+ wasStar = false;
+ ss.append((char) lexer.get());
+ }
+ }
+ if (pieces.size() == 0)
+ {
+ return PRESENT;
+ }
+ if (leftstar || rightstar || pieces.size() > 1)
+ {
+ // insert leading and/or trailing "" to anchor ends
+ if (rightstar)
+ {
+ pieces.add("");
+ }
+ if (leftstar)
+ {
+ pieces.add(0, "");
+ }
+ return SUBSTRING;
+ }
+ // assert !leftstar && !rightstar && pieces.size == 1
+ return SIMPLE;
+ }
+
+ // Debug stuff
+
+ static boolean debug = false;
+
+ PrintStream dbgout = null;
+
+ public void setDebug(PrintStream out)
+ {
+ debug = true;
+ dbgout = out;
+ }
+
+ void debug(String proc)
+ {
+ if (!debug || dbgout == null)
+ {
+ return;
+ }
+ dbgout.println("parsing " + proc + ":" + lexer.charno());
+ dbgout.flush();
+ }
+
+ // Exclusive inner classes
+
+ private static class AndOperator extends Operator
+ {
+ private int operandCount;
+
+ public AndOperator(int opcnt)
+ {
+ operandCount = opcnt;
+ }
+
+ public void execute(Stack operands, Mapper mapper)
+ throws EvaluationException
+ {
+ // Determine result using short-circuit evaluation.
+ boolean result = true;
+ for (int i = 0; i < operandCount; i++)
+ {
+ if (operands.empty())
+ {
+ fewOperands("AND");
+ }
+
+ // For short-circuited evaluation, once the AND
+ // becomes false, we can ignore the remaining
+ // expressions, but we must still pop them off.
+ if (!result)
+ {
+ operands.pop();
+ }
+ else
+ {
+ result = ((Boolean) operands.pop()).booleanValue();
+ }
+ }
+ operands.push(new Boolean(result));
+ }
+
+ public String toString()
+ {
+ return "&(" + operandCount + ")";
+ }
+
+ public void buildTree(Stack operands)
+ {
+ children = new Operator[operandCount];
+ // need to preserve stack order
+ for (int i = 0; i < operandCount; i++)
+ {
+ children[(operandCount - 1) - i] =
+ (Operator) operands.pop();
+ }
+ operands.push(this);
+ }
+
+ public void toStringInfix(StringBuffer b)
+ {
+ b.append("(&");
+ for (int i = 0; i < children.length; i++)
+ {
+ Operator o = (Operator) children[i];
+ o.toStringInfix(b);
+ }
+ b.append(")");
+ }
+ }
+
+ private static class OrOperator extends Operator
+ {
+ private int operandCount;
+
+ public OrOperator(int opcnt)
+ {
+ operandCount = opcnt;
+ }
+
+ public void execute(Stack operands, Mapper mapper)
+ throws EvaluationException
+ {
+ // Determine result using short-circuit evaluation.
+ boolean result = false;
+ for (int i = 0; i < operandCount; i++)
+ {
+ if (operands.empty())
+ {
+ fewOperands("OR");
+ }
+
+ // For short-circuited evaluation, once the OR
+ // becomes true, we can ignore the remaining
+ // expressions, but we must still pop them off.
+ if (result)
+ {
+ operands.pop();
+ }
+ else
+ {
+ result = ((Boolean) operands.pop()).booleanValue();
+ }
+ }
+ operands.push(new Boolean(result));
+ }
+
+ public String toString()
+ {
+ return "|(" + operandCount + ")";
+ }
+
+ public void buildTree(Stack operands)
+ {
+ children = new Operator[operandCount];
+ // need to preserve stack order
+ for (int i = 0; i < operandCount; i++)
+ {
+ children[(operandCount - 1) - i] =
+ (Operator) operands.pop();
+ }
+ operands.push(this);
+ }
+
+ public void toStringInfix(StringBuffer b)
+ {
+ b.append("(|");
+ for (int i = 0; i < children.length; i++)
+ {
+ Operator o = (Operator) children[i];
+ o.toStringInfix(b);
+ }
+ b.append(")");
+ }
+ }
+
+ private static class NotOperator extends Operator
+ {
+ public NotOperator()
+ {
+ }
+
+ public void execute(Stack operands, Mapper mapper)
+ throws EvaluationException
+ {
+ if (operands.empty())
+ {
+ fewOperands("NOT");
+ }
+ boolean result = !((Boolean) operands.pop()).booleanValue();
+ operands.push(new Boolean(result));
+ }
+
+ public String toString()
+ {
+ return "!()";
+ }
+
+ public void buildTree(Stack operands)
+ {
+ children = new Operator[1];
+ children[0] = (Operator) operands.pop();
+ operands.push(this);
+ }
+
+ public void toStringInfix(StringBuffer b)
+ {
+ b.append("(!");
+ for (int i = 0; i < children.length; i++)
+ {
+ Operator o = (Operator) children[i];
+ o.toStringInfix(b);
+ }
+ b.append(")");
+ }
+ }
+
+ private static class EqualOperator extends Operator
+ {
+ public EqualOperator()
+ {
+ }
+
+ public void execute(Stack operands, Mapper mapper)
+ throws EvaluationException
+ {
+ if (operands.empty())
+ {
+ fewOperands("=");
+ }
+
+ // We cheat and use the knowledge that top (right) operand
+ // will always be a string because of the way code was generated
+ String rhs = (String) operands.pop();
+ if (operands.empty())
+ {
+ fewOperands("=");
+ }
+
+ Object lhs = operands.pop();
+
+ operands.push(new Boolean(compare(lhs, rhs, EQUAL)));
+ }
+
+ public String toString()
+ {
+ return "=()";
+ }
+
+ public void buildTree(Stack operands)
+ {
+ children = new Operator[2];
+ // need to preserve stack order
+ for (int i = 0; i < 2; i++)
+ {
+ Operator o = (Operator) operands.pop();
+ children[1 - i] = o;
+ }
+ operands.push(this);
+ }
+
+ public void toStringInfix(StringBuffer b)
+ {
+ b.append("(");
+ for (int i = 0; i < children.length; i++)
+ {
+ Operator o = (Operator) children[i];
+ if (i > 0)
+ {
+ b.append("=");
+ }
+ o.toStringInfix(b);
+ }
+ b.append(")");
+ }
+ }
+
+ private static class GreaterEqualOperator extends Operator
+ {
+ public GreaterEqualOperator()
+ {
+ }
+
+ public void execute(Stack operands, Mapper mapper)
+ throws EvaluationException
+ {
+ if (operands.empty())
+ {
+ fewOperands(">=");
+ }
+ // We cheat and use the knowledge that top (right) operand
+ // will always be a string because of the way code was generated
+ String rhs = (String) operands.pop();
+ if (operands.empty())
+ {
+ fewOperands(">=");
+ }
+ Object lhs = operands.pop();
+
+ operands.push(new Boolean(compare(lhs, rhs, GREATER_EQUAL)));
+ }
+
+ public String toString()
+ {
+ return ">=()";
+ }
+
+ public void buildTree(Stack operands)
+ {
+ children = new Operator[2];
+ // need to preserve stack order
+ for (int i = 0; i < 2; i++)
+ {
+ children[1 - i] = (Operator) operands.pop();
+ }
+ operands.push(this);
+ }
+
+ public void toStringInfix(StringBuffer b)
+ {
+ b.append("(");
+ for (int i = 0; i < children.length; i++)
+ {
+ Operator o = (Operator) children[i];
+ if (i > 0)
+ {
+ b.append(">=");
+ }
+ o.toStringInfix(b);
+ }
+ b.append(")");
+ }
+ }
+
+ private static class LessEqualOperator extends Operator
+ {
+ public LessEqualOperator()
+ {
+ }
+
+ public void execute(Stack operands, Mapper mapper)
+ throws EvaluationException
+ {
+ if (operands.empty())
+ {
+ fewOperands("<=");
+ }
+ // We cheat and use the knowledge that top (right) operand
+ // will always be a string because of the way code was generated
+ String rhs = (String) operands.pop();
+ if (operands.empty())
+ {
+ fewOperands("<=");
+ }
+ Object lhs = (Object) operands.pop();
+ operands.push(new Boolean(compare(lhs, rhs, LESS_EQUAL)));
+ }
+
+ public String toString()
+ {
+ return "<=()";
+ }
+
+ public void buildTree(Stack operands)
+ {
+ children = new Operator[2];
+ // need to preserve stack order
+ for (int i = 0; i < 2; i++)
+ {
+ children[1 - i] = (Operator) operands.pop();
+ }
+ operands.push(this);
+ }
+
+ public void toStringInfix(StringBuffer b)
+ {
+ b.append("(");
+ for (int i = 0; i < children.length; i++)
+ {
+ Operator o = (Operator) children[i];
+ if (i > 0)
+ {
+ b.append("<=");
+ }
+ o.toStringInfix(b);
+ }
+ b.append(")");
+ }
+ }
+
+ private static class ApproxOperator extends Operator
+ {
+ public ApproxOperator()
+ {
+ }
+
+ public void execute(Stack operands, Mapper mapper)
+ throws EvaluationException
+ {
+ if (operands.empty())
+ {
+ fewOperands("~=");
+ }
+ // We cheat and use the knowledge that top (right) operand
+ // will always be a string because of the way code was generated
+ String rhs = (String) operands.pop();
+ if (operands.empty())
+ {
+ fewOperands("~=");
+ }
+ Object lhs = operands.pop();
+ operands.push(new Boolean(compare(lhs, rhs, APPROX)));
+ }
+
+ public String toString()
+ {
+ return "~=()";
+ }
+
+ public void buildTree(Stack operands)
+ {
+ children = new Operator[2];
+ // need to preserve stack order
+ for (int i = 0; i < 2; i++)
+ {
+ children[1 - i] = (Operator) operands.pop();
+ }
+ operands.push(this);
+ }
+
+ public void toStringInfix(StringBuffer b)
+ {
+ b.append("(");
+ for (int i = 0; i < children.length; i++)
+ {
+ Operator o = (Operator) children[i];
+ if (i > 0)
+ {
+ b.append("~=");
+ }
+ o.toStringInfix(b);
+ }
+ b.append(")");
+ }
+ }
+
+ private static class PresentOperator extends Operator
+ {
+ String attribute;
+
+ public PresentOperator(String attribute)
+ {
+ this.attribute = attribute;
+ }
+
+ public void execute(Stack operands, Mapper mapper)
+ throws EvaluationException
+ {
+ Object value = mapper.lookup(attribute);
+ operands.push(new Boolean(value != null));
+ }
+
+ public String toString()
+ {
+ return attribute + "=*";
+ }
+
+ public void buildTree(Stack operands)
+ {
+ operands.push(this);
+ }
+
+ public void toStringInfix(StringBuffer b)
+ {
+ b.append("(");
+ b.append(attribute + "=*");
+ b.append(")");
+ }
+ }
+
+ private static class PushOperator extends Operator
+ {
+ String attribute;
+
+ public PushOperator(String attribute)
+ {
+ this.attribute = attribute;
+ }
+
+ public void execute(Stack operands, Mapper mapper)
+ throws EvaluationException
+ {
+ // find and push the value of a given attribute
+ Object value = mapper.lookup(attribute);
+ if (value == null)
+ {
+ throw new AttributeNotFoundException(
+ "attribute " + attribute + " not found");
+ }
+ operands.push(value);
+ }
+
+ public String toString()
+ {
+ return "push(" + attribute + ")";
+ }
+
+ public String toStringInfix()
+ {
+ return attribute;
+ }
+
+ public void buildTree(Stack operands)
+ {
+ operands.push(this);
+ }
+
+ public void toStringInfix(StringBuffer b)
+ {
+ b.append(attribute);
+ }
+ }
+
+ private static class ConstOperator extends Operator
+ {
+ Object val;
+
+ public ConstOperator(Object val)
+ {
+ this.val = val;
+ }
+
+ public void execute(Stack operands, Mapper mapper)
+ throws EvaluationException
+ {
+ operands.push(val);
+ }
+
+ public String toString()
+ {
+ return "const(" + val + ")";
+ }
+
+ public String toStringInfix()
+ {
+ return val.toString();
+ }
+
+ public void buildTree(Stack operands)
+ {
+ operands.push(this);
+ }
+
+ public void toStringInfix(StringBuffer b)
+ {
+ b.append(val.toString());
+ }
+ }
+
+ private static class SubStringOperator extends Operator
+ implements OperatorConstants
+ {
+ String[] pieces;
+
+ public SubStringOperator(String[] pieces)
+ {
+ this.pieces = pieces;
+ }
+
+ public void execute(Stack operands, Mapper mapper)
+ throws EvaluationException
+ {
+ if (operands.empty())
+ {
+ fewOperands("SUBSTRING");
+ }
+
+ Object op = operands.pop();
+
+ // The operand can either be a string or an array of strings.
+ if (op instanceof String)
+ {
+ operands.push(check((String) op));
+ }
+ else if (op instanceof String[])
+ {
+ // If one element of the array matches, then push true.
+ String[] ops = (String[]) op;
+ boolean result = false;
+ for (int i = 0; !result && (i < ops.length); i++)
+ {
+ if (check(ops[i]) == Boolean.TRUE)
+ {
+ result = true;
+ }
+ }
+
+ operands.push((result) ? Boolean.TRUE : Boolean.FALSE);
+ }
+ else
+ {
+ unsupportedType("SUBSTRING", op.getClass());
+ }
+ }
+
+ private Boolean check(String s)
+ {
+ // Walk the pieces to match the string
+ // There are implicit stars between each piece,
+ // and the first and last pieces might be "" to anchor the match.
+ // assert (pieces.length > 1)
+ // minimal case is <string>*<string>
+
+ Boolean result = Boolean.FALSE;
+ int len = pieces.length;
+
+ loop : for (int i = 0; i < len; i++)
+ {
+ String piece = (String) pieces[i];
+ int index = 0;
+ if (i == len - 1)
+ {
+ // this is the last piece
+ if (s.endsWith(piece))
+ {
+ result = Boolean.TRUE;
+ }
+ else
+ {
+ result = Boolean.FALSE;
+ }
+ break loop;
+ }
+ // initial non-star; assert index == 0
+ else if (i == 0)
+ {
+ if (!s.startsWith(piece))
+ {
+ result = Boolean.FALSE;
+ break loop;
+ }
+ }
+ // assert i > 0 && i < len-1
+ else
+ {
+ // Sure wish stringbuffer supported e.g. indexOf
+ index = s.indexOf(piece, index);
+ if (index < 0)
+ {
+ result = Boolean.FALSE;
+ break loop;
+ }
+ }
+ // start beyond the matching piece
+ index += piece.length();
+ }
+
+ return result;
+ }
+
+ public String toString()
+ {
+ StringBuffer b = new StringBuffer();
+ b.append("substring(");
+ for (int i = 0; i < pieces.length; i++)
+ {
+ String piece = pieces[i];
+ if (i > 0)
+ {
+ b.append("*");
+ }
+ b.append(escape(piece));
+ }
+ b.append(")");
+ return b.toString();
+ }
+
+ public String escape(String s)
+ {
+ int len = s.length();
+ StringBuffer buf = new StringBuffer(len);
+ for (int i = 0; i < len; i++)
+ {
+ char c = s.charAt(i);
+ if (c == ')' || c == '*')
+ buf.append('\\');
+ buf.append(c);
+ }
+ return buf.toString();
+ }
+
+ public void buildTree(Stack operands)
+ {
+ children = new Operator[1];
+ children[0] = (Operator) operands.pop();
+ operands.push(this);
+ }
+
+ public void toStringInfix(StringBuffer b)
+ {
+ b.append("(");
+ children[0].toStringInfix(b); // dump attribute
+ b.append("=");
+ for (int i = 0; i < pieces.length; i++)
+ {
+ String piece = (String) pieces[i];
+ if (i > 0)
+ {
+ b.append("*");
+ }
+ b.append(piece);
+ }
+ b.append(")");
+ }
+ }
+
+ // Utility classes and Interfaces
+
+ private interface OperatorConstants
+ {
+ static final int SSINIT = 0;
+ static final int SSFINAL = 1;
+ static final int SSMIDDLE = 2;
+ static final int SSANY = 3;
+ }
+
+ /**
+ * Compare two operands in an expression with respect
+ * to the following operators =, <=, >= and ~=
+ *
+ * Example: value=100
+ *
+ * @param lhs an object that implements comparable or an array of
+ * objects that implement comparable.
+ * @param rhs a string representing the right operand.
+ * @param operator an integer that represents the operator.
+ * @return <tt>true</tt> or <tt>false</tt> according to the evaluation.
+ * @throws EvaluationException if it is not possible to do the comparison.
+ **/
+ public static boolean compare(Object lhs, String rhs, int operator)
+ throws EvaluationException
+ {
+ // Determine class of LHS.
+ Class lhsClass = null;
+
+ // If LHS is an array, then call compare() on each element
+ // of the array until a match is found.
+ if (lhs.getClass().isArray())
+ {
+ // First, if this is an array of primitives, then convert
+ // the entire array to an array of the associated
+ // primitive wrapper class instances.
+ if (lhs.getClass().getComponentType().isPrimitive())
+ {
+ lhs = convertPrimitiveArray(lhs);
+ }
+
+ // Now call compare on each element of array.
+ Object[] array = (Object[]) lhs;
+ for (int i = 0; i < array.length; i++)
+ {
+ if (compare(array[i], rhs, operator))
+ {
+ return true;
+ }
+ }
+ }
+ // If LHS is a vector, then call compare() on each element
+ // of the vector until a match is found.
+ else if (lhs instanceof Vector)
+ {
+ for (Enumeration e = ((Vector) lhs).elements(); e.hasMoreElements();)
+ {
+ if (compare(e.nextElement(), rhs, operator))
+ {
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // Get the class of LHS.
+ lhsClass = lhs.getClass();
+
+ // At this point we are expecting the LHS to be a comparable,
+ // but Boolean is a special case since it is the only primitive
+ // wrapper class that does not implement comparable; deal with
+ // Boolean separately.
+ if (lhsClass == Boolean.class)
+ {
+ return compareBoolean(lhs, rhs, operator);
+ }
+
+ // If LHS is not a Boolean, then verify it is a comparable
+ // and perform comparison.
+ if (!(Comparable.class.isAssignableFrom(lhsClass)))
+ {
+ String opName = null;
+ switch (operator)
+ {
+ case EQUAL :
+ opName = "=";
+ case GREATER_EQUAL :
+ opName = ">=";
+ case LESS_EQUAL :
+ opName = "<=";
+ case APPROX:
+ opName = "~=";
+ default:
+ opName = "UNKNOWN OP";
+ }
+
+ unsupportedType(opName, lhsClass);
+ }
+
+ // We will try to create a comparable object from the
+ // RHS string.
+ Comparable rhsComparable = null;
+ try
+ {
+ // We are expecting to be able to construct a comparable
+ // instance from the RHS string by passing it into the
+ // constructor of the corresponing comparable class. The
+ // Character class is a special case, since its constructor
+ // does not take a string, so handle it separately.
+ if (lhsClass == Character.class)
+ {
+ rhsComparable = new Character(rhs.charAt(0));
+ }
+ else
+ {
+ rhsComparable = (Comparable) lhsClass
+ .getConstructor(new Class[] { String.class })
+ .newInstance(new Object[] { rhs });
+ }
+ }
+ catch (Exception ex)
+ {
+ String msg = (ex.getCause() == null)
+ ? ex.toString() : ex.getCause().toString();
+ throw new EvaluationException(
+ "Could not instantiate class "
+ + lhsClass.getName()
+ + " with constructor String parameter "
+ + rhs + " " + msg);
+ }
+
+ Comparable lhsComparable = (Comparable) lhs;
+
+ switch (operator)
+ {
+ case EQUAL :
+ return (lhsComparable.compareTo(rhsComparable) == 0);
+ case GREATER_EQUAL :
+ return (lhsComparable.compareTo(rhsComparable) >= 0);
+ case LESS_EQUAL :
+ return (lhsComparable.compareTo(rhsComparable) <= 0);
+ case APPROX:
+ return compareToApprox(lhsComparable, rhsComparable);
+ default:
+ throw new EvaluationException("Unknown comparison operator..."
+ + operator);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * This is an ugly utility method to convert an array of primitives
+ * to an array of primitive wrapper objects. This method simplifies
+ * processing LDAP filters since the special case of primitive arrays
+ * can be ignored.
+ * @param array
+ * @return
+ **/
+ private static Object[] convertPrimitiveArray(Object array)
+ {
+ Class clazz = array.getClass().getComponentType();
+
+ if (clazz == Boolean.TYPE)
+ {
+ boolean[] src = (boolean[]) array;
+ array = new Boolean[src.length];
+ for (int i = 0; i < src.length; i++)
+ {
+ ((Object[]) array)[i] = new Boolean(src[i]);
+ }
+ }
+ else if (clazz == Character.TYPE)
+ {
+ char[] src = (char[]) array;
+ array = new Character[src.length];
+ for (int i = 0; i < src.length; i++)
+ {
+ ((Object[]) array)[i] = new Character(src[i]);
+ }
+ }
+ else if (clazz == Byte.TYPE)
+ {
+ byte[] src = (byte[]) array;
+ array = new Byte[src.length];
+ for (int i = 0; i < src.length; i++)
+ {
+ ((Object[]) array)[i] = new Byte(src[i]);
+ }
+ }
+ else if (clazz == Short.TYPE)
+ {
+ byte[] src = (byte[]) array;
+ array = new Byte[src.length];
+ for (int i = 0; i < src.length; i++)
+ {
+ ((Object[]) array)[i] = new Byte(src[i]);
+ }
+ }
+ else if (clazz == Integer.TYPE)
+ {
+ int[] src = (int[]) array;
+ array = new Integer[src.length];
+ for (int i = 0; i < src.length; i++)
+ {
+ ((Object[]) array)[i] = new Integer(src[i]);
+ }
+ }
+ else if (clazz == Long.TYPE)
+ {
+ long[] src = (long[]) array;
+ array = new Long[src.length];
+ for (int i = 0; i < src.length; i++)
+ {
+ ((Object[]) array)[i] = new Long(src[i]);
+ }
+ }
+ else if (clazz == Float.TYPE)
+ {
+ float[] src = (float[]) array;
+ array = new Float[src.length];
+ for (int i = 0; i < src.length; i++)
+ {
+ ((Object[]) array)[i] = new Float(src[i]);
+ }
+ }
+ else if (clazz == Double.TYPE)
+ {
+ double[] src = (double[]) array;
+ array = new Double[src.length];
+ for (int i = 0; i < src.length; i++)
+ {
+ ((Object[]) array)[i] = new Double(src[i]);
+ }
+ }
+
+ return (Object[]) array;
+ }
+
+ private static boolean compareBoolean(Object lhs, String rhs, int operator)
+ throws EvaluationException
+ {
+ Boolean rhsBoolean = new Boolean(rhs);
+ if (lhs.getClass().isArray())
+ {
+ Object[] objs = (Object[]) lhs;
+ for (int i = 0; i < objs.length; i++)
+ {
+ switch (operator)
+ {
+ case EQUAL :
+ case GREATER_EQUAL :
+ case LESS_EQUAL :
+ case APPROX:
+ if (objs[i].equals(rhsBoolean))
+ {
+ return true;
+ }
+ break;
+ default:
+ throw new EvaluationException(
+ "Unknown comparison operator: " + operator);
+ }
+ }
+ return false;
+ }
+ else
+ {
+ switch (operator)
+ {
+ case EQUAL :
+ case GREATER_EQUAL :
+ case LESS_EQUAL :
+ case APPROX:
+ return (lhs.equals(rhsBoolean));
+ default:
+ throw new EvaluationException("Unknown comparison operator..."
+ + operator);
+ }
+ }
+ }
+
+ /**
+ * Test if two objects are approximate. The two objects that are passed must
+ * have the same type.
+ *
+ * Approximate for numerical values involves a difference of less than APPROX_CRITERIA
+ * Approximate for string values is calculated by using the Levenshtein distance
+ * between strings. Less than APPROX_CRITERIA of difference is considered as approximate.
+ *
+ * Supported types only include the following subclasses of Number:
+ * - Byte
+ * - Double
+ * - Float
+ * - Int
+ * - Long
+ * - Short
+ * - BigInteger
+ * - BigDecimal
+ * As subclasses of Number must provide methods to convert the represented numeric value
+ * to byte, double, float, int, long, and short. (see API)
+ *
+ * @param obj1
+ * @param obj2
+ * @return true if they are approximate
+ * @throws EvaluationException if it the two objects cannot be approximated
+ **/
+ private static boolean compareToApprox(Object obj1, Object obj2) throws EvaluationException
+ {
+ if (obj1 instanceof Byte)
+ {
+ byte value1 = ((Byte)obj1).byteValue();
+ byte value2 = ((Byte)obj2).byteValue();
+ return (value2 >= (value1-((Math.abs(value1)*(byte)APPROX_CRITERIA)/(byte)100))
+ && value2 <= (value1+((Math.abs(value1)*(byte)APPROX_CRITERIA)/(byte)100)));
+ }
+ else if (obj1 instanceof Character)
+ {
+ char value1 = ((Character)obj1).charValue();
+ char value2 = ((Character)obj2).charValue();
+ return (value2 >= (value1-((Math.abs(value1)*(char)APPROX_CRITERIA)/(char)100))
+ && value2 <= (value1+((Math.abs(value1)*(char)APPROX_CRITERIA)/(char)100)));
+ }
+ else if (obj1 instanceof Double)
+ {
+ double value1 = ((Double)obj1).doubleValue();
+ double value2 = ((Double)obj2).doubleValue();
+ return (value2 >= (value1-((Math.abs(value1)*(double)APPROX_CRITERIA)/(double)100))
+ && value2 <= (value1+((Math.abs(value1)*(double)APPROX_CRITERIA)/(double)100)));
+ }
+ else if (obj1 instanceof Float)
+ {
+ float value1 = ((Float)obj1).floatValue();
+ float value2 = ((Float)obj2).floatValue();
+ return (value2 >= (value1-((Math.abs(value1)*(float)APPROX_CRITERIA)/(float)100))
+ && value2 <= (value1+((Math.abs(value1)*(float)APPROX_CRITERIA)/(float)100)));
+ }
+ else if (obj1 instanceof Integer)
+ {
+ int value1 = ((Integer)obj1).intValue();
+ int value2 = ((Integer)obj2).intValue();
+ return (value2 >= (value1-((Math.abs(value1)*(int)APPROX_CRITERIA)/(int)100))
+ && value2 <= (value1+((Math.abs(value1)*(int)APPROX_CRITERIA)/(int)100)));
+ }
+ else if (obj1 instanceof Long)
+ {
+ long value1 = ((Long)obj1).longValue();
+ long value2 = ((Long)obj2).longValue();
+ return (value2 >= (value1-((Math.abs(value1)*(long)APPROX_CRITERIA)/(long)100))
+ && value2 <= (value1+((Math.abs(value1)*(long)APPROX_CRITERIA)/(long)100)));
+ }
+ else if (obj1 instanceof Short)
+ {
+ short value1 = ((Short)obj1).shortValue();
+ short value2 = ((Short)obj2).shortValue();
+ return (value2 >= (value1-((Math.abs(value1)*(short)APPROX_CRITERIA)/(short)100))
+ && value2 <= (value1+((Math.abs(value1)*(short)APPROX_CRITERIA)/(short)100)));
+ }
+ else if (obj1 instanceof String)
+ {
+ int distance = getDistance(obj1.toString(),obj2.toString());
+ int size = ((String)obj1).length();
+ return (distance <= ((size*APPROX_CRITERIA)/100));
+ }
+ else if (m_hasBigNumbers && (obj1 instanceof BigInteger))
+ {
+ BigInteger value1 = (BigInteger)obj1;
+ BigInteger value2 = (BigInteger)obj2;
+ BigInteger delta = value1.abs().multiply(
+ BigInteger.valueOf(APPROX_CRITERIA)
+ .divide(BigInteger.valueOf(100)));
+ BigInteger low = value1.subtract(delta);
+ BigInteger high = value1.add(delta);
+ return (value2.compareTo(low) >= 0) && (value2.compareTo(high) <= 0);
+ }
+ else if (m_hasBigNumbers && (obj1 instanceof BigDecimal))
+ {
+ BigDecimal value1 = (BigDecimal)obj1;
+ BigDecimal value2 = (BigDecimal)obj2;
+ BigDecimal delta = value1.abs().multiply(
+ BigDecimal.valueOf(APPROX_CRITERIA)
+ .divide(BigDecimal.valueOf(100), BigDecimal.ROUND_HALF_DOWN));
+ BigDecimal low = value1.subtract(delta);
+ BigDecimal high = value1.add(delta);
+ return (value2.compareTo(low) >= 0) && (value2.compareTo(high) <= 0);
+ }
+ throw new EvaluationException(
+ "Approximate operator not supported for type "
+ + obj1.getClass().getName());
+ }
+
+ /**
+ * Calculate the Levenshtein distance (LD) between two strings.
+ * The Levenshteing distance is a measure of the similarity between
+ * two strings, which we will refer to as the source string (s) and
+ * the target string (t). The distance is the number of deletions,
+ * insertions, or substitutions required to transform s into t.
+ *
+ * Algorithm from: http://www.merriampark.com/ld.htm
+ *
+ * @param s the first string
+ * @param t the second string
+ * @return
+ */
+ private static int getDistance(String s, String t)
+ {
+ int d[][]; // matrix
+ int n; // length of s
+ int m; // length of t
+ int i; // iterates through s
+ int j; // iterates through t
+ char s_i; // ith character of s
+ char t_j; // jth character of t
+ int cost; // cost
+
+ // Step 1
+ n = s.length();
+ m = t.length();
+ if (n == 0)
+ {
+ return m;
+ }
+ if (m == 0)
+ {
+ return n;
+ }
+ d = new int[n + 1][m + 1];
+
+ // Step 2
+ for (i = 0; i <= n; i++)
+ {
+ d[i][0] = i;
+ }
+
+ for (j = 0; j <= m; j++)
+ {
+ d[0][j] = j;
+ }
+
+ // Step 3
+ for (i = 1; i <= n; i++)
+ {
+ s_i = s.charAt(i - 1);
+ // Step 4
+ for (j = 1; j <= m; j++)
+ {
+ t_j = t.charAt(j - 1);
+ // Step 5
+ if (s_i == t_j)
+ {
+ cost = 0;
+ }
+ else
+ {
+ cost = 1;
+ }
+ // Step 6
+ d[i][j] =
+ Minimum(
+ d[i - 1][j] + 1,
+ d[i][j - 1] + 1,
+ d[i - 1][j - 1] + cost);
+ }
+ }
+ // Step 7
+ return d[n][m];
+ }
+
+ /**
+ * Calculate the minimum between three values
+ *
+ * @param a
+ * @param b
+ * @param c
+ * @return
+ */
+ private static int Minimum(int a, int b, int c)
+ {
+ int mi;
+ mi = a;
+ if (b < mi)
+ {
+ mi = b;
+ }
+ if (c < mi)
+ {
+ mi = c;
+ }
+ return mi;
+ }
+
+ private static void fewOperands(String op) throws EvaluationException
+ {
+ throw new EvaluationException(op + ": too few operands");
+ }
+
+ private static void unsupportedType(String opStr, Class clazz)
+ throws EvaluationException
+ {
+ throw new EvaluationException(
+ opStr + ": unsupported type " + clazz.getName(), clazz);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/framework/util/ldap/Unknown.java b/src/org/apache/osgi/framework/util/ldap/Unknown.java
new file mode 100644
index 0000000..8139f8b
--- /dev/null
+++ b/src/org/apache/osgi/framework/util/ldap/Unknown.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.framework.util.ldap;
+
+/**
+ * This class is used to create simple marker instances that are inserted
+ * into the evaluation stack of a LDAP filter expression when an attribute
+ * is referenced that has no defined value. These invalid marker instances
+ * force the operators to throw an "unsupported type" exception, which the
+ * evaluator catches and then converts the entire subexpression containing
+ * the non-existent attribute to <tt>false</tt>.
+**/
+class Unknown
+{
+}
diff --git a/src/org/apache/osgi/moduleloader/DefaultURLPolicy.java b/src/org/apache/osgi/moduleloader/DefaultURLPolicy.java
new file mode 100644
index 0000000..55b2e96
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/DefaultURLPolicy.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+import java.net.URL;
+
+/**
+ * <p>
+ * This class implements a simple <tt>URLPolicy</tt> that the <tt>ModuleManager</tt>
+ * uses if the application does not specify one. This implementation always returns
+ * <tt>null</tt> for <tt>CodeSource</tt> <tt>URL</tt>s, which means that security
+ * is simply ignored. For resource <tt>URL</tt>s, it returns an <tt>URL</tt> in the
+ * form of:
+ * </p>
+ * <pre>
+ * module://<module-id>/<resource-path>
+ * </pre>
+ * <p>
+ * In order to properly handle the "<tt>module:</tt>" protocol, this policy
+ * also defines a custom <tt>java.net.URLStreamHandler</tt> that it assigns
+ * to each <tt>URL</tt> as it is created. This custom handler is used to
+ * return a custom <tt>java.net.URLConnection</tt> that will correctly parse
+ * the above <tt>URL</tt> and retrieve the associated resource bytes using
+ * methods from <tt>ModuleManager</tt> and <tt>Module</tt>.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ModuleManager
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.URLPolicy
+**/
+public class DefaultURLPolicy implements URLPolicy
+{
+ private ModuleURLStreamHandler m_handler = null;
+
+ /**
+ * <p>
+ * This method is a stub and always returns <tt>null</tt>.
+ * </p>
+ * @param mgr the <tt>ModuleManager</tt> of the module.
+ * @param module the module for which the <tt>URL</tt> is to be created.
+ * @return <tt>null</tt>.
+ **/
+ public URL createCodeSourceURL(ModuleManager mgr, Module module)
+ {
+ return null;
+ }
+
+ /**
+ * <p>
+ * This method returns a <tt>URL</tt> that is suitable
+ * for accessing the bytes of the specified resource.
+ * </p>
+ * @param mgr the <tt>ModuleManager</tt> of the module.
+ * @param module the module for which the resource is being loaded.
+ * @param rsIdx the index of the <tt>ResourceSource</tt> containing the resource.
+ * @param name the name of the resource being loaded.
+ * @return an <tt>URL</tt> for retrieving the resource.
+ **/
+ public URL createResourceURL(ModuleManager mgr, Module module, int rsIdx, String name)
+ {
+ if (m_handler == null)
+ {
+ m_handler = new ModuleURLStreamHandler(mgr);
+ }
+
+ // Add a slash if there is one already, otherwise
+ // the is no slash separating the host from the file
+ // in the resulting URL.
+ if (!name.startsWith("/"))
+ {
+ name = "/" + name;
+ }
+
+ try
+ {
+ return new URL("module", module.getId(), -1, "/" + rsIdx + name, m_handler);
+ }
+ catch (Exception ex)
+ {
+ System.err.println("DefaultResourceURLPolicy: " + ex);
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/JarResourceSource.java b/src/org/apache/osgi/moduleloader/JarResourceSource.java
new file mode 100644
index 0000000..16cd8fc
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/JarResourceSource.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+import java.io.*;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+/**
+ * <p>
+ * This class implements a <tt>ResourceSource</tt> for retrieving resources
+ * from a JAR file. The approach used by this implementation is to defer
+ * opening the JAR file until a request for a resource is made.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ResourceSource
+**/
+public class JarResourceSource implements ResourceSource
+{
+ private static final int BUFSIZE = 4096;
+
+ private File m_file = null;
+ private JarFile m_jarFile = null;
+ private boolean m_opened = false;
+
+ /**
+ * <p>
+ * Constructs an instance using the specified file name as the source
+ * of the JAR file.
+ * </p>
+ * @param fileName the name of the JAR file to be used as the source.
+ **/
+ public JarResourceSource(String fileName)
+ {
+ m_file = new File(fileName);
+ }
+
+ /**
+ * <p>
+ * Constructs an instance using the specified file as the source
+ * of the JAR file.
+ * </p>
+ * @param file the JAR file to be used as the source.
+ **/
+ public JarResourceSource(File file)
+ {
+ m_file = file;
+ }
+
+ /**
+ * <p>
+ * Closes the JAR file if it has not already been closed.
+ * <p>
+ **/
+ protected void finalize()
+ {
+ if (m_jarFile != null)
+ {
+ try {
+ m_jarFile.close();
+ } catch (IOException ex) {
+ // Not much we can do, so ignore it.
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * This method initializes the resource source. Since opening
+ * the JAR file is deferred until a request for a resource is
+ * actually made, this method really only sets a flag indicating
+ * that the resource source has been initialized.
+ * <p>
+ **/
+ public void open()
+ {
+ m_opened = true;
+ }
+
+ /**
+ * <p>
+ * This method deinitializes the resource source by closing
+ * the associated JAR file if it is open.
+ * <p>
+ **/
+ public synchronized void close()
+ {
+ try {
+ if (m_jarFile != null)
+ {
+ m_jarFile.close();
+ }
+ } catch (Exception ex) {
+ System.err.println("JarResourceSource: " + ex);
+ }
+
+ m_jarFile = null;
+ m_opened = false;
+ }
+
+ // JavaDoc comments are copied from ResourceSource.
+ public synchronized boolean hasResource(String name) throws IllegalStateException
+ {
+ if (!m_opened)
+ {
+ throw new IllegalStateException("JarResourceSource is not open");
+ }
+
+ // Open JAR file if not already opened.
+ if (m_jarFile == null)
+ {
+ try {
+ openJarFile();
+ } catch (IOException ex) {
+ System.err.println("JarResourceSource: " + ex);
+ return false;
+ }
+ }
+
+ try {
+ ZipEntry ze = m_jarFile.getEntry(name);
+ return ze != null;
+ } catch (Exception ex) {
+ return false;
+ } finally {
+ }
+ }
+
+ // JavaDoc comments are copied from ResourceSource.
+ public synchronized byte[] getBytes(String name) throws IllegalStateException
+ {
+ if (!m_opened)
+ {
+ throw new IllegalStateException("JarResourceSource is not open");
+ }
+
+ // Open JAR file if not already opened.
+ if (m_jarFile == null)
+ {
+ try {
+ openJarFile();
+ } catch (IOException ex) {
+ System.err.println("JarResourceSource: " + ex);
+ return null;
+ }
+ }
+
+ // Get the embedded resource.
+ InputStream is = null;
+ ByteArrayOutputStream baos = null;
+
+ try {
+ ZipEntry ze = m_jarFile.getEntry(name);
+ if (ze == null)
+ {
+ return null;
+ }
+ is = m_jarFile.getInputStream(ze);
+ if (is == null)
+ {
+ return null;
+ }
+ baos = new ByteArrayOutputStream(BUFSIZE);
+ byte[] buf = new byte[BUFSIZE];
+ int n = 0;
+ while ((n = is.read(buf, 0, buf.length)) >= 0)
+ {
+ baos.write(buf, 0, n);
+ }
+ return baos.toByteArray();
+
+ } catch (Exception ex) {
+ return null;
+ } finally {
+ try {
+ if (baos != null) baos.close();
+ } catch (Exception ex) {
+ }
+ try {
+ if (is != null) is.close();
+ } catch (Exception ex) {
+ }
+ }
+ }
+
+ private void openJarFile() throws IOException
+ {
+ if (m_jarFile == null)
+ {
+ m_jarFile = new JarFile(m_file);
+ }
+ }
+
+ public String toString()
+ {
+ return "JAR " + m_file.getPath();
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/LibrarySource.java b/src/org/apache/osgi/moduleloader/LibrarySource.java
new file mode 100644
index 0000000..775dcd7
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/LibrarySource.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+/**
+ * <p>
+ * This interface represents a source for obtaining native libraries for a
+ * given module via the module's class loader. The main goal of a library
+ * source is to map a library name to a path in the file system.
+ * </p>
+ * <p>
+ * All library sources are initialized before first usage via a call
+ * to the <a href="#open()"><tt>LibrarySource.open()</tt></a> method and
+ * are also deinitialized via a call to
+ * <a href="#open()"><tt>LibrarySource.close()</tt></a>. Library sources
+ * should be implemented such that they can be opened, closed, and then
+ * re-opened.
+ * </p>
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+**/
+public interface LibrarySource
+{
+ /**
+ * <p>
+ * This method initializes the library source. It is called when
+ * the associated module is added to the <tt>ModuleManager</tt>. It
+ * is acceptable for implementations to ignore duplicate calls to this
+ * method if the library source is already opened.
+ * </p>
+ **/
+ public void open();
+
+ /**
+ * <p>
+ * This method de-initializes the library source. It is called when
+ * the associated module is removed from the <tt>ModuleManager</tt> or
+ * when the module is reset by the <tt>ModuleManager</tt>.
+ * </p>
+ **/
+ public void close();
+
+ /**
+ * <p>
+ * Returns a file system path to the specified library.
+ * </p>
+ * @param name the name of the library that is being requested.
+ * @return a file system path to the specified library.
+ * @throws java.lang.IllegalStateException if the resource source has not
+ * been opened.
+ **/
+ public String getPath(String name) throws IllegalStateException;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/Module.java b/src/org/apache/osgi/moduleloader/Module.java
new file mode 100644
index 0000000..c33a5fa
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/Module.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+
+/**
+ * <p>
+ * The <tt>Module</tt> class is a grouping mechanism for application classes
+ * and resources. Conceptually, most applications are grouped into
+ * entities such as JAR files (containing classes and resources) and native
+ * libraries. In some cases, these entities are core application classes and
+ * resources, while in other cases, these entities are ancillary, such as
+ * dynamically loaded plug-ins. Applications place some level of semantics
+ * onto these types of entities or <i>modules</i>, but for the <tt>ModuleLoader</tt>,
+ * no particular semantics are attached to modules (other than they are a grouping
+ * mechanism for classes and resources). This means that the application
+ * is free to map itself into modules any way that is appropriate.
+ * </p>
+ * <p>
+ * A module has the following features:
+ * </p>
+ * <ul>
+ * <li>A unique identifier within the scope of its <tt>ModuleManager</tt>.
+ * </li>
+ * <li>A set of key-value attribute pairs.
+ * </li>
+ * <li>A set of resource sources from which it is possible to
+ * retrieve classes and resources.
+ * </li>
+ * <li>A set of native library sources from which it is possible
+ * to retrieve native libraries.
+ * </li>
+ * </ul>
+ * <p>
+ * A module's identifier must be unique within the scope of its
+ * <tt>ModuleManager</tt>, but there is no meaning associated with it. The
+ * set of attribute-value pairs attached to the module have no meaning to
+ * the <tt>ModuleManager</tt>, nor does it consult them at all. The point
+ * of these attributes is to attach meta-data for use by
+ * <a href="SearchPolicy.html"><tt>SearchPolicy</tt></a> implementations.
+ * Attributes are represented as an array of <tt>Object</tt>
+ * arrays, i.e., <tt>Object[][]</tt>. Each element in the attribute array is
+ * a two-element <tt>Object</tt> array, where <tt>Module.KEY_IDX</tt> is the attribute's
+ * key and <tt>Module.VALUE_IDX</tt> is the attribute's value.
+ * </p>
+ * <p>
+ * The actual contents of a module is contained in two sets of sources
+ * for its resources and native libraries,
+ * <a href="ResourceSource.html"><tt>ResourceSource</tt></a>s
+ * and <a href="LibrarySource.html"><tt>LibrarySource</tt></a>s, respectively.
+ * Each module also has a <a href="ModuleClassLoader.html"><tt>ModuleClassLoader</tt></a>
+ * associated with it. The <tt>ModuleClassLoader</tt> consults these two types
+ * of sources to find classes, resources, and native libraries.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ModuleManager
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+ * @see org.apache.osgi.moduleloader.ResourceSource
+ * @see org.apache.osgi.moduleloader.LibrarySource
+**/
+public class Module
+{
+ /**
+ * This is the index used to retrieve the key of an attribute;
+ * an attribute is represented as an <tt>Object[]</tt> instance.
+ **/
+ public static final int KEY_IDX = 0;
+ /**
+ * This is the index used to retrieve the value of an attribute;
+ * an attribute is represented as an <tt>Object[]</tt> instance.
+ **/
+ public static final int VALUE_IDX = 1;
+
+ private ModuleManager m_mgr = null;
+ private String m_id = null;
+ private boolean m_useParentSource = false;
+ private Map m_attributeMap = new HashMap();
+ private ResourceSource[] m_resSources = null;
+ private LibrarySource[] m_libSources = null;
+ private ModuleClassLoader m_loader = null;
+
+ /**
+ * <p>
+ * Constructs a <tt>Module</tt> instance that will be associated with
+ * the specified <tt>ModuleManager</tt> and will have the specified
+ * identifier, attributes, resource sources, and library sources. In general,
+ * modules should not be created directly, but should be created by making
+ * a call to <tt>ModuleManager.addModule()</tt>.
+ * </p>
+ * @param mgr the <tt>ModuleManager</tt> that will be associated to
+ * the instance.
+ * @param id the identifier of the instance.
+ * @param attributes the set of attributes associated with the instance.
+ * @param resSources the set of <tt>ResourceSource</tt>s associated with
+ * the instance.
+ * @param libSources the set of <tt>LibrarySource</tt>s associated with
+ * the instance.
+ * @param useParentSource a flag indicating whether or not the parent
+ * class loader should be used as a resource source; this is an
+ * ugly hack to allow a module to masquerade as the system
+ * class loader.
+ * @see org.apache.osgi.moduleloader.ModuleManager
+ * @see org.apache.osgi.moduleloader.ResourceSource
+ * @see org.apache.osgi.moduleloader.LibrarySource
+ **/
+ public Module(
+ ModuleManager mgr, String id, Object[][] attributes,
+ ResourceSource[] resSources, LibrarySource[] libSources,
+ boolean useParentSource)
+ {
+ m_mgr = mgr;
+ m_id = id;
+ m_useParentSource = useParentSource;
+ initialize(attributes, resSources, libSources);
+ }
+
+ /**
+ * <p>
+ * Returns the identifier of the module.
+ * </p>
+ * @return the identifier of the module.
+ **/
+ public String getId()
+ {
+ return m_id;
+ }
+
+ /**
+ * <p>
+ * Returns the attribute set associated with this module. Attributes
+ * are represented as an array of <tt>Object</tt> arrays, i.e.,
+ * <tt>Object[][]</tt>. Each element in the attribute array is
+ * two-element <tt>Object</tt> array, where <tt>Module.KEY_IDX</tt>
+ * is the index to the attribute key and <tt>Module.VALUE_IDX</tt>
+ * is the index to the attribute value. The returned array is a
+ * copy and may be freely modified.
+ * </p>
+ * @return the attribute set associated with this module.
+ **/
+ public synchronized Object[][] getAttributes()
+ {
+ Set s = m_attributeMap.entrySet();
+ Object[][] attributes = new Object[s.size()][];
+ Iterator iter = s.iterator();
+ for (int i = 0; iter.hasNext(); i++)
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ attributes[i] = new Object[] { entry.getKey(), entry.getValue() };
+ }
+ return attributes;
+ }
+
+ /**
+ * <p>
+ * Returns the attribute value associated with the specified key.
+ * </p>
+ * @param key the key of the attribute whose value is to be retrieved.
+ * @return the attribute's value or <tt>null</tt>.
+ **/
+ public synchronized Object getAttribute(String key)
+ {
+ return m_attributeMap.get(key);
+ }
+
+ /**
+ * <p>
+ * Sets the attribute value associated with the specified key. The
+ * attribute will be added if it does not currently exist.
+ * </p>
+ * @param key the key of the attribute whose value is to be set.
+ * @param value the new value to be associated with the attribute key.
+ **/
+ public synchronized void setAttribute(String key, Object value)
+ {
+ m_attributeMap.put(key, value);
+ }
+
+ /**
+ * <p>
+ * Returns the array of <tt>ResourceSource</tt>s associated with
+ * the module. The returned array is not a copy and therefore should
+ * not be modified.
+ * </p>
+ * @return the array of <tt>ResourceSource</tt>s associated with
+ * the module.
+ * @see org.apache.osgi.moduleloader.ResourceSource
+ **/
+ public ResourceSource[] getResourceSources()
+ {
+ return m_resSources;
+ }
+
+ /**
+ * <p>
+ * Returns the array of <tt>LibrarySource</tt>s associated with
+ * the module. The returned array is not a copy and therefore should
+ * not be modified.
+ * </p>
+ * @return the array of <tt>LibrarySource</tt>s associated with
+ * the module.
+ * @see org.apache.osgi.moduleloader.LibrarySource
+ **/
+ public LibrarySource[] getLibrarySources()
+ {
+ return m_libSources;
+ }
+
+ /**
+ * <p>
+ * Returns the <tt>ModuleClassLoader</tt> associated with this module.
+ * If a security manager is installed, then this method uses a privileged
+ * action to avoid a security exception being thrown to the caller.
+ * </p>
+ * @return the <tt>ModuleClassLoader</tt> associated with this module.
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+ **/
+ public synchronized ModuleClassLoader getClassLoader()
+ {
+ if (m_loader == null)
+ {
+ if (System.getSecurityManager() != null)
+ {
+ m_loader = (ModuleClassLoader) AccessController.doPrivileged(
+ new GetClassLoaderPrivileged(m_mgr, this, m_useParentSource));
+ }
+ else
+ {
+ m_loader = new ModuleClassLoader(m_mgr, this, m_useParentSource);
+ }
+ }
+
+ return m_loader;
+ }
+
+ /**
+ * <p>
+ * Returns the module's identifier.
+ * </p>
+ * @return the module's identifier.
+ **/
+ public String toString()
+ {
+ return m_id;
+ }
+
+ /**
+ * <p>
+ * Resets the module by throwing away its associated class loader and
+ * re-initializing its attributes, resource sources, and library sources
+ * with the specified values.
+ * </p>
+ * @param attributes the new attributes to be associated with the module.
+ * @param resSources the new resource sources to be associated with the module.
+ * @param libSources the new library sources to be associated with the module.
+ * @see org.apache.osgi.moduleloader.ResourceSource
+ * @see org.apache.osgi.moduleloader.LibrarySource
+ **/
+ protected synchronized void reset(
+ Object[][] attributes, ResourceSource[] resSources,
+ LibrarySource[] libSources)
+ {
+ // Throw away class loader.
+ m_loader = null;
+ // Clear attribute map.
+ m_attributeMap.clear();
+ // Close all sources.
+ dispose();
+ // Re-initialize.
+ initialize(attributes, resSources, libSources);
+ }
+
+ /**
+ * <p>
+ * Disposes the module by closing all resource and library sources.
+ * </p>
+ **/
+ protected synchronized void dispose()
+ {
+ // Close sources.
+ for (int i = 0; (m_resSources != null) && (i < m_resSources.length); i++)
+ {
+ m_resSources[i].close();
+ }
+ for (int i = 0; (m_libSources != null) && (i < m_libSources.length); i++)
+ {
+ m_libSources[i].close();
+ }
+ }
+
+ /**
+ * <p>
+ * Initializes the module by copying the specified attribute array into
+ * a map and opening all resource and library sources.
+ * </p>
+ * @param attributes the attributes to be put into a map.
+ * @param resSources the resource sources to be opened.
+ * @param libSources the library sources to be opened.
+ * @see org.apache.osgi.moduleloader.ResourceSource
+ * @see org.apache.osgi.moduleloader.LibrarySource
+ **/
+ private void initialize(
+ Object[][] attributes, ResourceSource[] resSources, LibrarySource[] libSources)
+ {
+ for (int i = 0; (attributes != null) && (i < attributes.length); i++)
+ {
+ m_attributeMap.put(attributes[i][KEY_IDX], attributes[i][VALUE_IDX]);
+ }
+
+ m_resSources = resSources;
+ m_libSources = libSources;
+
+ // Open sources.
+ for (int i = 0; (m_resSources != null) && (i < m_resSources.length); i++)
+ {
+ m_resSources[i].open();
+ }
+ for (int i = 0; (m_libSources != null) && (i < m_libSources.length); i++)
+ {
+ m_libSources[i].open();
+ }
+ }
+
+ private static class GetClassLoaderPrivileged implements PrivilegedAction
+ {
+ private ModuleManager m_mgr = null;
+ private Module m_module = null;
+ private boolean m_useParentSource = false;
+
+ public GetClassLoaderPrivileged(ModuleManager mgr, Module module, boolean useParentSource)
+ {
+ m_mgr = mgr;
+ m_module = module;
+ m_useParentSource = useParentSource;
+ }
+
+ public Object run()
+ {
+ return new ModuleClassLoader(m_mgr, m_module, m_useParentSource);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleClassLoader.java b/src/org/apache/osgi/moduleloader/ModuleClassLoader.java
new file mode 100644
index 0000000..e2c9b96
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleClassLoader.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.CodeSource;
+import java.security.SecureClassLoader;
+import java.security.cert.Certificate;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * <p>
+ * Each module that is managed by a <tt>ModuleManager</tt> has a
+ * <tt>ModuleClassLoader</tt> associated with it. The <tt>ModuleClassLoader</tt>
+ * is responsible for loading all classes, resources, and native libraries
+ * for its module. The <tt>ModuleClassLoader</tt> of a module
+ * is accessed using the <tt>Module.getClassLoader()</tt> method. The
+ * <tt>ModuleClassLoader</tt> uses its module's
+ * <a href="ResourceSource.html"><tt>ResourceSource</tt></a>s
+ * and <a href="LibrarySource.html"><tt>LibrarySource</tt></a>s
+ * to perform its function.
+ * </p>
+ * <p>
+ * When loading a class or resource, the <tt>ModuleClassLoader</tt> does
+ * not immediately search its module's <tt>ResourceSource</tt>s, instead
+ * it first delegates the request to the
+ * <a href="SearchPolicy.html"><tt>SearchPolicy</tt></a> of the
+ * <tt>ModuleManager</tt>; this allows applications to inject specific
+ * class/resource loading policies. When the <tt>ModuleClassLoader</tt> delegates
+ * to the search policy, the search policy uses application-specific behavior
+ * to typically service the request from the <tt>ResourceSource</tt>s of
+ * other modules. If the search policy returns a result, then this result is
+ * returned immediately by the <tt>ModuleClassLoader</tt>; otherwise, it searches
+ * the <tt>ResourceSource</tt>s its module in an attempt to satisfy the
+ * original request.
+ * </p>
+ * <p>
+ * <b><i>Important:</i></b> The search policy <i>searches</i> modules in
+ * some application-specific manner in order to find a class or resource.
+ * This <i>search</i> is instigated, either directly or indirectly, by calls
+ * to <tt>ModuleClassLoader.loadClass()</tt> and <tt>ModuleClassLoader.getResource()</tt>,
+ * respectively. In order for the search policy to load a class or resource,
+ * it must <b>not</b> use <tt>ModuleClassLoader.loadClass()</tt> or
+ * <tt>ModuleClassLoader.getResource()</tt> again, because this would result
+ * in an infinite loop. Instead, the <tt>ModuleClassLoader</tt> offers the
+ * the methods <tt>ModuleClassLoader.loadClassFromModule()</tt> and
+ * <tt>ModuleClassLoader.getResourceFromModule()</tt> to search a given module
+ * and to avoid an infinite loop. As an example, consider the following
+ * snippet of code that implements an "exhaustive" search policy:
+ * </p>
+ * <pre>
+ * ...
+ * public Class findClass(Module module, String name)
+ * {
+ * Module[] modules = m_mgr.getModules();
+ * for (int i = 0; i < modules.length; i++)
+ * {
+ * try {
+ * Class clazz = modules[i].getClassLoader().loadClassFromModule(name);
+ * if (clazz != null)
+ * {
+ * return clazz;
+ * }
+ * } catch (Throwable th) {
+ * }
+ * }
+ *
+ * return null;
+ * }
+ * ...
+ * </pre>
+ * <p>
+ * In the above code, the search policy "exhaustively" searches every module in the
+ * <tt>ModuleManager</tt> to find the requested resource. Note that this policy
+ * will also search the module that originated the request, which is not totally
+ * necessary since returning <tt>null</tt> will cause the <tt>ModuleClassLoader</tt>
+ * to search the originating module's <tt>ResourceSource</tt>s.
+ * </p>
+**/
+public class ModuleClassLoader extends SecureClassLoader
+{
+ private ModuleManager m_mgr = null;
+ private Module m_module = null;
+ private boolean m_useParentSource = false;
+
+ /**
+ * <p>
+ * Constructs an instance using the specified <tt>ModuleManager</tt>, for
+ * the specified <tt>Module</tt>. This constructor is protected so that
+ * it cannot be created publicly.
+ * </p>
+ * @param mgr the <tt>ModuleManager</tt> of the <tt>Module</tt>.
+ * @param module the <tt>Module</tt> instance associated with the class loader.
+ **/
+ protected ModuleClassLoader(ModuleManager mgr, Module module, boolean useParentSource)
+ {
+ super(ModuleClassLoader.class.getClassLoader());
+ m_mgr = mgr;
+ m_module = module;
+ m_useParentSource = useParentSource;
+ }
+
+ /**
+ * <p>
+ * This method is nearly an exact copy of the ClassLoader.loadClass()
+ * method. The main difference is that it delegates to its associated
+ * <tt>ModuleManager</tt>'s search policy before calling the
+ * <tt>ClassLoader.findClass()</tt> method. Additionally, the synchronized
+ * modifier was removed from the superclass method; this change was necessary
+ * because ClassLoader class assumes a tree of class loaders, but the class
+ * loading structure in the <tt>ModuleManager</tt> might actually be a graph
+ * of class loaders; thus, it was necessary to loosen the concurrency locking
+ * to allow for cycles.
+ * </p>
+ * @param name the class to be loaded.
+ * @param resolve flag indicating whether the class should be resolved or not.
+ * @return the loaded class.
+ * @throws java.lang.ClassNotFoundException if the class could not be loaded.
+ **/
+ protected Class loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ // Make sure the class was not already loaded.
+ Class c = findLoadedClass(name);
+ // Ask the search policy for the clas before consulting the module.
+ c = m_mgr.getSearchPolicy().findClassBeforeModule(getParent(), m_module, name);
+ // If the search policy didn't find it, then consult the module.
+ if (c == null)
+ {
+ c = findClass(name);
+ }
+ // If the module didn't find it, then consult the search policy
+ // one more time.
+ if (c == null)
+ {
+ c = m_mgr.getSearchPolicy().findClassAfterModule(getParent(), m_module, name);
+ }
+ // If still not found, then throw an exception.
+ if (c == null)
+ {
+ throw new ClassNotFoundException(name);
+ }
+ // Otherwise resolve the class.
+ if (resolve)
+ {
+ resolveClass(c);
+ }
+ return c;
+ }
+
+ /**
+ * <p>
+ * This method overriden from from <tt>ClassLoader</tt>.
+ * It is implemented such that it loads classes from the set of
+ * <tt>ResourceSource</tt>s from its associated module.
+ * </p>
+ * @param name the name of the resource to load.
+ * @return the loaded <tt>Class</tt> object.
+ * @throws java.lang.ClassNotFoundException if the class could not be loaded.
+ **/
+ protected Class findClass(String name) throws ClassNotFoundException
+ {
+ Class clazz = findLoadedClass(name);
+
+ // If the parent is used as a source, try to
+ // load the class from it.
+ // TODO: This is really a hack and should be generalized somehow.
+ if (m_useParentSource)
+ {
+ clazz = (getParent() == null) ? null : getParent().loadClass(name);
+ }
+
+ // Otherwise search for class in resource sources.
+ if (clazz == null)
+ {
+ String actual = name.replace('.', '/') + ".class";
+ ResourceSource[] sources = m_module.getResourceSources();
+ for (int i = 0;
+ (clazz == null) && (sources != null) && (i < sources.length);
+ i++)
+ {
+ byte[] bytes = sources[i].getBytes(actual);
+ if (bytes != null)
+ {
+ // We need to try to define a Package object for the class
+ // before we call defineClass(). Get the package name and
+ // see if we have already created the package.
+ String pkgName = Util.getClassPackage(name);
+ if (pkgName.length() > 0)
+ {
+ if (getPackage(pkgName) == null)
+ {
+ Object[] params =
+ m_mgr.getSearchPolicy().definePackage(m_module, pkgName);
+ if (params != null)
+ {
+ definePackage(
+ pkgName,
+ (String) params[0],
+ (String) params[1],
+ (String) params[2],
+ (String) params[3],
+ (String) params[4],
+ (String) params[5],
+ null);
+ }
+ }
+ }
+
+ // Get the code source URL for this class. For concurrency
+ // purposes, we are performing this call outside of the
+ // synchronized block below since we call out to application
+ // code, which might in turn need to call back into the
+ // module loader code. Because of this, it is better to
+ // not be holding any locks before making the call.
+ URL url = m_mgr.getURLPolicy().createCodeSourceURL(
+ m_mgr, m_module);
+
+ // If we have a valid code source URL, then use it to
+ // define the class for security purposes, otherwise
+ // define the class without a code source.
+ if (url != null)
+ {
+ CodeSource cs = new CodeSource(url, (Certificate[]) null);
+ clazz = defineClass(name, bytes, 0, bytes.length, cs);
+ }
+ else
+ {
+ clazz = defineClass(name, bytes, 0, bytes.length);
+ }
+ }
+ }
+ }
+
+ if (clazz != null)
+ {
+ return clazz;
+ }
+
+ return null;
+ }
+
+ /**
+ * <p>
+ * This method is used by <tt>SearchPolicy</tt> instances when they want
+ * to load a class from a module. The search policy is initially invoked when
+ * <tt>ModuleClassLoader.loadClass()</tt> delegates a class loading
+ * request to it. In general, the ultimate goal of the search policy is to
+ * return a class from another module if possible. Unfortunately, if a search
+ * policy tries to directly load a class from another module's class loader, an
+ * infinite loop will result because the module's class loader will delegate the
+ * request back to the search policy. To avoid this situation, search policies
+ * must use this method when trying to load a class from a module.
+ * </p>
+ * @param name the name of the class to load.
+ * @return the loaded class or <tt>null</tt>.
+ **/
+ public Class loadClassFromModule(String name)
+ {
+ try
+ {
+ return findClass(name);
+ } catch (Throwable th) {
+ // Not much we can do.
+// TODO: Do something with this error message.
+// System.err.println("ModuleClassLoader: " + th.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * <p>
+ * This method is nearly an exact copy of the ClassLoader.getResource()
+ * method. The main difference is that it delegates to its associated
+ * <tt>ModuleManager</tt>'s search policy before calling the
+ * <tt>ClassLoader.findResource()</tt> method.
+ * </p>
+ * @param name the class to be loaded.
+ * @return a URL to the resource or <tt>null</tt> if the resource was not found.
+ **/
+ public URL getResource(String name)
+ {
+ URL url = null;
+
+ // Ask the search policy for the resource.
+ if (m_mgr.getSearchPolicy() != null)
+ {
+ try
+ {
+ url = m_mgr.getSearchPolicy().findResource(getParent(), m_module, name);
+ }
+ catch (ResourceNotFoundException ex)
+ {
+ // We return null here because if SearchPolicy.findResource()
+ // throws an exception we interpret that to mean that the
+ // search should be stopped.
+ return null;
+ }
+ }
+
+ // If not found, then search locally.
+ if (url == null)
+ {
+ url = findResource(name);
+ }
+
+ return url;
+ }
+
+ /**
+ * <p>
+ * This method overriden from from <tt>ClassLoader</tt>.
+ * It is implemented such that it loads resources from the set of
+ * <tt>ResourceSource</tt>s from its associated module.
+ * </p>
+ * @param name the name of the resource to load.
+ * @return the <tt>URL</tt> associated with the resource or <tt>null</tt>.
+ **/
+ protected URL findResource(String name)
+ {
+ URL url = null;
+
+ // If the parent is used as a source, try to
+ // load the class from it.
+ if (m_useParentSource)
+ {
+ url = (getParent() == null) ? null : getParent().getResource(name);
+ }
+
+ // Try to load the resource from the module's resource
+ // sources.
+ if (url == null)
+ {
+ // Remove leading slash, if present.
+ if (name.startsWith("/"))
+ {
+ name = name.substring(1);
+ }
+
+ ResourceSource[] sources = m_module.getResourceSources();
+ for (int i = 0;
+ (url == null) && (sources != null) && (i < sources.length);
+ i++)
+ {
+ if (sources[i].hasResource(name))
+ {
+ url = m_mgr.getURLPolicy().createResourceURL(m_mgr, m_module, i, name);
+ }
+ }
+ }
+
+ return url;
+ }
+
+ /**
+ * <p>
+ * This method is used by <tt>SearchPolicy</tt> instances when they want
+ * to load a resource from a module. The search policy is initially invoked when
+ * <tt>ModuleClassLoader.loadClass()</tt> delegates a resource loading
+ * request to it. In general, the ultimate goal of the search policy is to
+ * return a resource from another module if possible. Unfortunately, if a search
+ * policy tries to directly load a resource from another module's class loader, an
+ * infinite loop will result because the module's class loader will delegate the
+ * request back to the search policy. To avoid this situation, search policies
+ * must use this method when trying to load a resource from a module.
+ * </p>
+ * @param name the name of the resource to load.
+ * @return a URL to the resource or <tt>null</tt>.
+ **/
+ public URL getResourceFromModule(String name)
+ {
+ try
+ {
+ return findResource(name);
+ }
+ catch (Throwable th)
+ {
+ // Ignore and just return null.
+ }
+ return null;
+ }
+
+ protected Enumeration findResources(String name)
+ {
+ Vector v = new Vector();
+ // If the parent is used as a source, try to
+ // load the class from it.
+ if (m_useParentSource)
+ {
+ try
+ {
+ Enumeration e = (getParent() == null)
+ ? null : getParent().getResources(name);
+ while ((e != null) && e.hasMoreElements())
+ {
+ v.addElement(e.nextElement());
+ }
+ }
+ catch (IOException ex)
+ {
+ // What can we do?
+ }
+ }
+
+ // Remove leading slash, if present.
+ if (name.startsWith("/"))
+ {
+ name = name.substring(1);
+ }
+
+ // Try to load the resource from the module's resource
+ // sources.
+
+ ResourceSource[] sources = m_module.getResourceSources();
+ for (int i = 0; (sources != null) && (i < sources.length); i++)
+ {
+ if (sources[i].hasResource(name))
+ {
+ v.addElement(m_mgr.getURLPolicy().createResourceURL(m_mgr, m_module, i, name));
+ }
+ }
+
+ return v.elements();
+ }
+
+ /**
+ * <p>
+ * This method overriden from from <tt>ClassLoader</tt>. It maps a library
+ * name to a library path by consulting the <tt>LibrarySource</tt>s of the
+ * class loader's module.
+ * </p>
+ * @param name the name of the library to find.
+ * @return the file system path of library or <tt>null</tt>
+ **/
+ protected String findLibrary(String name)
+ {
+ // Remove leading slash, if present.
+ if (name.startsWith("/"))
+ {
+ name = name.substring(1);
+ }
+
+ LibrarySource[] sources = m_module.getLibrarySources();
+ for (int i = 0;
+ (sources != null) && (i < sources.length);
+ i++)
+ {
+ String path = sources[i].getPath(name);
+ if (path != null)
+ {
+ return path;
+ }
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleEvent.java b/src/org/apache/osgi/moduleloader/ModuleEvent.java
new file mode 100644
index 0000000..ee878bc
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleEvent.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+import java.util.EventObject;
+
+/**
+ * <p>
+ * This is an event class that is used by the <tt>ModuleManager</tt> to
+ * indicate when modules are added, removed, or reset. To receive these
+ * events, a <tt>ModuleListener</tt> must be added to the <tt>ModuleManager</tt>
+ * instance.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ModuleManager
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleListener
+**/
+public class ModuleEvent extends EventObject
+{
+ private Module m_module = null;
+
+ /**
+ * <p>
+ * Constructs a module event with the specified <tt>ModuleManager</tt>
+ * as the event source and the specified module as the subject of
+ * the event.
+ * </p>
+ * @param mgr the source of the event.
+ * @param module the subject of the event.
+ **/
+ public ModuleEvent(ModuleManager mgr, Module module)
+ {
+ super(mgr);
+ m_module = module;
+ }
+
+ /**
+ * <p>
+ * Returns the module that is the subject of the event.
+ * </p>
+ * @return the module that is the subject of the event.
+ **/
+ public Module getModule()
+ {
+ return m_module;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleListener.java b/src/org/apache/osgi/moduleloader/ModuleListener.java
new file mode 100644
index 0000000..aae6b3c
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+import java.util.EventListener;
+
+/**
+ * <p>
+ * This interface is an event listener for <tt>ModuleEvent</tt> events.
+ * To receive events, an implementation of this listener must be added
+ * to the <tt>ModuleManager</tt> instance.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ModuleManager
+ * @see org.apache.osgi.moduleloader.ModuleEvent
+**/
+public interface ModuleListener extends EventListener
+{
+ /**
+ * <p>
+ * This method is called after a module is added to the
+ * <tt>ModuleManager</tt>.
+ * </p>
+ * @param event the event object containing the event details.
+ **/
+ public void moduleAdded(ModuleEvent event);
+
+ /**
+ * <p>
+ * This method is called after a module has been reset by the
+ * <tt>ModuleManager</tt>.
+ * </p>
+ * @param event the event object containing the event details.
+ **/
+ public void moduleReset(ModuleEvent event);
+
+ /**
+ * <p>
+ * This method is called after a module is remove from the
+ * <tt>ModuleManager</tt>.
+ * </p>
+ * @param event the event object containing the event details.
+ **/
+ public void moduleRemoved(ModuleEvent event);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleManager.java b/src/org/apache/osgi/moduleloader/ModuleManager.java
new file mode 100644
index 0000000..68a0cc2
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleManager.java
@@ -0,0 +1,524 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+import java.util.*;
+
+/**
+ * <p>
+ * The <tt>ModuleManager</tt> class is the core facility for defining a
+ * re-usable, policy-driven class loader for applications that require
+ * flexible class loading mechanisms. The <tt>ModuleManager</tt> is not
+ * class loader itself, but it supports the concept of a
+ * <a href="Module.html"><tt>Module</tt></a>,
+ * which is a unit of organization for application classes and resources.
+ * The <tt>ModuleManager</tt> has only a handful of methods that allow
+ * an application to add, remove, reset, and query modules; the intent
+ * is to place as few assumptions in the <tt>ModuleManager</tt> as possible.
+ * </p>
+ * <p>
+ * The idea is simple, allow the application to map itself into modules
+ * however it sees fit and let the <tt>ModuleManager</tt> assume the
+ * responsibility of managing the modules and loading classes and resources
+ * from them as necessary via <a href="ModuleClassLoader.html"><tt>ModuleClassLoader</tt></a>s
+ * that are associated with each module. In order to achieve this goal, though, the
+ * <tt>ModuleManager</tt> must make at least one assumption on behalf of
+ * the application. This assumption is that the loading of classes and resources
+ * from the available modules must happen using a search algorithm
+ * that is particular to the application itself. As a result of this assumption,
+ * the <tt>ModuleManager</tt> requires that the application provide a concrete
+ * implementation of the <a href="SearchPolicy.html"><tt>SearchPolicy</tt></a>
+ * interface.
+ * </p>
+ * <p>
+ * The search policy allows the <tt>ModuleLoader</tt> to let applications inject
+ * their own particular class loading policies, without dictating strict or
+ * constraining base assumptions. Of course, it is likely that many applications
+ * will use the same or very similar search policies. Because of this, another
+ * goal of the <tt>ModuleLoader</tt> approach is to foster a common library of
+ * search policies that applications are free to use or customize as they see
+ * fit. These common search policies are analagous to patterns, where each search
+ * policy is viewable as a <i>class loading pattern</i>. Some initial search
+ * policies included with the <tt>ModuleLoader</tt> are
+ * <a href="search/ExhaustiveSearchPolicy.html"><tt>ExhaustiveSearchPolicy</tt></a>,
+ * <a href="search/SelfContainedSearchPolicy.html"><tt>SelfContainedSearchPolicy</tt></a>, and
+ * <a href="search/ImportSearchPolicy.html"><tt>ImportSearchPolicy</tt></a>.
+ * </p>
+ * <p>
+ * Due to the fact that class loaders are tied to security and resource loading,
+ * the search policy alone is not sufficient for the <tt>ModuleLoader</tt> to
+ * perform its function. To fulfill these other purposes, the <tt>ModuleLoader</tt>
+ * introduces another policy interface, called the <a href="URLPolicy.html"><tt>URLPolicy</tt></a>.
+ * The <tt>URLPolicy</tt> allows the application to inject its particular policy
+ * for to purposes:
+ * </p>
+ * <ol>
+ * <li>Creating the <tt>URL</tt> associated with loading a resource, such as
+ * the <tt>URL</tt> returned from a call to <tt>Class.getResource()</tt>.
+ * </li>
+ * <li>Creating the <tt>URL</tt> that will be associated with a class's
+ * <tt>CodeSource</tt> when defining the class for purposes of security
+ * and assigning permissions.
+ * </li>
+ * </ol>
+ * <p>
+ * The <tt>ModuleLoader</tt> defines a default <tt>URLPolicy</tt>, called
+ * <a href="DefaultURLPolicy.html"><tt>DefaultURLPolicy</tt></a>, that provides
+ * a simple <tt>URLStreamHandler</tt> for accessing resources inside of modules
+ * and that returns <tt>null</tt> for the <tt>CodeSource</tt> <tt>URL</tt>.
+ * Applications only need to supply their own <tt>URLPolicy</tt> if the default
+ * one does not provide the appropriate behavior.
+ * </p>
+ * <p>
+ * It is possible for an application to create multiple instances of the
+ * <tt>ModuleManager</tt> within a single JVM, but it is not possible to
+ * share modules across multiple <tt>ModuleManager</tt>s. A given <tt>ModuleManager</tt>
+ * can only have one <tt>SelectionPolicy</tt> and one <tt>URLPolicy</tt>.
+ * </p>
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+ * @see org.apache.osgi.moduleloader.SearchPolicy
+ * @see org.apache.osgi.moduleloader.URLPolicy
+ * @see org.apache.osgi.moduleloader.DefaultURLPolicy
+**/
+public class ModuleManager
+{
+ private List m_moduleList = new ArrayList();
+ private Map m_moduleMap = new HashMap();
+ private SearchPolicy m_searchPolicy = null;
+ private URLPolicy m_urlPolicy = null;
+ private ModuleListener[] m_listeners = null;
+ private static final ModuleListener[] m_noListeners = new ModuleListener[0];
+
+ /**
+ * <p>
+ * Constructs a <tt>ModuleManager</tt> instance using the specified
+ * search policy and the default <tt>URL</tt> policy.
+ * </p>
+ * @param searchPolicy the search policy that the instance should use.
+ * @see org.apache.osgi.moduleloader.SearchPolicy
+ **/
+ public ModuleManager(SearchPolicy searchPolicy)
+ {
+ this(searchPolicy, null);
+ }
+
+ /**
+ * <p>
+ * Constructs a <tt>ModuleManager</tt> instance using the specified
+ * search policy and the specified <tt>URL</tt> policy.
+ * </p>
+ * @param searchPolicy the search policy that the instance should use.
+ * @param urlPolicy the <tt>URL</tt> policy that the instance should use.
+ * @see org.apache.osgi.moduleloader.SearchPolicy
+ * @see org.apache.osgi.moduleloader.URLPolicy
+ **/
+ public ModuleManager(SearchPolicy searchPolicy, URLPolicy urlPolicy)
+ {
+ m_listeners = m_noListeners;
+ m_searchPolicy = searchPolicy;
+ m_searchPolicy.setModuleManager(this);
+
+ if (urlPolicy == null)
+ {
+ m_urlPolicy = new DefaultURLPolicy();
+ }
+ else
+ {
+ m_urlPolicy = urlPolicy;
+ }
+ }
+
+ /**
+ * <p>
+ * Returns the <tt>URL</tt> policy used by this instance.
+ * </p>
+ * @return the <tt>URL</tt> policy used by this instance.
+ * @see org.apache.osgi.moduleloader.URLPolicy
+ **/
+ public URLPolicy getURLPolicy()
+ {
+ return m_urlPolicy;
+ }
+
+ /**
+ * <p>
+ * Returns the search policy used by this instance.
+ * </p>
+ * @return the search policy used by this instance.
+ * @see org.apache.osgi.moduleloader.SearchPolicy
+ **/
+ public SearchPolicy getSearchPolicy()
+ {
+ return m_searchPolicy;
+ }
+
+ /**
+ * <p>
+ * Returns an array of all modules being managed by the
+ * <tt>ModuleManager</tt> instance. The array contains a snapshot of
+ * all modules in the <tt>ModuleManager</tt> at the time when this
+ * method was called.
+ * </p>
+ * @return an array of all modules being managed by the <tt>ModuleManager</tt>
+ * instance.
+ * @see org.apache.osgi.moduleloader.Module
+ **/
+ public synchronized Module[] getModules()
+ {
+ Module[] modules = new Module[m_moduleList.size()];
+ return (Module[]) m_moduleList.toArray(modules);
+ }
+
+ /**
+ * <p>
+ * Returns a module associated with the specified identifier.
+ * </p>
+ * @param id the identifier for the module to be retrieved.
+ * @return the module associated with the identifier or <tt>null</tt>.
+ * @see org.apache.osgi.moduleloader.Module
+ **/
+ public synchronized Module getModule(String id)
+ {
+ return (Module) m_moduleMap.get(id);
+ }
+
+ /**
+ * <p>
+ * Adds a module to the module manager. The module will have the specified
+ * unique identifier, with the associated attributes, resource sources, and
+ * library sources. If the identifier is not unique, then an exception is
+ * thrown.
+ * </p>
+ * @param id the unique identifier of the new module.
+ * @param attributes an array of key-value attribute pairs to
+ * associate with the module.
+ * @param resSources an array of <tt>ResourceSource</tt>s to associate
+ * with the module.
+ * @param libSources an array of <tt>LibrarySource</tt>s to associate
+ * with the module.
+ * @return the newly created module.
+ * @throws java.lang.IllegalArgumentException if the module identifier
+ * is not unique.
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ResourceSource
+ * @see org.apache.osgi.moduleloader.LibrarySource
+ **/
+ public Module addModule(String id, Object[][] attributes,
+ ResourceSource[] resSources, LibrarySource[] libSources)
+ {
+ return addModule(id, attributes, resSources, libSources, false);
+ }
+
+ public Module addModule(String id, Object[][] attributes,
+ ResourceSource[] resSources, LibrarySource[] libSources,
+ boolean useParentSource)
+ {
+ Module module = null;
+
+ // Use a synchronized block instead of synchronizing the
+ // method, so we can fire our event outside of the block.
+ synchronized (this)
+ {
+ if (m_moduleMap.get(id) == null)
+ {
+ module = new Module(this, id, attributes, resSources, libSources, useParentSource);
+ m_moduleList.add(module);
+ m_moduleMap.put(id, module);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Module ID must be unique.");
+ }
+ }
+
+ // Fire event here instead of inside synchronized block.
+ fireModuleAdded(module);
+
+ return module;
+ }
+
+ /**
+ * <p>
+ * Resets a given module. In resetting a module, the module's associated
+ * class loader is thrown away; it is the application's responsibility to
+ * determine when and how that application code stops using classes (and
+ * subsequent instances) from the class loader of the reset module.
+ * This method allows the associated elements of the module (i.e.,
+ * attributes, resource sources, and library sources) to be changed also;
+ * if these elements have not changed then they simply need to be passed
+ * back in from the existing module. This method is useful in situations
+ * where the underlying module needs to be changed at run time, such as
+ * might be necessary if a module was updated.
+ * </p>
+ * <p>
+ * The same effect could be achieved by first removing and then re-adding
+ * a module, but with one subtle different. By removing and then re-adding
+ * a module, a new module is created and, thus, all existing references
+ * become invalid. By explicitly having this method, the <tt>ModuleManager</tt>
+ * maintains the integrity of the module reference, which is more intuitive
+ * in the case where an updated module is intended to be the same module,
+ * only updated.
+ * </p>
+ * @param module the module reset.
+ * @param attributes an array of key-value attribute pairs to
+ * associate with the module.
+ * @param resSources an array of <tt>ResourceSource</tt>s to associate
+ * with the module.
+ * @param libSources an array of <tt>LibrarySource</tt>s to associate
+ * with the module.
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ResourceSource
+ * @see org.apache.osgi.moduleloader.LibrarySource
+ **/
+ public void resetModule(
+ Module module, Object[][] attributes,
+ ResourceSource[] resSources, LibrarySource[] libSources)
+ {
+ // Use a synchronized block instead of synchronizing the
+ // method, so we can fire our event outside of the block.
+ synchronized (this)
+ {
+ module = (Module) m_moduleMap.get(module.getId());
+ if (module != null)
+ {
+ module.reset(attributes, resSources, libSources);
+ }
+ else
+ {
+ // Don't fire event.
+ return;
+ }
+ }
+
+ // Fire event here instead of inside synchronized block.
+ fireModuleReset(module);
+ }
+
+ /**
+ * <p>
+ * Removes the specified module from the <tt>ModuleManager</tt>. Removing
+ * a module only removed the module from the <tt>ModuleManager</tt>. It is
+ * the application's responsibility to determine when and how application code
+ * stop using classes (and subsequent instances) that were loaded from
+ * the class loader of the removed module.
+ * </p>
+ * @param module the module to remove.
+ **/
+ public void removeModule(Module module)
+ {
+ // Use a synchronized block instead of synchronizing the
+ // method, so we can fire our event outside of the block.
+ synchronized (this)
+ {
+ if (m_moduleMap.get(module.getId()) != null)
+ {
+ // Remove from data structures.
+ m_moduleList.remove(module);
+ m_moduleMap.remove(module.getId());
+
+ // Dispose of the module.
+ module.dispose();
+ }
+ else
+ {
+ // Don't fire event.
+ return;
+ }
+ }
+
+ // Fire event here instead of inside synchronized block.
+ fireModuleRemoved(module);
+ }
+
+ /**
+ * <p>
+ * Adds a listener to the <tt>ModuleManager</tt> to listen for
+ * module added, reset, and removed events.
+ * </p>
+ * @param l the <tt>ModuleListener</tt> to add.
+ **/
+ public void addModuleListener(ModuleListener l)
+ {
+ // Verify listener.
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener is null");
+ }
+
+ // Use the m_noListeners object as a lock.
+ synchronized (m_noListeners)
+ {
+ // If we have no listeners, then just add the new listener.
+ if (m_listeners == m_noListeners)
+ {
+ m_listeners = new ModuleListener[] { l };
+ }
+ // Otherwise, we need to do some array copying.
+ // Notice, the old array is always valid, so if
+ // the dispatch thread is in the middle of a dispatch,
+ // then it has a reference to the old listener array
+ // and is not affected by the new value.
+ else
+ {
+ ModuleListener[] newList = new ModuleListener[m_listeners.length + 1];
+ System.arraycopy(m_listeners, 0, newList, 0, m_listeners.length);
+ newList[m_listeners.length] = l;
+ m_listeners = newList;
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Removes a listener from the <tt>ModuleManager</tt>.
+ * </p>
+ * @param l the <tt>ModuleListener</tt> to remove.
+ **/
+ public void removeModuleListener(ModuleListener l)
+ {
+ // Verify listener.
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener is null");
+ }
+
+ // Use the m_noListeners object as a lock.
+ synchronized (m_noListeners)
+ {
+ // Try to find the instance in our list.
+ int idx = -1;
+ for (int i = 0; i < m_listeners.length; i++)
+ {
+ if (m_listeners[i].equals(l))
+ {
+ idx = i;
+ break;
+ }
+ }
+
+ // If we have the instance, then remove it.
+ if (idx >= 0)
+ {
+ // If this is the last listener, then point to empty list.
+ if (m_listeners.length == 1)
+ {
+ m_listeners = m_noListeners;
+ }
+ // Otherwise, we need to do some array copying.
+ // Notice, the old array is always valid, so if
+ // the dispatch thread is in the middle of a dispatch,
+ // then it has a reference to the old listener array
+ // and is not affected by the new value.
+ else
+ {
+ ModuleListener[] newList = new ModuleListener[m_listeners.length - 1];
+ System.arraycopy(m_listeners, 0, newList, 0, idx);
+ if (idx < newList.length)
+ {
+ System.arraycopy(m_listeners, idx + 1, newList, idx,
+ newList.length - idx);
+ }
+ m_listeners = newList;
+ }
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Fires an event indicating that the specified module was added
+ * to the <tt>ModuleManager</tt>.
+ * </p>
+ * @param module the module that was added.
+ **/
+ protected void fireModuleAdded(Module module)
+ {
+ // Event holder.
+ ModuleEvent event = null;
+
+ // Get a copy of the listener array, which is guaranteed
+ // to not be null.
+ ModuleListener[] listeners = m_listeners;
+
+ // Loop through listeners and fire events.
+ for (int i = 0; i < listeners.length; i++)
+ {
+ // Lazily create event.
+ if (event == null)
+ {
+ event = new ModuleEvent(this, module);
+ }
+ listeners[i].moduleAdded(event);
+ }
+ }
+
+ /**
+ * <p>
+ * Fires an event indicating that the specified module was reset.
+ * </p>
+ * @param module the module that was reset.
+ **/
+ protected void fireModuleReset(Module module)
+ {
+ // Event holder.
+ ModuleEvent event = null;
+
+ // Get a copy of the listener array, which is guaranteed
+ // to not be null.
+ ModuleListener[] listeners = m_listeners;
+
+ // Loop through listeners and fire events.
+ for (int i = 0; i < listeners.length; i++)
+ {
+ // Lazily create event.
+ if (event == null)
+ {
+ event = new ModuleEvent(this, module);
+ }
+ listeners[i].moduleReset(event);
+ }
+ }
+
+ /**
+ * <p>
+ * Fires an event indicating that the specified module was removed
+ * from the <tt>ModuleManager</tt>.
+ * </p>
+ * @param module the module that was removed.
+ **/
+ protected void fireModuleRemoved(Module module)
+ {
+ // Event holder.
+ ModuleEvent event = null;
+
+ // Get a copy of the listener array, which is guaranteed
+ // to not be null.
+ ModuleListener[] listeners = m_listeners;
+
+ // Loop through listeners and fire events.
+ for (int i = 0; i < listeners.length; i++)
+ {
+ // Lazily create event.
+ if (event == null)
+ {
+ event = new ModuleEvent(this, module);
+ }
+ listeners[i].moduleRemoved(event);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleURLConnection.java b/src/org/apache/osgi/moduleloader/ModuleURLConnection.java
new file mode 100644
index 0000000..92cacf2
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleURLConnection.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.Permission;
+
+class ModuleURLConnection extends URLConnection
+{
+ private ModuleManager m_mgr = null;
+ private int m_contentLength;
+ private long m_contentTime;
+ private String m_contentType;
+ private InputStream m_is;
+
+ public ModuleURLConnection(ModuleManager mgr, URL url)
+ {
+ super(url);
+ m_mgr = mgr;
+ }
+
+ public void connect() throws IOException
+ {
+ if (!connected)
+ {
+ // The URL is constructed like this:
+ // module://<module-id>/<source-idx>/<resource-path>
+ Module module = m_mgr.getModule(url.getHost());
+ if (module == null)
+ {
+ throw new IOException("Unable to find bundle's module.");
+ }
+
+ String resource = url.getFile();
+ if (resource == null)
+ {
+ throw new IOException("Unable to find resource: " + url.toString());
+ }
+ if (resource.startsWith("/"))
+ {
+ resource = resource.substring(1);
+ }
+ int rsIdx = -1;
+ try
+ {
+ rsIdx = Integer.parseInt(resource.substring(0, resource.indexOf("/")));
+ }
+ catch (NumberFormatException ex)
+ {
+ new IOException("Error parsing resource index.");
+ }
+ resource = resource.substring(resource.indexOf("/") + 1);
+
+ // Get the resource bytes from the resource source.
+ byte[] bytes = null;
+ ResourceSource[] resSources = module.getResourceSources();
+ if ((resSources != null) && (rsIdx < resSources.length))
+ {
+ if (resSources[rsIdx].hasResource(resource))
+ {
+ bytes = resSources[rsIdx].getBytes(resource);
+ }
+ }
+
+ if (bytes == null)
+ {
+ throw new IOException("Unable to find resource: " + url.toString());
+ }
+
+ m_is = new ByteArrayInputStream(bytes);
+ m_contentLength = bytes.length;
+ m_contentTime = 0L; // TODO: Change this.
+ m_contentType = URLConnection.guessContentTypeFromName(resource);
+ connected = true;
+ }
+ }
+
+ public InputStream getInputStream()
+ throws IOException
+ {
+ if (!connected)
+ {
+ connect();
+ }
+ return m_is;
+ }
+
+ public int getContentLength()
+ {
+ if (!connected)
+ {
+ try {
+ connect();
+ } catch(IOException ex) {
+ return -1;
+ }
+ }
+ return m_contentLength;
+ }
+
+ public long getLastModified()
+ {
+ if (!connected)
+ {
+ try {
+ connect();
+ } catch(IOException ex) {
+ return 0;
+ }
+ }
+ if (m_contentTime != -1L)
+ {
+ return m_contentTime;
+ }
+ else
+ {
+ return 0L;
+ }
+ }
+
+ public String getContentType()
+ {
+ if (!connected)
+ {
+ try {
+ connect();
+ } catch(IOException ex) {
+ return null;
+ }
+ }
+ return m_contentType;
+ }
+
+ public Permission getPermission()
+ {
+ // TODO: This should probably return a FilePermission
+ // to access the bundle JAR file, but we don't have the
+ // necessary information here to construct the absolute
+ // path of the JAR file...so it would take some
+ // re-arranging to get this to work.
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ModuleURLStreamHandler.java b/src/org/apache/osgi/moduleloader/ModuleURLStreamHandler.java
new file mode 100644
index 0000000..3cba43a
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ModuleURLStreamHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+import java.io.IOException;
+import java.net.*;
+
+class ModuleURLStreamHandler extends URLStreamHandler
+{
+ private ModuleManager m_mgr = null;
+
+ public ModuleURLStreamHandler(ModuleManager mgr)
+ {
+ m_mgr = mgr;
+ }
+
+ protected URLConnection openConnection(URL url) throws IOException
+ {
+ return new ModuleURLConnection(m_mgr, url);
+ }
+}
diff --git a/src/org/apache/osgi/moduleloader/ResourceNotFoundException.java b/src/org/apache/osgi/moduleloader/ResourceNotFoundException.java
new file mode 100644
index 0000000..c10b451
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ResourceNotFoundException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+public class ResourceNotFoundException extends Exception
+{
+ public ResourceNotFoundException(String msg)
+ {
+ super(msg);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/ResourceSource.java b/src/org/apache/osgi/moduleloader/ResourceSource.java
new file mode 100644
index 0000000..a710a60
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/ResourceSource.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+/**
+ * <p>
+ * This interface represents a source for obtaining resources for a
+ * given module via the module's class loader. A resource source is used
+ * for retrieving both classes and resources; at this level, classes are
+ * treated in an identical manner as an ordinary resource. Resource sources
+ * are completely arbitrary and implementations may load resources from a JAR
+ * file, the network, a database, or anywhere.
+ * </p>
+ * <p>
+ * All resource sources are initialized before first usage via a call
+ * to the <a href="#open()"><tt>ResourceSource.open()</tt></a> method and
+ * are also deinitialized via a call to
+ * <a href="#open()"><tt>ResourceSource.close()</tt></a>. Resource sources
+ * should be implemented such that they can be opened, closed, and then
+ * re-opened.
+ * </p>
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+**/
+public interface ResourceSource
+{
+ /**
+ * <p>
+ * This method initializes the resource source. It is called when
+ * the associated module is added to the <tt>ModuleManager</tt>. It
+ * is acceptable for implementations to ignore duplicate calls to this
+ * method if the resource source is already opened.
+ * </p>
+ **/
+ public void open();
+
+ /**
+ * <p>
+ * This method de-initializes the resource source. It is called when
+ * the associated module is removed from the <tt>ModuleManager</tt> or
+ * when the module is reset by the <tt>ModuleManager</tt>.
+ * </p>
+ **/
+ public void close();
+
+ /**
+ * <p>
+ * This method returns a boolean indicating whether the resource source
+ * contains the specified resource.
+ * </p>
+ * @param name the name of the resource whose existence is being checked.
+ * @param <tt>true</tt> if the resource source has the resource, <tt>false</tt>
+ * otherwise.
+ * @throws java.lang.IllegalStateException if the resource source has not
+ * been opened.
+ **/
+ public boolean hasResource(String name) throws IllegalStateException;
+
+ /**
+ * <p>
+ * This method returns a byte array of the specified resource's contents.
+ * </p>
+ * @param name the name of the resource to retrieve.
+ * @param a byte array of the resource's contents or <tt>null</tt>
+ * if the resource was not found.
+ * @throws java.lang.IllegalStateException if the resource source has not
+ * been opened.
+ **/
+ public byte[] getBytes(String name) throws IllegalStateException;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/SearchPolicy.java b/src/org/apache/osgi/moduleloader/SearchPolicy.java
new file mode 100644
index 0000000..fac3d23
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/SearchPolicy.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+import java.net.URL;
+
+/**
+ * <p>
+ * This interface represents a policy to define the most basic behavior
+ * of how classes, resources, and native libraries within a specific instance
+ * of <tt>ModuleManager</tt> are found. A <tt>ModuleManager</tt> manages a set of
+ * <tt>Module</tt>s, each of which is a potential source of classes, resources,
+ * and native libraries. The search policy makes it possible to consult these
+ * sources without hard-coding assumptions about application behavior
+ * or structure. Applicaitons inject their own specific class loading policy
+ * by creating a custom search policy or by selecting a pre-existing search
+ * policy that matches their needs.
+ * </p>
+ * <p>
+ * The search policy is used by <tt>ModuleClassLoader</tt>, of which, there
+ * is one per <tt>Module</tt> within a given <tt>ModuleManager</tt> instance.
+ * The search policy is consulted by the <tt>ModuleClassLoader</tt> whenever
+ * there is a request for a class, resource, or native library. The search
+ * policy will generally search other modules in an application-specific
+ * way in order to find the requested item; for example, an application may
+ * use a policy where module's may import from one another. If the search
+ * policy provides an answer, then the <tt>ModuleClassLoader</tt> will use
+ * this to answer the originating request.
+ * </p>
+ * <p>
+ * <b><i>Important:</i></b> The search policy <i>searches</i> modules in
+ * some application-specific manner in order to find a class or resource.
+ * This <i>search</i> is instigated, either directly or indirectly, by calls
+ * to <tt>ModuleClassLoader.loadClass()</tt> and <tt>ModuleClassLoader.getResource()</tt>,
+ * respectively. In order for the search policy to load a class or resource,
+ * it must <b>not</b> use <tt>ModuleClassLoader.loadClass()</tt> or
+ * <tt>ModuleClassLoader.getResource()</tt> again, because this would result
+ * in an infinite loop. Instead, the <tt>ModuleClassLoader</tt> offers the
+ * the methods <tt>ModuleClassLoader.loadClassFromModule()</tt> and
+ * <tt>ModuleClassLoader.getResourceFromModule()</tt> to search a given module
+ * and to avoid an infinite loop.
+ * </p>
+ * <pre>
+ * ...
+ * public Class findClass(Module module, String name)
+ * {
+ * Module[] modules = m_mgr.getModules();
+ * for (int i = 0; i < modules.length; i++)
+ * {
+ * try {
+ * Class clazz = modules[i].getClassLoader().loadClassFromModule(name);
+ * if (clazz != null)
+ * {
+ * return clazz;
+ * }
+ * } catch (Throwable th) {
+ * }
+ * }
+ *
+ * return null;
+ * }
+ * ...
+ * </pre>
+ * <p>
+ * In the above code, the search policy "exhaustively" searches every module in the
+ * <tt>ModuleManager</tt> to find the requested resource. Note that this policy
+ * will also search the module that originated the request, which is not totally
+ * necessary since returning <tt>null</tt> will cause the <tt>ModuleClassLoader</tt>
+ * to search the originating module's <tt>ResourceSource</tt>s.
+ * </p>
+**/
+public interface SearchPolicy
+{
+ /**
+ * <p>
+ * This method is called once by the <tt>ModuleManager</tt> to
+ * give the search policy instance a reference to its associated
+ * module manager. This method should be implemented such that
+ * it cannot be called twice; calling this method a second time
+ * should produce an illegal state exception.
+ * </p>
+ * @param mgr the module manager associated with this search policy.
+ * @throws java.lang.IllegalStateException if the method is called
+ * more than once.
+ **/
+ public void setModuleManager(ModuleManager mgr)
+ throws IllegalStateException;
+
+ /**
+ * <p>
+ * The <tt>ModuleClassLoader</tt> calls this method before performing
+ * the call to <tt>ClassLoader.defineClass()</tt> to give the search policy
+ * an opportunity to define the <tt>Package</tt> object for the specified
+ * package. The method should return an array of <tt>String</tt> values for
+ * each of the following: specTitle, specVersion, specVendor, implTitle,
+ * implVersion, and implVendor. See <tt>ClassLoader.definePackage()</tt>
+ * for more details. The returned array may contain <tt>null</tt>s, but
+ * the return array must have six elements.
+ * </p>
+ * @param module the module requesting a class from the package.
+ * @param pkgName the package name of the class being requested.
+ * @return an array containing values for creating the <tt>Package</tt>
+ * object for the specified package.
+ **/
+ public Object[] definePackage(Module module, String pkgName);
+
+ /**
+ * <p>
+ * When a module instigates a class load operation, this method is called
+ * to find the desired class for the instigating module. This method is
+ * called <b>before</b> searching the module's resource sources for the class.
+ * How the class is found is dependent upon the search policy implementation.
+ * </p>
+ * <p>
+ * This method may return <tt>null</tt> or throw an exception if the
+ * specified class is not found. Whether a specific search policy
+ * implementation should do one or the other depends on the details
+ * of the specific search policy. The <tt>ModuleClassLoader</tt>
+ * first delegates to this method, then to the local resources
+ * sources of the module, and then finally to then the
+ * <tt>SearchPolicy.findClassAfterModule()</tt> method. If this method
+ * returns null, then the search for the class will continue to these
+ * latter two steps. On the other hand, if this method returns a class
+ * or throws an exception, then the latter two steps will not be searched.
+ * </p>
+ * <p>
+ * <b>Important:</b> If the implementation of this method delegates
+ * the class loading to a <tt>ModuleClassLoader</tt> of another module,
+ * then it should <b>not</b> use the method <tt>ModuleClassLoader.loadClass()</tt>
+ * to load the class; it should use <tt>ModuleClassLoader.loadClassFromModule()</tt>
+ * instead. This is necessary to eliminate an infinite loop that would
+ * occur otherwise. Also, with respect to the <tt>ModuleLoader</tt> framework,
+ * this method will only be called by a single thread at a time and is only
+ * intended to be called by <tt>ModuleClassLoader.loadClass()</tt>.
+ * </p>
+ * @param parent the parent class loader of the delegating class loader.
+ * @param module the target module that is loading the class.
+ * @param name the name of the class being loaded.
+ * @return the class if found, <tt>null</tt> otherwise.
+ * @throws java.lang.ClassNotFoundException if the class could not be
+ * found and the entire search operation should fail.
+ **/
+ public Class findClassBeforeModule(ClassLoader parent, Module module, String name)
+ throws ClassNotFoundException;
+
+ /**
+ * <p>
+ * When a module instigates a class load operation, this method is called
+ * to find the desired class for the instigating module. This method is
+ * called <b>after</b> searching the module's resource sources for the class.
+ * How the class is found is dependent upon the search policy implementation.
+ * </p>
+ * <p>
+ * The <tt>ModuleClassLoader</tt> first delegates to the
+ * <tt>SearchPolicy.findClassBeforeModule() method, then to the local
+ * resources sources of the module, and then finally to this method.
+ * This method is the last attempt to find the class and if it fails
+ * (by either return <tt>null</tt> or throwing an exception), then the
+ * result of the entire class load will fail.
+ * </p>
+ * <p>
+ * <b>Important:</b> If the implementation of this method delegates
+ * the class loading to a <tt>ModuleClassLoader</tt> of another module,
+ * then it should <b>not</b> use the method <tt>ModuleClassLoader.loadClass()</tt>
+ * to load the class; it should use <tt>ModuleClassLoader.loadClassFromModule()</tt>
+ * instead. This is necessary to eliminate an infinite loop that would
+ * occur otherwise. Also, with respect to the <tt>ModuleLoader</tt> framework,
+ * this method will only be called by a single thread at a time and is only
+ * intended to be called by <tt>ModuleClassLoader.loadClass()</tt>.
+ * </p>
+ * @param parent the parent class loader of the delegating class loader.
+ * @param module the target module that is loading the class.
+ * @param name the name of the class being loaded.
+ * @return the class if found, <tt>null</tt> otherwise.
+ * @throws java.lang.ClassNotFoundException if the class could not be
+ * found and the entire search operation should fail.
+ **/
+ public Class findClassAfterModule(ClassLoader parent, Module module, String name)
+ throws ClassNotFoundException;
+
+ /**
+ * <p>
+ * This method tries to find the specified resource for the specified
+ * module. How the resource is found or whether it is actually retrieved
+ * from the specified module is dependent upon the implementation. The
+ * default <tt>ModuleClassLoader.getResource()</tt> method does not do
+ * any searching on its own.
+ * </p>
+ * <p>
+ * This method may return <tt>null</tt> or throw an exception if the
+ * specified resource is not found. Whether a specific search policy
+ * implementation should do one or the other depends on the details
+ * of the specific search policy. The <tt>ModuleClassLoader</tt>
+ * first delegates to this method and then to the local resource
+ * sources of the module. If this method returns null, then the local
+ * resource sources will be searched. On the other hand, if this method
+ * throws an exception, then the local resource sources will not be
+ * searched.
+ * </p>
+ * <p>
+ * <b>Important:</b> If the implementation of this method delegates
+ * the resource loading to a <tt>ModuleClassLoader</tt> of another module,
+ * then it should not use the method <tt>ModuleClassLoader.getResource()</tt>
+ * to get the resource; it should use <tt>ModuleClassLoader.getResourceFromModule()</tt>
+ * instead. This is necessary to eliminate an infinite loop that would
+ * occur otherwise. Also, with respect to the <tt>ModuleLoader</tt> framework,
+ * this method will only be called by a single thread at a time and is not
+ * intended to be called directly.
+ * </p>
+ * @param parent the parent class loader of the delegating class loader.
+ * @param module the target module that is loading the resource.
+ * @param name the name of the resource being loaded.
+ * @return a <tt>URL</tt> to the resource if found, <tt>null</tt> otherwise.
+ * @throws org.apache.osgi.moduleloader.ResourceNotFoundException if the
+ * resource could not be found and the entire search operation
+ * should fail.
+ **/
+ public URL findResource(ClassLoader parent, Module module, String name)
+ throws ResourceNotFoundException;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/URLPolicy.java b/src/org/apache/osgi/moduleloader/URLPolicy.java
new file mode 100644
index 0000000..0372c96
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/URLPolicy.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+import java.net.URL;
+
+/**
+ * <p>
+ * This interface represents the <tt>ModuleLoader</tt>'s policy for creating
+ * <tt>URL</tt> for resource loading and security purposes. Java requires the
+ * use of <tt>URL</tt>s for resource loading and security. For resource loading,
+ * <tt>URL</tt>s are returned for requested resources. Subsequently, the resource
+ * <tt>URL</tt> is used to create an input stream for reading the resources
+ * bytes. With respect to security, <tt>URL</tt>s are used when defining a
+ * class in order to determine where the code came from, this concept is called
+ * a <tt>CodeSource</tt>. This approach enables Java to assign permissions to
+ * code that originates from particular locations.
+ * </p>
+ * <p>
+ * The <tt>ModuleManager</tt> requires a concrete implementation of this
+ * interface in order to function. Whenever the <tt>ModuleManager</tt> requires
+ * a <tt>URL</tt> for either resource loading or security, it delegates to
+ * the policy implementation. A default implementation is provided,
+ * called <a href="DefaultURLPolicy.html"><tt>DefaultURLPolicy</tt></a>, but
+ * it only supports resource loading, not security.
+ * </p>
+ * @see org.apache.osgi.moduleloader.ModuleManager
+ * @see org.apache.osgi.moduleloader.DefaultURLPolicy
+**/
+public interface URLPolicy
+{
+ /**
+ * <p>
+ * This method should return a <tt>URL</tt> that represents the
+ * location from which the module originated. This <tt>URL</tt>
+ * can be used when assigning permissions to the module, such as
+ * is done in the Java permissions policy file.
+ * </p>
+ * @param mgr the <tt>ModuleManager</tt> of the module.
+ * @param module the module for which the <tt>URL</tt> is to be created.
+ * @return an <tt>URL</tt> to associate with the module.
+ **/
+ public URL createCodeSourceURL(ModuleManager mgr, Module module);
+
+ /**
+ * <p>
+ * This method should return a <tt>URL</tt> that is suitable
+ * for accessing the bytes of the specified resource. It must be possible
+ * open a connection to this <tt>URL</tt>, which may require that
+ * the implementer of this method also introduce a custom
+ * <tt>java.net.URLStreamHander</tt> when creating the <tt>URL</tt>.
+ * </p>
+ * @param mgr the <tt>ModuleManager</tt> of the module.
+ * @param module the module for which the resource is being loaded.
+ * @param rsIdx the index of the <tt>ResourceSource</tt> containing the resource.
+ * @param name the name of the resource being loaded.
+ * @return an <tt>URL</tt> for retrieving the resource.
+ **/
+ public URL createResourceURL(ModuleManager mgr, Module module, int rsIdx, String name);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/Util.java b/src/org/apache/osgi/moduleloader/Util.java
new file mode 100755
index 0000000..28a12da
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/Util.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader;
+
+public class Util
+{
+ public static String getClassName(String className)
+ {
+ if (className == null)
+ {
+ className = "";
+ }
+ return (className.lastIndexOf('.') < 0)
+ ? "" : className.substring(className.lastIndexOf('.') + 1);
+ }
+
+ public static String getClassPackage(String className)
+ {
+ if (className == null)
+ {
+ className = "";
+ }
+ return (className.lastIndexOf('.') < 0)
+ ? "" : className.substring(0, className.lastIndexOf('.'));
+ }
+
+ public static String getResourcePackage(String resource)
+ {
+ if (resource == null)
+ {
+ resource = "";
+ }
+ // NOTE: The package of a resource is tricky to determine since
+ // resources do not follow the same naming conventions as classes.
+ // This code is pessimistic and assumes that the package of a
+ // resource is everything up to the last '/' character. By making
+ // this choice, it will not be possible to load resources from
+ // imports using relative resource names. For example, if a
+ // bundle exports "foo" and an importer of "foo" tries to load
+ // "/foo/bar/myresource.txt", this will not be found in the exporter
+ // because the following algorithm assumes the package name is
+ // "foo.bar", not just "foo". This only affects imported resources,
+ // local resources will work as expected.
+ String pkgName = (resource.startsWith("/")) ? resource.substring(1) : resource;
+ pkgName = (pkgName.lastIndexOf('/') < 0)
+ ? "" : pkgName.substring(0, pkgName.lastIndexOf('/'));
+ pkgName = pkgName.replace('/', '.');
+ return pkgName;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/CompatibilityPolicy.java b/src/org/apache/osgi/moduleloader/search/CompatibilityPolicy.java
new file mode 100644
index 0000000..2ebb074
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/CompatibilityPolicy.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search;
+
+/**
+ * <p>
+ * This interface represents the naming and version numbering policy of
+ * import and export identifiers for the <tt>ImportSearchPolicy</tt>. A concrete
+ * implementation of this interface is required to create an instance
+ * of <tt>ImportSearchPolicy</tt>. The sole purpose of this interface
+ * is to allow the <tt>ImportSearchPolicy</tt> to determine if one
+ * import/export identifier and version is compatible with another.
+ * </p>
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy
+**/
+public interface CompatibilityPolicy
+{
+ /**
+ * Compares two import/export identifiers.
+ * @param leftId the identifier to test for compatibility.
+ * @param leftVersion the version number to test for compatibility.
+ * @param rightId the identifier used as the compatibility base line.
+ * @param rightVersion the version used as the compatibility base line.
+ * @return <tt>0</tt> if the identifiers are equal, <tt>-1</tt> if the
+ * left identifier is less then the right identifier, and <tt>1</tt>
+ * if the left identifier is greater than the right identifier.
+ * @throws java.lang.IllegalArgumentException if the two identifiers
+ * are not comparable, i.e., they refer to intrinsically different
+ * entities.
+ **/
+ public int compare(
+ Object leftId, Object leftVersion,
+ Object rightId, Object rightVersion);
+
+ /**
+ * Returns whether the first import/export identifer is compatible
+ * with the second; this method should not throw any exceptions.
+ * @param leftId the identifier to test for compatibility.
+ * @param leftVersion the version number to test for compatibility.
+ * @param rightId the identifier used as the compatibility base line.
+ * @param rightVersion the version used as the compatibility base line.
+ * @return <tt>true</tt> if the left version number object is compatible
+ * with the right version number object, otherwise <tt>false</tt>.
+ **/
+ public boolean isCompatible(
+ Object leftId, Object leftVersion,
+ Object rightId, Object rightVersion);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ExhaustiveSearchPolicy.java b/src/org/apache/osgi/moduleloader/search/ExhaustiveSearchPolicy.java
new file mode 100644
index 0000000..cc7cf4d
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ExhaustiveSearchPolicy.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search;
+
+import java.net.URL;
+
+import org.apache.osgi.moduleloader.*;
+
+/**
+ * <p>
+ * This class implements a <tt>ModuleLoader</tt> search policy that
+ * exhaustively and linearly searches all modules when trying to load
+ * a particular class or resource. As a result of this algorithm, every class loader
+ * for every module is essentially identical, meaning that each will
+ * load a given class or resource from the same class loader. This search policy
+ * provides behavior similar to the standard <tt>CLASSPATH</tt> environment
+ * variable approach. The main difference is that modules can be added
+ * to the module manager at run time; thus, the class path is dynamically
+ * extended. This search policy is not fully dynamic, since it does not
+ * support the removal of modules at run time; if a module is removed from
+ * the module manager at run time, there is no attempt to clean up its
+ * loaded classes.
+ * </p>
+ * @see org.apache.osgi.moduleloader.SearchPolicy
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+ * @see org.apache.osgi.moduleloader.ModuleManager
+**/
+public class ExhaustiveSearchPolicy implements SearchPolicy
+{
+ private ModuleManager m_mgr = null;
+
+ /**
+ * This method is part of the <tt>SearchPolicy</tt> interface.
+ * This method is called by the <tt>ModuleManager</tt> once to
+ * give the search policy instance a reference to its associated
+ * module manager. This method should be implemented such that
+ * it cannot be called twice; calling this method a second time
+ * should produce an illegal state exception.
+ * @param mgr the module manager associated with this search policy.
+ * @throws java.lang.IllegalStateException if the method is called
+ * more than once.
+ **/
+ public void setModuleManager(ModuleManager mgr)
+ throws IllegalStateException
+ {
+ if (m_mgr == null)
+ {
+ m_mgr = mgr;
+ }
+ else
+ {
+ throw new IllegalStateException("Module manager is already initialized");
+ }
+ }
+
+ public Object[] definePackage(Module module, String pkgName)
+ {
+ return null;
+ }
+
+ /**
+ * This method finds the specified class for the specified module. It
+ * finds the class by linearly asking each module in the module manager
+ * for the specific class. As soon as the class is found, it is returned.
+ * @param parent the parent class loader of the delegating class loader.
+ * @param module the target module that is loading the class.
+ * @param name the name of the class being loaded.
+ * @return the class if found, <tt>null</tt> otherwise.
+ **/
+ public Class findClassBeforeModule(ClassLoader parent, Module module, String name)
+ {
+ // First, try to load from parent.
+ if (parent != null)
+ {
+ try
+ {
+ Class c = parent.loadClass(name);
+ if (c != null)
+ {
+ return c;
+ }
+ }
+ catch (ClassNotFoundException ex)
+ {
+ // Ignore and search modules.
+ }
+ }
+
+ Module[] modules = m_mgr.getModules();
+ for (int i = 0; i < modules.length; i++)
+ {
+ try {
+ Class clazz = modules[i].getClassLoader().loadClassFromModule(name);
+ if (clazz != null)
+ {
+ return clazz;
+ }
+ } catch (Throwable th) {
+ }
+ }
+
+ return null;
+ }
+
+ public Class findClassAfterModule(ClassLoader parent, Module module, String name)
+ {
+ return null;
+ }
+
+ /**
+ * This method finds the specified resource for the specified module. It
+ * finds the resource by linearly asking each module in the module manager
+ * for specific resource. As soon as the resource is found, a <tt>URL</tt>
+ * to it is returned.
+ * @param parent the parent class loader of the delegating class loader.
+ * @param module the target module that is loading the resource.
+ * @param name the name of the resource being loaded.
+ * @return a <tt>URL</tt> to the resource if found, <tt>null</tt> otherwise.
+ **/
+ public URL findResource(ClassLoader parent, Module module, String name)
+ {
+ // First, try to load from parent.
+ if (parent != null)
+ {
+ URL url = parent.getResource(name);
+ if (url != null)
+ {
+ return url;
+ }
+ }
+
+ Module[] modules = m_mgr.getModules();
+ for (int i = 0; i < modules.length; i++)
+ {
+ try {
+ URL url = modules[i].getClassLoader().getResourceFromModule(name);
+ if (url != null)
+ {
+ return url;
+ }
+ } catch (Throwable th) {
+ }
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ImportSearchPolicy.java b/src/org/apache/osgi/moduleloader/search/ImportSearchPolicy.java
new file mode 100644
index 0000000..1f776f9
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ImportSearchPolicy.java
@@ -0,0 +1,1322 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search;
+
+import java.net.URL;
+import java.util.*;
+
+import org.apache.osgi.moduleloader.*;
+
+/**
+ * <p>
+ * This class implements a <tt>ModuleLoader</tt> search policy to support
+ * modules that import and export classes and resources from/to one another.
+ * Modules import from other modules by specifying a set of import identifiers
+ * and associated version numbers. Modules export their classes and
+ * resources by specifying a set of export identifiers and associated
+ * versions. Exports for a given module are also treated as imports for that module,
+ * meaning that it is possible for a module that exports classes to not use
+ * the classes it exports, but to actually use classes that are exported from
+ * another module. This search policy requires the following meta-data
+ * attributes be attached to each module:
+ * </p>
+ * <ul>
+ * <li><tt>ImportSearchPolicy.EXPORTS_ATTR</tt> - the "<tt>exports</tt>"
+ * meta-data attribute is used to declare the module's exports,
+ * </li>
+ * <li><tt>ImportSearchPolicy.IMPORTS_ATTR</tt> - the "<tt>imports</tt>"
+ * meta-data attribute is used to declare the module's imports,
+ * </li>
+ * <li><tt>ImportSearchPolicy.PROPAGATES_ATTR</tt> - the "<tt>propagates</tt>"
+ * meta-data attribute is used to declare which imports are exposed or
+ * "propagated" to clients of the module's exports, and
+ * </li>
+ * <li><tt>ImportSearchPolicy.VALID_ATTR</tt> - the "<tt>valid</tt>"
+ * meta-data attribute signifies the current <i>validation</i> status
+ * of the module (this will be defined more fully below).
+ * </li>
+ * </ul>
+ * <p>
+ * The value of the <tt>ImportSearchPolicy.EXPORTS_ATTR</tt> attribute is
+ * an array of <tt>Object</tt> arrays, i.e., <tt>Object[][]</tt>. Each element
+ * in the array signifies a particular export that is offered by this
+ * associated module. Each element is an array triple of
+ * <tt>Object</tt>, where the index into this triple is:
+ * </p>
+ * <ul>
+ * <li><tt>ImportSearchPolicy.IDENTIFIER_IDX</tt> - the first element
+ * is the export identifier object, used to identify the
+ * export target. The export identifier does not have any special
+ * meaning to the search policy and any value is allowed. A
+ * typical identifier might be the package name of the exported classes,
+ * such as <tt>javax.servlet</tt>.
+ * </li>
+ * <li><tt>ImportSearchPolicy.VERSION_IDX</tt> - the second element
+ * is the export version number. The version number does not have
+ * any special meaning to the search policy and any value is allowed.
+ * A typical version number might be major, minor, and release number.
+ * </li>
+ * <li><tt>ImportSearchPolicy.RESOLVING_MODULE_IDX</tt> - the third element
+ * is the resolving module for this export; since exports are treated like
+ * imports, it is possible that the resolving module will not be the
+ * exporting module itself. This value is filled in automatically by the
+ * search policy and is initially <tt>null</tt>.
+ * </li>
+ * </ul>
+ * </p>
+ * <p>
+ * The value of the <tt>ImportSearchPolicy.IMPORTS_ATTR</tt> attribute is
+ * essentially the same as the <tt>ImportSearchPolicy.EXPORTS_ATTR</tt> defined
+ * above; the only difference is that the array of versioned identifiers denote
+ * import targets rather than exports.
+ * </p>
+ * <p>
+ * The value of the <tt>ImportSearchPolicy.PROPAGATES_ATTR</tt> attribute is
+ * an array of <tt>Object</tt>s, i.e., <tt>Object[]</tt>. Each element in the
+ * array is an identifier of a propagated import target from the
+ * <tt>ImportSearchPolicy.IMPORTS_ATTR</tt> attribute. Only identifiers for
+ * import targets are candidates for inclusion and the version number is
+ * unnecessary since it is assumed from the corresponding import target.
+ * </p>
+ * <p>
+ * The value of the <tt>ImportSearchPolicy.VALID_ATTR</tt> attribute is a
+ * <tt>Boolean</tt>. The value is initially set to <tt>Boolean.FALSE</tt>
+ * and indicates that the module has not yet been validated. After the module
+ * is validated, the value is set to <tt>Boolean.TRUE</tt>. The search policy
+ * automatically adds this attribute to all modules and maintains its value.
+ * </p>
+ * <p>
+ * These meta-data attributes help the search policy enforce consistency
+ * using a process called <i>validation</i>; validation ensures that classes
+ * and resources are only loaded from a module whose imports are satisfied.
+ * Therefore, a <i>valid</i> module is a module whose imports are satisfied and
+ * an <i>invalid</i> module is a module whose imports are not yet satisfied.
+ * An invalid module may be invalid for two reasons:
+ * </p>
+ * <p>
+ * <ol>
+ * <li>Its imports are not available or</li>
+ * <li>It has not yet been validated.</li>
+ * </ol>
+ * </p>
+ * <p>
+ * These two possibilities arise due to the fact that module validation
+ * is not performed until it is necessary (i.e., lazy evaluation). A module
+ * is automatically validated when an attempt is made to get classes or
+ * resources from it, although it is possible to manually validate a module.
+ * For a given module, called <tt>M</tt>, the validation process attempts to
+ * find an exporting module for every import target of <tt>M</tt>. If an
+ * exporter is not found for a specific import target, then the validation of
+ * module <tt>M</tt> fails. If an exporting module is found, then this module
+ * is also validated, if it is not already. As a result, the validation of
+ * module <tt>M</tt> depends on the validation of the transitive closure of
+ * all modules on which <tt>M</tt> depends. It is also possible for modules
+ * to exhibit dependency cycles; circular dependencies are allowed.
+ * Initially, a module's <tt>VALID_ATTR</tt> is set to <tt>Boolean.FALSE</tt>,
+ * but after the module is successfully validated, this attribute is set to
+ * <tt>Boolean.TRUE</tt>.
+ * </p>
+ * <p>
+ * Besides ensuring that every import target is resolved to an appropriate
+ * exporting module, the validation process also attempts to maintain
+ * consistency along "propagation" chains. Propagation occurs when a module
+ * imports classes that are also visible from its own exports; for example,
+ * an HTTP server implementation may import classes from <tt>javax.servlet</tt>
+ * and export classes that have methods that use the type <tt>javax.servlet.Servlet</tt>
+ * in their signatures. Monitoring these types of occurences is important
+ * to uncover import source and version conflicts when multiple sources or
+ * versions of an import target are available within one virtual machine. When
+ * a module <tt>M</tt> is validated, the propagation information of each
+ * module that resolves the imports of <tt>M</tt> is checked to ensure
+ * that they do not propagate conflicting sources of <tt>M</tt>'s
+ * imports; specifically, it is verified that all propagators of a
+ * particular import target have the same source module for that import
+ * target.
+ * </p>
+ * <p>
+ * To facilitate applicability in as many scenarios as possible, this search
+ * policy delegates some decisions via additional policy interfaces. The following
+ * two policy interfaces must be specified by the code that instantiates the
+ * <tt>ImportSearchPolicy</tt> object:
+ * </p>
+ * <ul>
+ * <li><tt>CompatibilityPolicy</tt> - this policy is used to determine
+ * whether import/export version numbers are compatible.
+ * </li>
+ * <li><tt>SelectionPolicy</tt> - this policy is used to resolve a specific
+ * import target when multiple candidate exporting modules exist.
+ * </li>
+ * </ul>
+ * <p>
+ * Once an instance is created with definitions of the above policy interfaces,
+ * this search policy will operate largely self-contained. There are a few utility
+ * methods for manually validating modules, adding validation listeners, and
+ * access meta-data attributes, but for the most part these are not necessary
+ * except for implementing more sophisticated infrastructure.
+ * </p>
+ * <p>
+ * The follow snippet of code illustrates a typical usage scenario for
+ * this search policy:
+ * </p>
+ * <pre>
+ * ...
+ * ImportSearchPolicy searchPolicy =
+ * new ImportSearchPolicy(
+ * new MyCompatibilityPolicy(), new MySelectionPolicy());
+ * ModuleManager mgr = new ModuleManager(searchPolicy);
+ * ...
+ * Object[][] exports = new Object[][] {
+ * { "org.apache.jasper", "2.1.0", null }
+ * };
+ * Object[][] imports = new Object[][] {
+ * { "javax.servlet", "2.3.1", null }
+ * };
+ * Object[][] attributes = new Object[][] {
+ * new Object[] { ImportSearchPolicy.EXPORTS_ATTR, exports },
+ * new Object[] { ImportSearchPolicy.IMPORTS_ATTR, imports },
+ * new Object[] { ImportSearchPolicy.PROPAGATES_ATTR, new Object[] { "javax.servlet" } }
+ * };
+ * ResourceSource[] resSources = new ResourceSource[] {
+ * new JarResourceSource(file1)
+ * new JarResourceSource(file2)
+ * };
+ * Module module = mgr.addModule(id, attributes, resSources, null);
+ * ClassLoader loader = module.getClassLoader();
+ * // Assuming that all imports are satisfied...
+ * Class clazz = loader.loadClass("org.foo.MyClass");
+ * ...
+ * </pre>
+ * <p>
+ * The above code snippet illustrates creating a module with one export and one
+ * import, where the import is also propagated via the module's export. The module
+ * has multiple resource sources, but no library sources.
+ * </p>
+ * @see org.apache.osgi.moduleloader.SearchPolicy
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+ * @see org.apache.osgi.moduleloader.ModuleManager
+**/
+public class ImportSearchPolicy implements SearchPolicy, ModuleListener
+{
+ /**
+ * This is the name of the "exports" meta-data attribute that
+ * should be attached to each module. The value of this attribute
+ * is of type <tt>Object[][]</tt> and is described in the overview
+ * documentation for this class.
+ **/
+ public static final String EXPORTS_ATTR = "exports";
+ /**
+ * This is the name of the "imports" meta-data attribute that
+ * should be attached to each module. The value of this attribute
+ * is of type <tt>Object[][]</tt> and is described in the overview
+ * documentation for this class.
+ **/
+ public static final String IMPORTS_ATTR = "imports";
+ /**
+ * This is the name of the "propagates" meta-data attribute that
+ * should be attached to each module. The value of this attribute
+ * is of type <tt>Object[]</tt> and is described in the overview
+ * documentation for this class.
+ **/
+ public static final String PROPAGATES_ATTR = "propagates";
+ /**
+ * This is the name of the "valid" meta-data attribute that is
+ * automatically attached to each module. The value of this attribute
+ * is of type <tt>Boolean</tt> and is described in the overview
+ * documentation for this class.
+ **/
+ public static final String VALID_ATTR = "valid";
+
+ /**
+ * This is the index used to retrieve the import or export identifier
+ * from a given element of the <tt>EXPORTS_ATTR</tt> or the <tt>IMPORTS_ATTR</tt>
+ * attribute.
+ **/
+ public static final int IDENTIFIER_IDX = 0;
+ /**
+ * This is the index used to retrieve the import or export version number
+ * from a given element of the <tt>EXPORTS_ATTR</tt> or the <tt>IMPORTS_ATTR</tt>
+ * attribute.
+ **/
+ public static final int VERSION_IDX = 1;
+ /**
+ * This is the index used to retrieve the resolving module for an import
+ * or export target from a given element of the <tt>EXPORTS_ATTR</tt> or
+ * the <tt>IMPORTS_ATTR</tt> attribute.
+ **/
+ public static final int RESOLVING_MODULE_IDX = 2;
+
+ private ModuleManager m_mgr = null;
+ private CompatibilityPolicy m_compatPolicy = null;
+ private SelectionPolicy m_selectPolicy = null;
+ private ValidationListener[] m_listeners = null;
+ private String[] m_searchAttrs = { IMPORTS_ATTR, EXPORTS_ATTR };
+ private static final ValidationListener[] m_noListeners = new ValidationListener[0];
+
+ /**
+ * Constructs an import search policy instance with the supplied
+ * compatibility and selection policies.
+ * @param compatPolicy the compatibility policy implementation to be used
+ * by the search policy.
+ * @param selectPolicy the selection policy implementation to be used
+ * by the search policy.
+ **/
+ public ImportSearchPolicy(
+ CompatibilityPolicy compatPolicy,
+ SelectionPolicy selectPolicy)
+ {
+ m_compatPolicy = compatPolicy;
+ m_selectPolicy = selectPolicy;
+ m_listeners = m_noListeners;
+ }
+
+ /**
+ * Returns the compatibility policy used by this import search policy instance.
+ * @return the compatibility policy of this import search policy instance.
+ **/
+ public CompatibilityPolicy getCompatibilityPolicy()
+ {
+ return m_compatPolicy;
+ }
+
+ /**
+ * Returns the selection policy used by this import search policy instance.
+ * @return the selection policy of this import search policy instance.
+ **/
+ public SelectionPolicy getSelectionPolicy()
+ {
+ return m_selectPolicy;
+ }
+
+ // JavaDoc comment copied from SearchPolicy.
+ public void setModuleManager(ModuleManager mgr)
+ throws IllegalStateException
+ {
+ if (m_mgr == null)
+ {
+ m_mgr = mgr;
+ m_mgr.addModuleListener(this);
+ }
+ else
+ {
+ throw new IllegalStateException("Module manager is already initialized");
+ }
+ }
+
+ public Object[] definePackage(Module module, String pkgName)
+ {
+ return null;
+ }
+
+ /**
+ * This method is part of the <tt>SearchPolicy</tt> interface; it
+ * should not be called directly. This method finds a class
+ * based on the import/export meta-data attached to the module.
+ * It first attempts to validate the target module, if it cannot
+ * be validated, then a <tt>ClassNotFoundException</tt> is thrown.
+ * Once the module is validated, the module's imports are searched
+ * for the target class, then the module's exports are searched.
+ * If the class is found in either place, then it is returned;
+ * otherwise, <tt>null</tt> is returned.
+ * @param parent the parent class loader of the delegating class loader.
+ * @param module the target module that is loading the class.
+ * @param name the name of the class being loaded.
+ * @return the class if found, <tt>null</tt> otherwise.
+ * @throws java.lang.ClassNotFoundException if the target module
+ * could not be validated.
+ **/
+ public Class findClassBeforeModule(ClassLoader parent, Module module, String name)
+ throws ClassNotFoundException
+ {
+ // First, try to validate the originating module.
+ try {
+ validate(module);
+ } catch (ValidationException ex) {
+ throw new ClassNotFoundException(name);
+ }
+
+ // Try to load from parent.
+ if (parent != null)
+ {
+ try
+ {
+ Class c = parent.loadClass(name);
+ if (c != null)
+ {
+ return c;
+ }
+ }
+ catch (ClassNotFoundException ex)
+ {
+ // Ignore and search imports/exports.
+ }
+ }
+
+ // Get the package of the target class.
+ String pkgName = Util.getClassPackage(name);
+
+ // We delegate to the module's imports for finding the
+ // desired class first, then we delegate to the module's
+ // exports for finding the desired class. We do this because
+ // implicitly a module imports everything that it exports.
+ // To avoid code duplication, we use a simple array of
+ // attribute names to loop through both of the imports
+ // and exports meta-data searching for the desired class.
+ for (int attrIdx = 0; attrIdx < m_searchAttrs.length; attrIdx++)
+ {
+ Object[][] imports = getImportsOrExports(module, m_searchAttrs[attrIdx]);
+
+ // If the module doesn't import anything, then just
+ // return null.
+ if ((imports != null) && (imports.length > 0))
+ {
+ for (int i = 0; i < imports.length; i++)
+ {
+ // Only check when the package of the class is
+ // the same as the import package.
+ if (imports[i][IDENTIFIER_IDX].equals(pkgName))
+ {
+ Module resolvingModule = (Module) imports[i][RESOLVING_MODULE_IDX];
+ try {
+ Class clazz =
+ resolvingModule.getClassLoader().loadClassFromModule(name);
+ if (clazz != null)
+ {
+ return clazz;
+ }
+ } catch (Throwable th) {
+ // Not much we can do.
+ System.err.println("ImportSearchPolicy: " + th.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public Class findClassAfterModule(ClassLoader parent, Module module, String name)
+ {
+ return null;
+ }
+
+ /**
+ * This method is part of the <tt>SearchPolicy</tt> interface; it
+ * should not be called directly. This method finds a resource
+ * based on the import/export meta-data attached to the module.
+ * It first attempts to validate the target module, if it cannot
+ * be validated, then it returns <tt>null</tt>. Once the module is
+ * validated, the module's imports are searched for the target
+ * resource, then the module's exports are searched. If the resource
+ * is found in either place, then a <tt>URL</tt> to is is returned;
+ * otherwise, <tt>null</tt> is returned.
+ * @param parent the parent class loader of the delegating class loader.
+ * @param module the target module that is loading the resource.
+ * @param name the name of the resource being loaded.
+ * @return a <tt>URL</tt> to the resource if found, <tt>null</tt> otherwise.
+ **/
+ public URL findResource(ClassLoader parent, Module module, String name)
+ {
+ // First, try to validate the originating module.
+ try
+ {
+ validate(module);
+ }
+ catch (ValidationException ex)
+ {
+ return null;
+ }
+
+ // Try to load from parent.
+ if (parent != null)
+ {
+ URL url = parent.getResource(name);
+ if (url != null)
+ {
+ return url;
+ }
+ }
+
+ // Get the package of the target resource.
+ String pkgName = Util.getResourcePackage(name);
+
+ // We delegate to the module's imports for finding the
+ // desired class first, then we delegate to the module's
+ // exports for finding the desired class. We do this because
+ // implicitly a module imports everything that it exports.
+ // To avoid code duplication, we use a simple array of
+ // attribute names to loop through both of the imports
+ // and exports meta-data searching for the desired class.
+ for (int attrIdx = 0; attrIdx < m_searchAttrs.length; attrIdx++)
+ {
+ Object[][] imports = getImportsOrExports(module, m_searchAttrs[attrIdx]);
+
+ // If the module doesn't import or export anything,
+ // then just return null.
+ if ((imports != null) && (imports.length > 0))
+ {
+ for (int i = 0; i < imports.length; i++)
+ {
+ // Only check when the package of the resource is
+ // the same as the import package.
+ if (imports[i][IDENTIFIER_IDX].equals(pkgName))
+ {
+ Module resolvingModule = (Module) imports[i][RESOLVING_MODULE_IDX];
+ try {
+ URL url =
+ resolvingModule.getClassLoader().getResourceFromModule(name);
+ if (url != null)
+ {
+ return url;
+ }
+ } catch (Throwable th) {
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private Map m_validateMap = new HashMap();
+ private Module m_rootModule = null;
+
+ /**
+ * This method validates the specified target module. If the module
+ * is already validated, then this method returns immediately. This
+ * method synchronizes on the associated module manager to ensure that
+ * modules are not added or removed while the validation is occuring.
+ * Each import and export for the target module are resolved by first
+ * using the compatibility policy to create a list of candidate export
+ * modules, then using the selection policy to choose among the
+ * candidates. Each selected candidate is also recursively validated;
+ * this process validates a transitive closure of all dependent modules.
+ * After the selected candidate is validated, its propagated imports
+ * are checked to make sure that they do not conflict with any existing
+ * propagated imports. If no validation errors occur, then all dependent
+ * modules are marked as validated, if they are not already validated.
+ * If an error occurs, the valid state of all modules remains unchanged.
+ * @param module the module to validate.
+ * @throws org.apache.osgi.moduleloader.search.ValidationException if
+ * the module or any dependent modules could not be validated.
+ **/
+ public void validate(Module module)
+ throws ValidationException
+ {
+ if (getValidAttribute(module).booleanValue())
+ {
+ return;
+ }
+
+ // Flag to indicate whether the bundle is valid or not.
+ boolean isValid = true;
+
+ // This list will be used to remember which bundles
+ // were validated so that the validation events can
+ // be fired outside of the synchronized block.
+ List fireValidatedList = null;
+
+ // Will hold the exception to be thrown or rethrown.
+ ValidationException invalidException = null;
+
+ // Synchronize on the module manager, because we don't want
+ // anything to change while we are in the middle of this
+ // operation.
+ synchronized (m_mgr)
+ {
+ // If we are already validating this module, then
+ // just return; this is necessary for cycles.
+ if (m_validateMap.get(module) != null)
+ {
+ return;
+ }
+
+ // Add the module to the validation map; this
+ // is necessary for cycles.
+ m_validateMap.put(module, module);
+
+ // Keep track of the root module that started
+ // the validation request; this is necessary
+ // for cycles.
+ if (m_rootModule == null)
+ {
+ m_rootModule = module;
+ }
+
+ // Now perform the validation algorithm.
+ Map propagateMap = new HashMap();
+
+ // Validation binds the module's imports to a specific exporting
+ // module. A module also implicitly imports whatever it exports,
+ // so exports are validated in the same fashion as imports. It
+ // is possible, given the selection policy that a given export
+ // may actually be satisfied by a different module (i.e., a
+ // module is not guaranteed to be bound to what it exports). Since
+ // the imports and exports meta-data are validated in the same
+ // fashion, we will use the follow attribute array to loop and
+ // validate both imports and exports using the same code.
+ for (int attrIdx = 0; (isValid) && (attrIdx < m_searchAttrs.length); attrIdx++)
+ {
+ // Get the imports (exports are treated as imports to)
+ // for the current module.
+ Object[][] imports = getImportsOrExports(module, m_searchAttrs[attrIdx]);
+ // See if each import has available exporters.
+ for (int impIdx = 0; impIdx < imports.length; impIdx++)
+ {
+ // Get all exporter candidates.
+ Module[] candidates =
+ getCompatibleModules(
+ imports[impIdx][IDENTIFIER_IDX], imports[impIdx][VERSION_IDX]);
+ // If there are no candidates, then prepare a
+ // validation exception.
+ if (candidates == null)
+ {
+ isValid = false;
+ invalidException =
+ new ValidationException(
+ "Unable to validate module",
+ module,
+ imports[impIdx][IDENTIFIER_IDX],
+ imports[impIdx][VERSION_IDX],
+ false);
+ break;
+ }
+
+ // Use selection policy to choose a single export candidate.
+ Module exportingModule = m_selectPolicy.select(
+ module, imports[impIdx][IDENTIFIER_IDX],
+ imports[impIdx][VERSION_IDX], candidates, m_compatPolicy);
+ // If there is no export module chosen, then prepare
+ // a validation exception.
+ if (exportingModule == null)
+ {
+ isValid = false;
+ invalidException =
+ new ValidationException(
+ "Unable to validate module",
+ module,
+ imports[impIdx][IDENTIFIER_IDX],
+ imports[impIdx][VERSION_IDX],
+ false);
+ break;
+ }
+
+ // Make sure that the export module is
+ // also validated.
+ try
+ {
+ validate(exportingModule);
+ }
+ catch (ValidationException ex)
+ {
+ // Prepare to rethrow the exception if
+ // the exporter could not be validated.
+ isValid = false;
+ invalidException = ex;
+ break;
+ }
+
+ // Keep track of all propagations from each module that this
+ // module imports from. Verify that any given import always
+ // comes form the same export module, otherwise there will be
+ // class cast exceptions.
+ Object[] propagates = getPropagatesAttribute(exportingModule);
+ for (int propIdx = 0; propIdx < propagates.length; propIdx++)
+ {
+ // If the module does not import the propagated target,
+ // then it can be safely ignored.
+ if (doesImport(module, propagates[propIdx]))
+ {
+ Module sourceModule =
+ (Module) propagateMap.get(propagates[propIdx]);
+
+ // If the propagation source module has not already been
+ // found, then remember the resolving module of the
+ // exporting module as the source of the propagated
+ // target.
+ if (sourceModule == null)
+ {
+ propagateMap.put(
+ propagates[propIdx],
+ getImportResolvingModule(
+ exportingModule, propagates[propIdx]));
+ }
+ // If the propagation source module is found, then check to
+ // see if it is propagating the import target from the same
+ // module as previously determined for this module. If not,
+ // then this is a propagation conflict.
+ else if (sourceModule !=
+ getImportResolvingModule(
+ exportingModule, propagates[propIdx]))
+ {
+ isValid = false;
+ invalidException =
+ new ValidationException(
+ "Unable to validate module",
+ exportingModule,
+ propagates[propIdx],
+ null,
+ true);
+ break;
+ }
+ }
+ }
+
+ // Set the chosen exporting module for the module
+ // being validated.
+ imports[impIdx][RESOLVING_MODULE_IDX] = exportingModule;
+ }
+ }
+
+ // Since this method is recursive, check to see it we are
+ // back at the root module that started the request, which
+ // would indicate that the request is finished.
+ if (m_rootModule == module)
+ {
+ // If the result is valid, then we have validated successfully.
+ if (isValid)
+ {
+ // Loop through all modules in validate map
+ // and mark them as valid.
+ Iterator iter = m_validateMap.keySet().iterator();
+ while (iter.hasNext())
+ {
+ Module m = (Module) iter.next();
+ if (!getValidAttribute(m).booleanValue())
+ {
+ m.setAttribute(VALID_ATTR, Boolean.TRUE);
+ if (fireValidatedList == null)
+ {
+ fireValidatedList = new ArrayList();
+ }
+ fireValidatedList.add(m);
+ }
+ }
+ }
+ // If we are here, then the validate failed, so we
+ // need to reset any partially validated modules.
+ else
+ {
+ Iterator iter = m_validateMap.keySet().iterator();
+ while (iter.hasNext())
+ {
+ Module m = (Module) iter.next();
+ invalidate(
+ m,
+ m.getAttributes(),
+ m.getResourceSources(),
+ m.getLibrarySources());
+ }
+ }
+
+ // Clear the root module and validation map
+ // before leaving the synchronized block.
+ m_rootModule = null;
+ m_validateMap.clear();
+ }
+ }
+
+ // (Re)throw the exception if invalid, otherwise
+ // fire validation events if the validated event
+ // list is not null.
+ if (!isValid)
+ {
+ throw invalidException;
+ }
+ else if (fireValidatedList != null)
+ {
+ for (int i = 0; i < fireValidatedList.size(); i++)
+ {
+ fireModuleValidated((Module) fireValidatedList.get(i));
+ }
+ }
+ }
+
+ /**
+ * This method returns a list of modules that have an export
+ * that is compatible with the given import identifier and version.
+ * @param identifier the import identifier.
+ * @param version the version of the import identifier.
+ * @return an array of modules that have compatible exports or <tt>null</tt>
+ * if none are found.
+ **/
+ protected Module[] getCompatibleModules(Object identifier, Object version)
+ {
+ List list = null;
+ Module[] modules = m_mgr.getModules();
+ for (int modIdx = 0; modIdx < modules.length; modIdx++)
+ {
+ Object[][] exports = getExportsAttribute(modules[modIdx]);
+ for (int expIdx = 0; expIdx < exports.length; expIdx++)
+ {
+ // If the identifiers are comparable and compatible,
+ // then add the export identifier to the list.
+ if (m_compatPolicy.isCompatible(
+ exports[expIdx][IDENTIFIER_IDX], exports[expIdx][VERSION_IDX],
+ identifier, version))
+ {
+ if (list == null)
+ {
+ list = new ArrayList();
+ }
+ list.add(modules[modIdx]);
+ }
+ }
+ }
+
+ if (list == null)
+ {
+ return null;
+ }
+
+ Module[] result = new Module[list.size()];
+ return (Module[]) list.toArray(result);
+ }
+
+ /**
+ * Invalidates a module by flushing its class loader and
+ * re-initializing its meta-data values.
+ * @param module the module to be invalidated.
+ * @param attributes the attributes associated with the module, since they
+ * might have changed.
+ * @param resSources the resource sources associated wih the module, since they
+ * might have changed.
+ * @param libSources the library sources associated wih the module, since they
+ * might have changed.
+ **/
+ public void invalidate(
+ Module module, Object[][] attributes,
+ ResourceSource[] resSources, LibrarySource[] libSources)
+ {
+ // Synchronize on the module manager, because we don't want
+ // anything to change while we are in the middle of this
+ // operation.
+ synchronized (m_mgr)
+ {
+ m_mgr.resetModule(module, attributes, resSources, libSources);
+ }
+
+ // Fire invalidation event if necessary.
+ fireModuleInvalidated(m_mgr.getModule(module.getId()));
+ }
+
+ //
+ // Event handling methods for validation events.
+ //
+
+ /**
+ * Adds a validation listener to this import search policy. Validation
+ * listeners are notified when a module is validated and/or invalidated
+ * by the search policy.
+ * @param l the validation listener to add.
+ **/
+ public void addValidationListener(ValidationListener l)
+ {
+ // Verify listener.
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener is null");
+ }
+
+ // Use the m_noListeners object as a lock.
+ synchronized (m_noListeners)
+ {
+ // If we have no listeners, then just add the new listener.
+ if (m_listeners == m_noListeners)
+ {
+ m_listeners = new ValidationListener[] { l };
+ }
+ // Otherwise, we need to do some array copying.
+ // Notice, the old array is always valid, so if
+ // the dispatch thread is in the middle of a dispatch,
+ // then it has a reference to the old listener array
+ // and is not affected by the new value.
+ else
+ {
+ ValidationListener[] newList = new ValidationListener[m_listeners.length + 1];
+ System.arraycopy(m_listeners, 0, newList, 0, m_listeners.length);
+ newList[m_listeners.length] = l;
+ m_listeners = newList;
+ }
+ }
+ }
+
+ /**
+ * Removes a validation listener to this import search policy.
+ * @param l the validation listener to remove.
+ **/
+ public void removeValidationListener(ValidationListener l)
+ {
+ // Verify listener.
+ if (l == null)
+ {
+ throw new IllegalArgumentException("Listener is null");
+ }
+
+ // Use the m_noListeners object as a lock.
+ synchronized (m_noListeners)
+ {
+ // Try to find the instance in our list.
+ int idx = -1;
+ for (int i = 0; i < m_listeners.length; i++)
+ {
+ if (m_listeners[i].equals(l))
+ {
+ idx = i;
+ break;
+ }
+ }
+
+ // If we have the instance, then remove it.
+ if (idx >= 0)
+ {
+ // If this is the last listener, then point to empty list.
+ if (m_listeners.length == 1)
+ {
+ m_listeners = m_noListeners;
+ }
+ // Otherwise, we need to do some array copying.
+ // Notice, the old array is always valid, so if
+ // the dispatch thread is in the middle of a dispatch,
+ // then it has a reference to the old listener array
+ // and is not affected by the new value.
+ else
+ {
+ ValidationListener[] newList = new ValidationListener[m_listeners.length - 1];
+ System.arraycopy(m_listeners, 0, newList, 0, idx);
+ if (idx < newList.length)
+ {
+ System.arraycopy(m_listeners, idx + 1, newList, idx,
+ newList.length - idx);
+ }
+ m_listeners = newList;
+ }
+ }
+ }
+ }
+
+ /**
+ * Fires a validation event for the specified module.
+ * @param module the module that was validated.
+ **/
+ protected void fireModuleValidated(Module module)
+ {
+ // Event holder.
+ ModuleEvent event = null;
+
+ // Get a copy of the listener array, which is guaranteed
+ // to not be null.
+ ValidationListener[] listeners = m_listeners;
+
+ // Loop through listeners and fire events.
+ for (int i = 0; i < listeners.length; i++)
+ {
+ // Lazily create event.
+ if (event == null)
+ {
+ event = new ModuleEvent(m_mgr, module);
+ }
+ listeners[i].moduleValidated(event);
+ }
+ }
+
+ /**
+ * Fires an invalidation event for the specified module.
+ * @param module the module that was invalidated.
+ **/
+ protected void fireModuleInvalidated(Module module)
+ {
+ // Event holder.
+ ModuleEvent event = null;
+
+ // Get a copy of the listener array, which is guaranteed
+ // to not be null.
+ ValidationListener[] listeners = m_listeners;
+
+ // Loop through listeners and fire events.
+ for (int i = 0; i < listeners.length; i++)
+ {
+ // Lazily create event.
+ if (event == null)
+ {
+ event = new ModuleEvent(m_mgr, module);
+ }
+ listeners[i].moduleInvalidated(event);
+ }
+ }
+
+ //
+ // ModuleListener methods.
+ //
+
+ /**
+ * Callback method for <tt>ModuleListener</tt>; this should not
+ * be called directly. This callback is used to initialize module
+ * meta-data attributes; it adds the <tt>VALID_ATTR</tt> attribute
+ * and initializes the resolving module entries in <tt>EXPORTS_ATTR</tt>
+ * and <tt>IMPORTS_ATTR</tt> to <tt>null</tt>.
+ **/
+ public void moduleAdded(ModuleEvent event)
+ {
+ synchronized (event.getModule())
+ {
+ // Add valid attribute to all modules.
+ event.getModule().setAttribute(VALID_ATTR, Boolean.FALSE);
+
+ for (int attrIdx = 0; attrIdx < m_searchAttrs.length; attrIdx++)
+ {
+ Object[][] imports =
+ getImportsOrExports(event.getModule(), m_searchAttrs[attrIdx]);
+ for (int i = 0; i < imports.length; i++)
+ {
+ imports[i][RESOLVING_MODULE_IDX] = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Callback method for <tt>ModuleListener</tt>; this should not
+ * be called directly. This callback is used to re-initialize module
+ * meta-data attributes; it adds the <tt>VALID_ATTR</tt> attribute
+ * and initializes the resolving module entries in <tt>EXPORTS_ATTR</tt>
+ * and <tt>IMPORTS_ATTR</tt> to <tt>null</tt>. It then invalidates
+ * all modules that import from the reset module.
+ **/
+ public void moduleReset(ModuleEvent event)
+ {
+ // This will reset module meta-data.
+ moduleAdded(event);
+
+// TODO: Synchronization?
+ ModuleManager m_mgr = (ModuleManager) event.getSource();
+ List list = createImporterList(m_mgr, event.getModule());
+ for (int i = 0; (list != null) && (i < list.size()); i++)
+ {
+ Module module = (Module) list.get(i);
+ invalidate(
+ module, module.getAttributes(),
+ module.getResourceSources(), module.getLibrarySources());
+ }
+ }
+
+ /**
+ * Callback method for <tt>ModuleListener</tt>; this should not
+ * be called directly. Used to listen for module removal events
+ * in order to invalidate all the modules that import form the
+ * removed moduled.
+ **/
+ public void moduleRemoved(ModuleEvent event)
+ {
+// TODO: Synchronization?
+ ModuleManager m_mgr = (ModuleManager) event.getSource();
+ List list = createImporterList(m_mgr, event.getModule());
+ for (int i = 0; (list != null) && (i < list.size()); i++)
+ {
+ Module module = (Module) list.get(i);
+ invalidate(
+ module, module.getAttributes(),
+ module.getResourceSources(), module.getLibrarySources());
+ }
+ }
+
+ //
+ // Instance utility methods.
+ //
+
+ /**
+ * This utility method returns the module that exports the
+ * specified import identifier and version. This method uses the
+ * <tt>validate()</tt> method to find the exporting module and,
+ * as a result, relies on the compatibility and selection
+ * policies associated with this <tt>ImportSearchPolicy</tt>
+ * instance. If successful, the returned module is guaranteed
+ * to be validated. This method only needs to be used for more
+ * advanced purposes (i.e., check import availability dynamically,
+ * etc.) and need not be used under normal circumstances.
+ * @param identifier the identifier of the import to resolve.
+ * @param version the version of the import to resolve.
+ * @return the exporting module selected to resolve the specified
+ * import target.
+ **/
+ public Module resolveImportTarget(Object identifier, Object version)
+ {
+ // Create a fake module that imports the specified target
+ // and then try to validate it so we can get the exporting
+ // module that is used to satisfy the import.
+ Object[] targetImport = { identifier, version, null };
+ Object[][] attrs = new Object[][] {
+ new Object[] { EXPORTS_ATTR, new Object[0][0] },
+ new Object[] { IMPORTS_ATTR, new Object[][] { targetImport } },
+ new Object[] { PROPAGATES_ATTR, new Object[0] },
+ new Object[] { VALID_ATTR, Boolean.FALSE}
+ };
+ Module fake = new Module(m_mgr, "resolve import", attrs, null, null, false);
+ try {
+ validate(fake);
+ } catch (ValidationException ex) {
+ // Ignore this.
+ }
+ return (Module) targetImport[RESOLVING_MODULE_IDX];
+ }
+
+ //
+ // Static utility methods.
+ //
+
+ private static final Object[][] m_emptyImports = new Object[0][0];
+ private static final Object[] m_emptyProp = new Object[0];
+
+ /**
+ * Utility method that returns the <tt>VALID_ATTR</tt> attribute for
+ * the specified module.
+ * @param module the module whose <tt>VALID_ATTR</tt> attribute is to
+ * be retrieved.
+ * @return an instance of <tt>Boolean</tt>.
+ **/
+ public static Boolean getValidAttribute(Module module)
+ {
+ Object value = module.getAttribute(VALID_ATTR);
+ if (value != null)
+ {
+ return (Boolean) value;
+ }
+ return Boolean.FALSE;
+ }
+
+ /**
+ * Utility method that returns the <tt>IMPORTS_ATTR</tt> attribute for
+ * the specified module.
+ * @param module the module whose <tt>IMPORTS_ATTR</tt> attribute is to
+ * be retrieved.
+ * @return an <tt>Object[][]</tt> value or <tt>null</tt>.
+ **/
+ public static Object[][] getImportsAttribute(Module module)
+ {
+ Object value = module.getAttribute(IMPORTS_ATTR);
+ if (value != null)
+ {
+ return (Object[][]) value;
+ }
+ return m_emptyImports;
+ }
+
+ /**
+ * Utility method that returns the <tt>EXPORTS_ATTR</tt> attribute for
+ * the specified module.
+ * @param module the module whose <tt>EXPORTS_ATTR</tt> attribute is to
+ * be retrieved.
+ * @return an <tt>Object[][]</tt> value or <tt>null</tt>.
+ **/
+ public static Object[][] getExportsAttribute(Module module)
+ {
+ Object value = module.getAttribute(EXPORTS_ATTR);
+ if (value != null)
+ {
+ return (Object[][]) value;
+ }
+ return m_emptyImports;
+ }
+
+ /**
+ * Utility method that returns the <tt>IMPORTS_ATTR</tt> or the
+ * <tt>EXPORTS_ATTR</tt> attribute for the specified module.
+ * @param module the module whose <tt>IMPORTS_ATTR</tt> or
+ * <tt>EXPORTS_ATTR</tt> attribute is to be retrieved.
+ * @param name either <tt>IMPORTS_ATTR</tt> or <tt>EXPORTS_ATTR</tt>
+ * depending on which attribute should be retrieved.
+ * @return an <tt>Object[][]</tt> value or <tt>null</tt>.
+ **/
+ public static Object[][] getImportsOrExports(Module module, String name)
+ {
+ Object value = module.getAttribute(name);
+ if (value != null)
+ {
+ return (Object[][]) value;
+ }
+ return m_emptyImports;
+ }
+
+ /**
+ * Utility method that returns the <tt>PROPAGATES_ATTR</tt> attribute for
+ * the specified module.
+ * @param module the module whose <tt>PROPAGATES_ATTR</tt> attribute is to
+ * be retrieved.
+ * @return an <tt>Object[]</tt> value or <tt>null</tt>.
+ **/
+ public static Object[] getPropagatesAttribute(Module module)
+ {
+ Object value = module.getAttribute(PROPAGATES_ATTR);
+ if (value != null)
+ {
+ return (Object[]) value;
+ }
+ return m_emptyProp;
+ }
+
+ /**
+ * Utility method to determine if the specified module imports a given
+ * import identifier, regardless of version. This method checks both
+ * imports and exports, since a module is assumed to import what it exports.
+ * @param module the module to check.
+ * @param identifier the import identifier to check.
+ * @return <tt>true</tt> if the module imports the specified
+ * import identifier or <tt>false</tt> if it does not.
+ **/
+ public static boolean doesImport(Module module, Object identifier)
+ {
+ Object[][] imports = getImportsAttribute(module);
+ for (int i = 0; i < imports.length; i++)
+ {
+ if (imports[i][IDENTIFIER_IDX].equals(identifier))
+ {
+ return true;
+ }
+ }
+ imports = getExportsAttribute(module);
+ for (int i = 0; i < imports.length; i++)
+ {
+ if (imports[i][IDENTIFIER_IDX].equals(identifier))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Utility method to create a list of modules that import from
+ * the specified module.
+ * @param mgr the module manager that contains the module.
+ * @param module the module for which to create an importer list.
+ * @return a list of modules that import from the specified module
+ * or <tt>null</tt>.
+ **/
+ public static List createImporterList(ModuleManager mgr, Module module)
+ {
+ List list = null;
+ Module[] modules = mgr.getModules();
+ for (int modIdx = 0; modIdx < modules.length; modIdx++)
+ {
+ Object[][] imports = getImportsAttribute(modules[modIdx]);
+ for (int impIdx = 0; impIdx < imports.length; impIdx++)
+ {
+ if (imports[impIdx][RESOLVING_MODULE_IDX] == module)
+ {
+ if (list == null)
+ {
+ list = new ArrayList();
+ }
+ list.add(modules[modIdx]);
+ break;
+ }
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Utility method to get the import version number associated with a specific
+ * import identifier of the specified module.
+ * @param module the module to investigate.
+ * @param identifier the import identifier for which the version should
+ * be retrieved.
+ * @return the version number object or <tt>null</tt>.
+ **/
+ public static Object getImportVersion(Module module, Object identifier)
+ {
+ Object[][] imports = getImportsAttribute(module);
+ for (int i = 0; i < imports.length; i++)
+ {
+ if (imports[i][IDENTIFIER_IDX].equals(identifier))
+ {
+ return imports[i][VERSION_IDX];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Utility method to get the export version number associated with a specific
+ * export identifier of the specified module.
+ * @param module the module to investigate.
+ * @param identifier the export identifier for which the version should
+ * be retrieved.
+ * @return the version number object or <tt>null</tt>.
+ **/
+ public static Object getExportVersion(Module module, Object identifier)
+ {
+ Object[][] exports = getExportsAttribute(module);
+ for (int i = 0; i < exports.length; i++)
+ {
+ if (exports[i][IDENTIFIER_IDX].equals(identifier))
+ {
+ return exports[i][VERSION_IDX];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Utility method to get the resolving module of a specific import
+ * identifier for the specified module.
+ * @param module the module to investigate.
+ * @param identifier the import identifier for which the resolving
+ * module should be retrieved.
+ * @return the resolving module or <tt>null</tt>.
+ **/
+ public static Module getImportResolvingModule(Module module, Object identifier)
+ {
+ Object[][] imports = getImportsAttribute(module);
+
+ for (int i = 0; i < imports.length; i++)
+ {
+ if (imports[i][IDENTIFIER_IDX].equals(identifier))
+ {
+ return (Module) imports[i][RESOLVING_MODULE_IDX];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Utility method to get the resolving module of a specific export
+ * identifier for the specified module.
+ * @param module the module to investigate.
+ * @param identifier the export identifier for which the resolving
+ * module should be retrieved.
+ * @return the resolving module or <tt>null</tt>.
+ **/
+ public static Module getExportResolvingModule(Module module, Object identifier)
+ {
+ Object[][] exports = getExportsAttribute(module);
+
+ for (int i = 0; i < exports.length; i++)
+ {
+ if (exports[i][IDENTIFIER_IDX].equals(identifier))
+ {
+ return (Module) exports[i][RESOLVING_MODULE_IDX];
+ }
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ResolveException.java b/src/org/apache/osgi/moduleloader/search/ResolveException.java
new file mode 100755
index 0000000..efe6949
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ResolveException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search;
+
+import org.apache.osgi.framework.searchpolicy.R4Package;
+import org.apache.osgi.moduleloader.Module;
+
+/**
+ * <p>
+ * This exception is thrown if a module cannot be resolved. The module
+ * that failed to be resolved is recorded, along with the failed import target
+ * identifier and version number. If the error was a result of a propagation
+ * conflict, then the propagation error flag is set.
+ * </p>
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy#validate(org.apache.osgi.moduleloader.Module)
+**/
+public class ResolveException extends Exception
+{
+ private Module m_module = null;
+ private R4Package m_pkg = null;
+
+ /**
+ * Constructs an exception with the specified message, module,
+ * import identifier, import version number, and propagation flag.
+ **/
+ public ResolveException(String msg, Module module, R4Package pkg)
+ {
+ super(msg);
+ m_module = module;
+ m_pkg = pkg;
+ }
+
+ /**
+ * Returns the module that was being resolved.
+ * @return the module that was being resolved.
+ **/
+ public Module getModule()
+ {
+ return m_module;
+ }
+
+ public R4Package getPackage()
+ {
+ return m_pkg;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ResolveListener.java b/src/org/apache/osgi/moduleloader/search/ResolveListener.java
new file mode 100755
index 0000000..ebd8acd
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ResolveListener.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search;
+
+import java.util.EventListener;
+
+import org.apache.osgi.moduleloader.ModuleEvent;
+
+/**
+ * <p>
+ * This is an event listener interface for listening to resolution
+ * events that are generated by the <tt>R4SearchPolicy</tt>. Events
+ * are fired when a module is resolved and when it is unresolved.
+ * </p>
+ * @see org.apache.osgi.framework.searchpolicy.R4SearchPolicy
+**/
+public interface ResolveListener extends EventListener
+{
+ /**
+ * This is an event callback method that indicates that
+ * a module was resolved.
+ * @param event the module event containing the event data.
+ **/
+ public void moduleResolved(ModuleEvent event);
+
+ /**
+ * This is an event callback method that indicates that
+ * a module was unresolved.
+ * @param event the module event containing the event data.
+ **/
+ public void moduleUnresolved(ModuleEvent event);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/SelectionPolicy.java b/src/org/apache/osgi/moduleloader/search/SelectionPolicy.java
new file mode 100644
index 0000000..6ec8c44
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/SelectionPolicy.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search;
+
+import org.apache.osgi.moduleloader.Module;
+
+/**
+ * <p>
+ * This interface represents the policy for selecting a specific export
+ * target from multiple <i>compatible</i> candidate export targets when
+ * the <tt>ImportSearchPolicy</tt> is trying to resolve an import target
+ * for a given module. A concrete implementation of this interface is
+ * required to create an instance of <tt>ImportSearchPolicy</tt>.
+ * </p>
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy
+**/
+public interface SelectionPolicy
+{
+ /**
+ * Selects a single module to resolve the specified import
+ * from the array of compatible candidate modules.
+ * @param module the module that is importing the target.
+ * @param identifier the identifier of the import target.
+ * @param version the version number of the import target.
+ * @param candidates array of compatible candidate modules from which to choose.
+ * @param compatPolicy the compatibility policy that is being used.
+ * @return the selected module or <tt>null</tt> if no module
+ * can be selected.
+ **/
+ public Module select(
+ Module module, Object identifier, Object version, Module[] candidates,
+ CompatibilityPolicy compatPolicy);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/SelfContainedSearchPolicy.java b/src/org/apache/osgi/moduleloader/search/SelfContainedSearchPolicy.java
new file mode 100644
index 0000000..e03b061
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/SelfContainedSearchPolicy.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search;
+
+import java.net.URL;
+
+import org.apache.osgi.moduleloader.*;
+
+/**
+ * <p>
+ * This class implements a <tt>ModuleLoader</tt> search policy that
+ * assumes that all modules are self-contained. In other words, when
+ * loading a class or resource for a particular module, only that
+ * particular module's resource sources are search. No classes or
+ * resources are shared among modules.
+ * </p>
+ * @see org.apache.osgi.moduleloader.SearchPolicy
+ * @see org.apache.osgi.moduleloader.Module
+ * @see org.apache.osgi.moduleloader.ModuleClassLoader
+ * @see org.apache.osgi.moduleloader.ModuleManager
+**/
+public class SelfContainedSearchPolicy implements SearchPolicy
+{
+ private ModuleManager m_mgr = null;
+
+ /**
+ * This method is part of the <tt>SearchPolicy</tt> interface.
+ * This method is called by the <tt>ModuleManager</tt> once to
+ * give the search policy instance a reference to its associated
+ * module manager. This method should be implemented such that
+ * it cannot be called twice; calling this method a second time
+ * should produce an illegal state exception.
+ * @param mgr the module manager associated with this search policy.
+ * @throws java.lang.IllegalStateException if the method is called
+ * more than once.
+ **/
+ public void setModuleManager(ModuleManager mgr)
+ throws IllegalStateException
+ {
+ if (m_mgr == null)
+ {
+ m_mgr = mgr;
+ }
+ else
+ {
+ throw new IllegalStateException("Module manager is already initialized");
+ }
+ }
+
+ public Object[] definePackage(Module module, String pkgName)
+ {
+ return null;
+ }
+
+ /**
+ * Simply returns <tt>null</tt> which forces the module class
+ * loader to only search the target module's resource sources
+ * for the specified class.
+ * @param parent the parent class loader of the delegating class loader.
+ * @param module the target module that is loading the class.
+ * @param name the name of the class being loaded.
+ * @return <tt>null</tt>.
+ **/
+ public Class findClassBeforeModule(ClassLoader parent, Module module, String name)
+ {
+ // First, try to load from parent.
+ if (parent != null)
+ {
+ try
+ {
+ Class c = parent.loadClass(name);
+ if (c != null)
+ {
+ return c;
+ }
+ }
+ catch (ClassNotFoundException ex)
+ {
+ // Ignore.
+ }
+ }
+
+ return null;
+ }
+
+ public Class findClassAfterModule(ClassLoader parent, Module module, String name)
+ {
+ return null;
+ }
+
+ /**
+ * Simply returns <tt>null</tt> which forces the module class
+ * loader to only search the target module's resource sources
+ * for the specified resource.
+ * @param parent the parent class loader of the delegating class loader.
+ * @param module the target module that is loading the class.
+ * @param name the name of the resource being loaded.
+ * @return <tt>null</tt>.
+ **/
+ public URL findResource(ClassLoader parent, Module module, String name)
+ {
+ if (parent != null)
+ {
+ return parent.getResource(name);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ValidationException.java b/src/org/apache/osgi/moduleloader/search/ValidationException.java
new file mode 100644
index 0000000..15a8c5f
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ValidationException.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search;
+
+import org.apache.osgi.moduleloader.Module;
+
+/**
+ * <p>
+ * This exception is thrown if a module cannot be validated. The module
+ * that failed to be validated is recorded, along with the failed import target
+ * identifier and version number. If the error was a result of a propagation
+ * conflict, then the propagation error flag is set.
+ * </p>
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy#validate(org.apache.osgi.moduleloader.Module)
+**/
+public class ValidationException extends Exception
+{
+ private Module m_module = null;
+ private Object m_identifier = null;
+ private Object m_version = null;
+ private boolean m_isPropagation = false;
+
+ /**
+ * Constructs an exception with the specified message, module,
+ * import identifier, import version number, and propagation flag.
+ **/
+ public ValidationException(String msg, Module module,
+ Object identifier, Object version, boolean isPropagation)
+ {
+ super(msg);
+ m_module = module;
+ m_identifier = identifier;
+ m_version = version;
+ m_isPropagation = isPropagation;
+ }
+
+ /**
+ * Returns the module that was being validated.
+ * @return the module that was being validated.
+ **/
+ public Module getModule()
+ {
+ return m_module;
+ }
+
+ /**
+ * Returns the identifier of the import target that could not be resolved.
+ * @return the identifier of the import target that could not be resolved.
+ **/
+ public Object getIdentifier()
+ {
+ return m_identifier;
+ }
+
+ /**
+ * Returns the version number of the import target that could not be resolved.
+ * @return the version number of the import target that could not be resolved.
+ **/
+ public Object getVersion()
+ {
+ return m_version;
+ }
+
+ /**
+ * Returns a flag indicating whether the exception was caused by a
+ * a propagation conflict.
+ * @return <tt>true</tt> if the exception was thrown due to a propagation
+ * conflict, <tt>false</tt> otherwise.
+ **/
+ public boolean isPropagationError()
+ {
+ return m_isPropagation;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/ValidationListener.java b/src/org/apache/osgi/moduleloader/search/ValidationListener.java
new file mode 100644
index 0000000..656b46e
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/ValidationListener.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search;
+
+import java.util.EventListener;
+
+import org.apache.osgi.moduleloader.ModuleEvent;
+
+/**
+ * <p>
+ * This is an event listener interface for listening to validation
+ * events that are generated by the <tt>ImportSearchPolicy</tt>. Events
+ * are fired when a module is validated and when it is invalidated.
+ * </p>
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy
+**/
+public interface ValidationListener extends EventListener
+{
+ /**
+ * This is an event callback method that indicates that
+ * a module was validated.
+ * @param event the module event containing the event data.
+ **/
+ public void moduleValidated(ModuleEvent event);
+
+ /**
+ * This is an event callback method that indicates that
+ * a module was invalidated.
+ * @param event the module event containing the event data.
+ **/
+ public void moduleInvalidated(ModuleEvent event);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/compatibility/ExactCompatibilityPolicy.java b/src/org/apache/osgi/moduleloader/search/compatibility/ExactCompatibilityPolicy.java
new file mode 100644
index 0000000..61fca1c
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/compatibility/ExactCompatibilityPolicy.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search.compatibility;
+
+import org.apache.osgi.moduleloader.search.CompatibilityPolicy;
+
+/**
+ * This class implements a simple version numbering compatibility policy for the
+ * <tt>ImportSearchPolicy</tt> where only exact version numbers are considered
+ * to be compatible. This policy simply returns the result of
+ * "<tt>leftId.equals(rightId) && leftVersion.equals(rightVersion)</tt>". Any
+ * calls to the <tt>compare()</tt> method result in an exception since this
+ * policy has no basis for comparing identifiers and versions.
+ * @see org.apache.osgi.moduleloader.search.CompatibilityPolicy
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy
+**/
+public class ExactCompatibilityPolicy implements CompatibilityPolicy
+{
+ /**
+ * Compares two versioned identifiers, but since this policy has
+ * no understanding of how to compare identifiers, it always throws
+ * an <tt>IllegalArgumentException</tt>.
+ * @param leftId the identifier to test for compatibility.
+ * @param leftVersion the version number to test for compatibility.
+ * @param rightId the identifier used as the compatibility base line.
+ * @param rightVersion the version used as the compatibility base line.
+ * @return <tt>0</tt> if the identifiers are equal, <tt>-1</tt> if the
+ * left identifier is less then the right identifier, and <tt>1</tt>
+ * if the left identifier is greater than the right identifier.
+ * @throws java.lang.IllegalArgumentException if the two identifiers
+ * are not comparable, i.e., they refer to completely different
+ * entities.
+ **/
+ public int compare(
+ Object leftId, Object leftVersion,
+ Object rightId, Object rightVersion)
+ {
+ throw new IllegalArgumentException("Identifiers are not comparable.");
+ }
+
+ /**
+ * Returns whether the first import/export target is compatible
+ * with the second. This method simply uses the "<tt>equals()</tt>" method
+ * to test both the identifier and the verison number.
+ * @param leftId the identifier to test for compatibility.
+ * @param leftVersion the version number to test for compatibility.
+ * @param rightId the identifier used as the compatibility base line.
+ * @param rightVersion the version used as the compatibility base line.
+ * @return <tt>true</tt> if the left version number object is compatible
+ * with the right version number object, otherwise <tt>false</tt>.
+ **/
+ public boolean isCompatible(
+ Object leftId, Object leftVersion,
+ Object rightId, Object rightVersion)
+ {
+ return leftId.equals(rightId) && leftVersion.equals(rightVersion);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/selection/InteractiveSelectionPolicy.java b/src/org/apache/osgi/moduleloader/search/selection/InteractiveSelectionPolicy.java
new file mode 100644
index 0000000..06c234d
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/selection/InteractiveSelectionPolicy.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search.selection;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+import org.apache.osgi.moduleloader.Module;
+import org.apache.osgi.moduleloader.search.CompatibilityPolicy;
+import org.apache.osgi.moduleloader.search.SelectionPolicy;
+
+/**
+ * This class implements an interactive selection policy for the
+ * <tt>ImportSearchPolicy</tt>. This policy simply uses standard
+ * output to present the list of candidate modules and uses standard
+ * input to allow the user to select a specific module from the
+ * candidates. This selection policy is generally only useful for
+ * debugging purposes.
+ * @see org.apache.osgi.moduleloader.search.SelectionPolicy
+ * @see org.apache.osgi.moduleloader.search.ImportSearchPolicy
+**/
+public class InteractiveSelectionPolicy implements SelectionPolicy
+{
+ /**
+ * Returns a single package from an array of packages.
+ * @param sources array of packages from which to choose.
+ * @return the selected package or <tt>null</tt> if no package
+ * can be selected.
+ **/
+ public Module select(Module module, Object target,
+ Object version, Module[] candidates, CompatibilityPolicy compatPolicy)
+ {
+ try {
+ if (candidates.length == 1)
+ {
+ return candidates[0];
+ }
+ // Now start an interactive prompt.
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+ do
+ {
+ System.out.println("\nImporting '" + target
+ + "(" + version + ")" + "' for '" + module + "'.");
+ System.out.println("");
+ for (int i = 0; i < candidates.length; i++)
+ {
+ System.out.println((i + 1) + ". " + candidates[i]);
+ }
+ System.out.print("Select: ");
+ String s = br.readLine();
+
+ int choice = -1;
+ try {
+ choice = Integer.parseInt(s);
+ } catch (Exception ex) {
+ }
+
+ if (choice == 0)
+ {
+ break;
+ }
+ else if ((choice > 0) && (choice <= candidates.length))
+ {
+ return candidates[choice - 1];
+ }
+ }
+ while (true);
+ } catch (Exception ex) {
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/moduleloader/search/selection/SimpleSelectionPolicy.java b/src/org/apache/osgi/moduleloader/search/selection/SimpleSelectionPolicy.java
new file mode 100644
index 0000000..9b2daa7
--- /dev/null
+++ b/src/org/apache/osgi/moduleloader/search/selection/SimpleSelectionPolicy.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.moduleloader.search.selection;
+
+import java.util.*;
+
+import org.apache.osgi.moduleloader.*;
+import org.apache.osgi.moduleloader.search.*;
+
+/**
+ * This class implements a reasonably simple selection policy for the
+ * <tt>ImportSearchPolicy</tt>. When given a choice, this selection
+ * policy will always select the newest version of the available
+ * candidates to satisfy the import identifier. In the case where
+ * a candidate has already been selected for a given import identifier,
+ * then the previously selected module will be returned, if possible.
+ * If it is not possible to return the previously selected module, then
+ * a <tt>null</tt> is returned. This policy assumes that classes are
+ * shared globally.
+**/
+public class SimpleSelectionPolicy implements SelectionPolicy, ModuleListener
+{
+ private Map m_resolvedPackageMap = new HashMap();
+ private Map m_resolvedModuleMap = new HashMap();
+
+ /**
+ * Selects a single module to resolve the specified import identifier
+ * from the array of compatible candidate modules. If the import
+ * identifier has not been resolved before, then this selection policy
+ * chooses the module that exports the newest version of the
+ * import identifer. If the import identifier has been resolved already,
+ * then the same module that was chosen before is chosen again.
+ * This ensures that all modules use the same version of all
+ * exported classes.
+ * @param module the module that is importing the target.
+ * @param identifier the identifier of the import target.
+ * @param version the version number of the import target.
+ * @param candidates array of compatible candidate modules from which to choose.
+ * @return the selected module or <tt>null</tt> if no module
+ * can be selected.
+ **/
+ public synchronized Module select(Module module, Object identifier,
+ Object version, Module[] candidates, CompatibilityPolicy compatPolicy)
+ {
+ // See if package is already resolved.
+ Module selModule = (Module) m_resolvedPackageMap.get(identifier);
+
+ // If no module was previously selected to export the package,
+ // then try to choose one now.
+ if (selModule == null)
+ {
+ Object selVersion = null;
+
+ // Examine all exported instances of the identifier and
+ // choose the one with the newest version number. If
+ // there is more than one source for the newest version,
+ // then just select the first one found.
+ for (int i = 0; i < candidates.length; i++)
+ {
+ Object tmpVersion =
+ ImportSearchPolicy.getExportVersion(candidates[i], identifier);
+
+ // If this is the first comparison, then
+ // just record it.
+ if (selVersion == null)
+ {
+ selModule = candidates[i];
+ selVersion = tmpVersion;
+ }
+ // If the current export package version is greater
+ // than the selected export package version, then
+ // record it instead.
+ else if (compatPolicy.compare(identifier, tmpVersion, identifier, selVersion) >= 0)
+ {
+ selModule = candidates[i];
+ selVersion = tmpVersion;
+ }
+ }
+
+ m_resolvedPackageMap.put(identifier, selModule);
+ m_resolvedModuleMap.put(selModule, selModule);
+ }
+ // See if the previously selected export module satisfies
+ // the current request, otherwise return null.
+ else
+ {
+ Object selVersion =
+ ImportSearchPolicy.getExportVersion(selModule, identifier);
+ Module tmpModule = selModule;
+ selModule = null;
+ if (compatPolicy.isCompatible(identifier, selVersion, identifier, version))
+ {
+ selModule = tmpModule;
+ }
+ }
+
+ return selModule;
+ }
+
+ public void moduleAdded(ModuleEvent event)
+ {
+ }
+
+ public void moduleReset(ModuleEvent event)
+ {
+ moduleRemoved(event);
+ }
+
+ public synchronized void moduleRemoved(ModuleEvent event)
+ {
+ // If the module that was removed was chosen for
+ // exporting identifier, then flush it from our
+ // data structures; we assume here that the application
+ // will flush references to the removed module's classes.
+ if (m_resolvedModuleMap.get(event.getModule()) != null)
+ {
+ // Remove from module map.
+ m_resolvedModuleMap.remove(event.getModule());
+ // Remove each exported package from package map.
+ Iterator iter = m_resolvedPackageMap.entrySet().iterator();
+ while (iter.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ if (entry.getValue() == event.getModule())
+ {
+ iter.remove();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/BundleRecord.java b/src/org/apache/osgi/service/bundlerepository/BundleRecord.java
new file mode 100644
index 0000000..906979a
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/BundleRecord.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.service.bundlerepository;
+
+import java.io.PrintStream;
+import java.lang.reflect.Array;
+import java.util.*;
+
+/**
+ * A simple interface used to hold meta-data about bundles
+ * contained in a bundle repository.
+**/
+public class BundleRecord
+{
+ public static final String BUNDLE_NAME = "Bundle-Name";
+ public static final String BUNDLE_SYMBOLICNAME = "Bundle-SymbolicName";
+ public static final String BUNDLE_VERSION = "Bundle-Version";
+ public static final String BUNDLE_UPDATELOCATION = "Bundle-UpdateLocation";
+ public static final String BUNDLE_URL = "Bundle-URL";
+ public static final String BUNDLE_SOURCEURL = "Bundle-SourceURL";
+ public static final String BUNDLE_DOCURL = "Bundle-DocURL";
+ public static final String BUNDLE_LICENSEURL = "Bundle-LicenseURL";
+ public static final String BUNDLE_DESCRIPTION = "Bundle-Description";
+ public static final String BUNDLE_CATEGORY = "Bundle-Category";
+ public static final String BUNDLE_VENDOR = "Bundle-Vendor";
+ public static final String BUNDLE_CONTACTADDRESS = "Bundle-ContactAddress";
+ public static final String BUNDLE_COPYRIGHT = "Bundle-Copyright";
+ public static final String BUNDLE_REQUIREDEXECUTIONENVIRONMENT = "Bundle-RequiredExecutionEnvironment";
+ public static final String BUNDLE_NATIVECODE = "Bundle-NativeCode";
+ public static final String IMPORT_PACKAGE = "Import-Package";
+ public static final String EXPORT_PACKAGE = "Export-Package";
+ public static final String DYNAMICIMPORT_PACKAGE = "DynamicImport-Package";
+ public static final String REQUIRE_SERVICE = "Require-Service";
+ public static final String PROVIDE_SERVICE = "Provide-Service";
+
+ private Map m_attrMap = null;
+ private Dictionary m_dict = null;
+
+ /**
+ * <p>
+ * Constructs a bundle record using the values of the supplied
+ * map as the meta-data values for the bundle. The supplied map
+ * is copied, but its values are not.
+ * </p>
+ * @param attrMap a map containing attribute-value pairs of meta-data
+ * for a bundle.
+ **/
+ public BundleRecord(Map attrMap)
+ {
+ // Create a case insensitive map.
+ m_attrMap = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+ m_attrMap.putAll(attrMap);
+ }
+
+ /**
+ * <p>
+ * Returns a dictionary object which can be used with
+ * <tt>org.osgi.framework.Filter</tt>, for example. The returned
+ * dictionary object is a minimum implementation, where only
+ * the <tt>get()</tt>, <tt>size()</tt>, and <tt>isEmpty()</tt>
+ * methods do anything useful.
+ * </p>
+ * @return a dictionary object for accessing the bundle record attributes.
+ **/
+ public synchronized Dictionary getDictionary()
+ {
+ if (m_dict == null)
+ {
+ m_dict = new Dictionary() {
+ public int size()
+ {
+ return m_attrMap.size();
+ }
+
+ public boolean isEmpty()
+ {
+ return m_attrMap.isEmpty();
+ }
+
+ public Enumeration elements()
+ {
+ throw new UnsupportedOperationException("Not implemented.");
+ }
+
+ public Enumeration keys()
+ {
+ throw new UnsupportedOperationException("Not implemented.");
+ }
+
+ public Object get(Object key)
+ {
+ return m_attrMap.get(key);
+ }
+
+ public Object remove(Object key)
+ {
+ throw new UnsupportedOperationException("Not implemented.");
+ }
+
+ public Object put(Object key, Object value)
+ {
+ throw new UnsupportedOperationException("Not implemented.");
+ }
+ };
+ }
+
+ return m_dict;
+ }
+
+ /**
+ * <p>
+ * Returns an array containing all attribute names associated with
+ * the bundle record. The return array is a copy and can be freely
+ * modified.
+ * </p>
+ * @return an array containing the attribute names contained in the
+ * bundle record.
+ **/
+ public String[] getAttributes()
+ {
+ return (String[]) m_attrMap.keySet().toArray(new String[m_attrMap.size()]);
+ }
+
+ /**
+ * <p>
+ * Returns the value of the specified attribute. If the value is an array,
+ * then a copy is returned.
+ * </p>
+ * @param name the attribute name for which to retrieve its value.
+ * @return the value of the specified attribute or <tt>null</tt> if
+ * the specified attribute does not exist.
+ **/
+ public Object getAttribute(String name)
+ {
+ Object obj = m_attrMap.get(name);
+ // If the value is an array, then make a copy
+ // since arrays are mutable.
+ if ((obj != null) && obj.getClass().isArray())
+ {
+ Class clazz = obj.getClass().getComponentType();
+ int len = Array.getLength(obj);
+ Object copy = Array.newInstance(obj.getClass().getComponentType(), len);
+ System.arraycopy(obj, 0, copy, 0, len);
+ obj = copy;
+ }
+ return obj;
+ }
+
+ /**
+ * <p>
+ * Dumps the contents of the bundle record to the specified print stream.
+ * </p>
+ * @param out the print stream to use for printing.
+ **/
+ public void printAttributes(PrintStream out)
+ {
+ String[] attrs = getAttributes();
+ if (attrs != null)
+ {
+ Arrays.sort(attrs);
+ for (int i = 0; (attrs != null) && (i < attrs.length); i++)
+ {
+ Object obj = getAttribute(attrs[i]);
+ if (obj.getClass().isArray())
+ {
+ out.println(attrs[i] + ":");
+ for (int j = 0; j < Array.getLength(obj); j++)
+ {
+ out.println(" " + Array.get(obj, j));
+ }
+ }
+ else
+ {
+ out.println(attrs[i] + ": " + getAttribute(attrs[i]));
+ }
+ }
+ }
+ }
+
+ public String toString()
+ {
+ return (String) m_attrMap.get(BUNDLE_NAME);
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/BundleRepository.java b/src/org/apache/osgi/service/bundlerepository/BundleRepository.java
new file mode 100644
index 0000000..3fe266b
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/BundleRepository.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.service.bundlerepository;
+
+import java.io.PrintStream;
+
+/**
+ * This interface defines a simple bundle repository service.
+**/
+public interface BundleRepository
+{
+ /**
+ * Get URL list of repositories.
+ * @return a space separated list of URLs to use or <tt>null</tt>
+ * to refresh the cached list of bundles.
+ **/
+ public String[] getRepositoryURLs();
+
+ /**
+ * Set URL list of repositories.
+ * @param url a space separated list of URLs to use or <tt>null</tt>
+ * to refresh the cached list of bundles.
+ **/
+ public void setRepositoryURLs(String[] urls);
+
+ /**
+ * Returns an array of the bundle symbolic names available
+ * in the repository.
+ * @return An arry of available bundle symbolic names.
+ **/
+ public BundleRecord[] getBundleRecords();
+
+ /**
+ * Get the specified bundle record from the repository.
+ * @param i the bundle record index to retrieve.
+ * @return the associated bundle record or <tt>null</tt>.
+ **/
+ public BundleRecord[] getBundleRecords(String symname);
+
+ /**
+ * Get bundle record for the bundle with the specified name
+ * and version from the repository.
+ * @param name the bundle record name to retrieve.
+ * @param version three-interger array of the version associated with
+ * the name to retrieve.
+ * @return the associated bundle record or <tt>null</tt>.
+ **/
+ public BundleRecord getBundleRecord(String symname, int[] version);
+
+ /**
+ * Deploys the bundle in the repository that corresponds to
+ * the specified update location. The action taken depends on
+ * whether the specified bundle is already installed in the local
+ * framework. If the bundle is already installed, then this
+ * method will attempt to update it. If the bundle is not already
+ * installed, then this method will attempt to install it.
+ * @param out the stream to use for informational messages.
+ * @param err the stream to use for error messages.
+ * @param symname the symbolic name of the bundle to deploy.
+ * @param isResolve a flag to indicates whether dependencies should
+ * should be resolved.
+ * @param isStart a flag to indicate whether installed bundles should
+ * be started.
+ * @return <tt>true</tt> if successful, <tt>false</tt> otherwise.
+ **/
+ public boolean deployBundle(
+ PrintStream out, PrintStream err, String symname, int[] version,
+ boolean isResolve, boolean isStart);
+
+ /**
+ * Returns an array containing all bundle records in the
+ * repository that resolve the transitive closure of the
+ * passed in array of package declarations.
+ * @param pkgs an array of package declarations to resolve.
+ * @return an array containing all bundle records in the
+ * repository that resolve the transitive closure of
+ * the passed in array of package declarations.
+ * @throws ResolveException if any packages in the transitive
+ * closure of packages cannot be resolved.
+ **/
+ public BundleRecord[] resolvePackages(IPackage[] pkgs)
+ throws ResolveException;
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/IAttribute.java b/src/org/apache/osgi/service/bundlerepository/IAttribute.java
new file mode 100644
index 0000000..13d5f57
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/IAttribute.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.service.bundlerepository;
+
+public interface IAttribute
+{
+ public String getName();
+
+ public String getValue();
+
+ public boolean isMandatory();
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/IDirective.java b/src/org/apache/osgi/service/bundlerepository/IDirective.java
new file mode 100644
index 0000000..1d407e3
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/IDirective.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.service.bundlerepository;
+
+public interface IDirective
+{
+ public String getName();
+
+ public String getValue();
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/IPackage.java b/src/org/apache/osgi/service/bundlerepository/IPackage.java
new file mode 100644
index 0000000..0877c69
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/IPackage.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.service.bundlerepository;
+
+public interface IPackage
+{
+ public String getId();
+
+ public IDirective[] getDirectives();
+
+ public IAttribute[] getAttributes();
+
+ public IVersion getVersionLow();
+
+ public IVersion getVersionHigh();
+
+ public boolean isOptional();
+
+ public boolean doesSatisfy(IPackage pkg);
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/IVersion.java b/src/org/apache/osgi/service/bundlerepository/IVersion.java
new file mode 100644
index 0000000..1433d9d
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/IVersion.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.service.bundlerepository;
+
+public interface IVersion extends Comparable
+{
+ public boolean equals(Object object);
+
+ public int getMajorComponent();
+
+ public int getMinorComponent();
+
+ public int getMicroComponent();
+
+ public String getQualifierComponent();
+
+ public boolean isInclusive();
+
+ public int compareTo(Object o);
+
+ public String toString();
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/bundlerepository/ResolveException.java b/src/org/apache/osgi/service/bundlerepository/ResolveException.java
new file mode 100644
index 0000000..c3a52c9
--- /dev/null
+++ b/src/org/apache/osgi/service/bundlerepository/ResolveException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.service.bundlerepository;
+
+public class ResolveException extends Exception
+{
+ private IPackage m_pkg = null;
+
+ public ResolveException(IPackage pkg)
+ {
+ m_pkg = pkg;
+ }
+
+ public IPackage getPackage()
+ {
+ return m_pkg;
+ }
+}
\ No newline at end of file
diff --git a/src/org/apache/osgi/service/shell/CdCommand.java b/src/org/apache/osgi/service/shell/CdCommand.java
new file mode 100644
index 0000000..1713175
--- /dev/null
+++ b/src/org/apache/osgi/service/shell/CdCommand.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.service.shell;
+
+/**
+ * This interface defines the <tt>cd</tt> command service interface for the
+ * Felix shell service. The <tt>cd</tt> command does not really change the
+ * directory of the shell, rather it maintains a base URL for
+ * simplifying URL entry.
+ * <p>
+ * For example, if the base URL is <tt>http://www.foo.com/<tt> and you
+ * try to install a bundle <tt>foo.jar</tt>, the actual URL will be
+ * expanded to <tt>http://www.foo.com/foo.jar</tt>. Any bundles wishing
+ * to retrieve or set the current directory of the shell can use this
+ * service interface.
+**/
+public interface CdCommand extends Command
+{
+ /**
+ * Property used to configure the base URL.
+ **/
+ public static final String BASE_URL_PROPERTY = "felix.shell.baseurl";
+
+ /**
+ * Returns the current <i>directory</i> of the shell service.
+ * @return the current shell directory.
+ **/
+ public String getBaseURL();
+
+ /**
+ * Sets the current <i>directory</i> of the shell service.
+ * @param s the new value for the base URL.
+ **/
+ public void setBaseURL(String s);
+}
diff --git a/src/org/apache/osgi/service/shell/Command.java b/src/org/apache/osgi/service/shell/Command.java
new file mode 100644
index 0000000..65c5852
--- /dev/null
+++ b/src/org/apache/osgi/service/shell/Command.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.service.shell;
+
+import java.io.PrintStream;
+
+/**
+ * This interface is used to define commands for the Felix shell
+ * service. Any bundle wishing to create commands for the
+ * shell service simply needs to create a service object that
+ * implements this interface and then register it with the OSGi
+ * framework. The shell service automatically includes any
+ * registered command services in its list of available commands.
+**/
+public interface Command
+{
+ /**
+ * Returns the name of the command that is implemented by the
+ * interface. The command name should not contain whitespace
+ * and should also be unique.
+ * @return the name of the command.
+ **/
+ public String getName();
+
+ /**
+ * Returns the usage string for the command. The usage string is
+ * a short string that illustrates how to use the command on the
+ * command line. This information is used when generating command
+ * help information. An example usage string for the <tt>install</tt>
+ * command is:
+ * <pre>
+ * install <URL> [<URL> ...]
+ * </pre>
+ * @return the usage string for the command.
+ **/
+ public String getUsage();
+
+ /**
+ * Returns a short description of the command; this description
+ * should be as short as possible. This information is used when
+ * generating the command help information.
+ * @return a short description of the command.
+ **/
+ public String getShortDescription();
+
+ /**
+ * Executes the command using the supplied command line, output
+ * print stream, and error print stream.
+ * @param line the complete command line, including the command name.
+ * @param out the print stream to use for standard output.
+ * @param err the print stream to use for standard error.
+ **/
+ public void execute(String line, PrintStream out, PrintStream err);
+}
diff --git a/src/org/apache/osgi/service/shell/ShellService.java b/src/org/apache/osgi/service/shell/ShellService.java
new file mode 100644
index 0000000..4f1178d
--- /dev/null
+++ b/src/org/apache/osgi/service/shell/ShellService.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.osgi.service.shell;
+
+import java.io.PrintStream;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * This interface defines the Felix shell service. The shell service
+ * is an extensible, user interface neutral shell for controlling and
+ * interacting with the framework. In general, the shell service assumes that
+ * it is operating in a command line fashion, i.e., it receives a
+ * complete command line, parses it, and executes the corresponding
+ * command, but graphical interfaces are also possible.
+ * <p>
+ * All commands in the shell service are actually implemented as OSGi
+ * services; these services implement the <tt>Command</tt> service
+ * interface. Any bundle can implement custom commands by creating
+ * command services and registering them with the OSGi framework.
+**/
+public interface ShellService
+{
+ /**
+ * Returns an array of command names available in the shell service.
+ * @return an array of available command names or an empty array.
+ **/
+ public String[] getCommands();
+
+ /**
+ * Returns the usage string associated with the specified command name.
+ * @param name the name of the target command.
+ * @return the usage string of the specified command or null.
+ **/
+ public String getCommandUsage(String name);
+
+ /**
+ * Returns the description associated with the specified command name.
+ * @param name the name of the target command.
+ * @return the description of the specified command or null.
+ **/
+ public String getCommandDescription(String name);
+
+ /**
+ * Returns the service reference associated with the specified
+ * command name.
+ * @param name the name of the target command.
+ * @return the description of the specified command or null.
+ **/
+ public ServiceReference getCommandReference(String name);
+
+ /**
+ *
+ * This method executes the supplied command line using the
+ * supplied output and error print stream. The assumption of
+ * this method is that a command line will be typed by the user
+ * (or perhaps constructed by a GUI) and passed into it for
+ * execution. The command line is interpretted in a very
+ * simplistic fashion; it takes the leading string of characters
+ * terminated by a space character (not including it) and
+ * assumes that this leading token is the command name. For an
+ * example, consider the following command line:
+ * </p>
+ * <pre>
+ * update 3 http://www.foo.com/bar.jar
+ * </pre>
+ * <p>
+ * This is interpretted as an <tt>update</tt> command; as a
+ * result, the entire command line (include command name) is
+ * passed into the <tt>execute()</tt> method of the command
+ * service with the name <tt>update</tt> if one exists. If the
+ * corresponding command service is not found, then an error
+ * message is printed to the error print stream.
+ * @param commandLine the command line to execute.
+ * @param out the standard output print stream.
+ * @param err the standard error print stream.
+ **/
+ public void executeCommand(
+ String commandLine, PrintStream out, PrintStream err)
+ throws Exception;
+}
\ No newline at end of file