Added new bundle repository service implementation.
git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@391296 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.felix.bundlerepository/pom.xml b/org.apache.felix.bundlerepository/pom.xml
new file mode 100644
index 0000000..4346850
--- /dev/null
+++ b/org.apache.felix.bundlerepository/pom.xml
@@ -0,0 +1,54 @@
+<project>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>osgi-bundle</packaging>
+ <name>Apache Felix Bundle Repository Service</name>
+ <artifactId>org.apache.felix.bundlerepository</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>${pom.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.apache.felix.shell</artifactId>
+ <version>${pom.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>kxml2</groupId>
+ <artifactId>kxml2</artifactId>
+ <version>2.2.2</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix.plugins</groupId>
+ <artifactId>maven-osgi-plugin</artifactId>
+ <version>${pom.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <osgiManifest>
+ <bundleName>BundleRepository</bundleName>
+ <bundleDescription>Bundle repository service for Felix (or any other OSGi framework).</bundleDescription>
+ <bundleActivator>org.apache.felix.bundlerepository.Activator</bundleActivator>
+ <bundleDocUrl>http://oscar-osgi.sf.net/obr2/bundlerepository/</bundleDocUrl>
+ <bundleSource>http://oscar-osgi.sf.net/obr2/bundlerepository/org.apache.felix.bundlerepository-src.jar</bundleSource>
+ <bundleSymbolicName>org.apache.felix.bundlerepository</bundleSymbolicName>
+ <importPackage>org.osgi.framework</importPackage>
+ <dynamicImportPackage>org.apache.felix.shell</dynamicImportPackage>
+ <exportPackage>org.osgi.service.obr</exportPackage>
+ <exportService>org.osgi.service.obr.RepositoryAdmin</exportService>
+ </osgiManifest>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/Activator.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/Activator.java
new file mode 100644
index 0000000..1f604f5
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/Activator.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.obr.RepositoryAdmin;
+
+public class Activator implements BundleActivator
+{
+ private transient BundleContext m_context = null;
+ private transient RepositoryAdminImpl m_repoAdmin = null;
+
+ public void start(BundleContext context)
+ {
+ m_context = context;
+
+ // Register bundle repository service.
+ m_repoAdmin = new RepositoryAdminImpl(m_context);
+ context.registerService(
+ RepositoryAdmin.class.getName(),
+ m_repoAdmin, null);
+
+ // We dynamically import the impl 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" impl command service as a
+ // wrapper for the bundle repository service.
+ context.registerService(
+ org.apache.felix.shell.Command.class.getName(),
+ new ObrCommandImpl(m_context, m_repoAdmin), null);
+ }
+ catch (Throwable th)
+ {
+ // Ignore.
+ }
+ }
+
+ public void stop(BundleContext context)
+ {
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/CapabilityImpl.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/CapabilityImpl.java
new file mode 100644
index 0000000..52a1fd6
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/CapabilityImpl.java
@@ -0,0 +1,41 @@
+package org.apache.felix.bundlerepository;
+
+import java.util.*;
+
+import org.osgi.service.obr.Capability;
+
+public class CapabilityImpl implements Capability
+{
+ private String m_name = null;
+ private Map m_map = null;
+
+ public CapabilityImpl()
+ {
+ m_map = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public void setName(String name)
+ {
+ m_name = name;
+ }
+
+ public Map getProperties()
+ {
+ return m_map;
+ }
+
+ public void addP(PropertyImpl prop)
+ {
+ m_map.put(prop.getN(), prop.getV());
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/CategoryImpl.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/CategoryImpl.java
new file mode 100644
index 0000000..45c3589
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/CategoryImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+public class CategoryImpl
+{
+ String m_id = null;
+
+ public void setId(String id)
+ {
+ m_id = id;
+ }
+
+ public String getId()
+ {
+ return m_id;
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/FileUtil.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/FileUtil.java
new file mode 100644
index 0000000..933fec9
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/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.felix.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,
+ URL srcURL, String dirStr, boolean extract)
+ {
+ // Get the file name from the URL.
+ String fileName = (srcURL.getFile().lastIndexOf('/') > 0)
+ ? srcURL.getFile().substring(srcURL.getFile().lastIndexOf('/') + 1)
+ : srcURL.getFile();
+
+ 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 = 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/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/IteratorToEnumeration.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/IteratorToEnumeration.java
new file mode 100644
index 0000000..34c2b70
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/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.felix.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/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/LocalRepositoryImpl.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/LocalRepositoryImpl.java
new file mode 100644
index 0000000..661a3b0
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/LocalRepositoryImpl.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+import java.net.URL;
+import java.util.*;
+
+import org.osgi.framework.*;
+import org.osgi.service.obr.*;
+
+public class LocalRepositoryImpl implements Repository
+{
+ private BundleContext m_context = null;
+ private long m_currentTimeStamp = 0;
+ private long m_snapshotTimeStamp = 0;
+ private List m_localResourceList = new ArrayList();
+ private BundleListener m_bundleListener = null;
+
+ public LocalRepositoryImpl(BundleContext context)
+ {
+ m_context = context;
+ initialize();
+ }
+
+ public void dispose()
+ {
+ m_context.removeBundleListener(m_bundleListener);
+ }
+
+ public URL getURL()
+ {
+ return null;
+ }
+
+ public String getName()
+ {
+ return "Locally Installed Repository";
+ }
+
+ public synchronized long getLastModified()
+ {
+ return m_snapshotTimeStamp;
+ }
+
+ public synchronized long getCurrentTimeStamp()
+ {
+ return m_currentTimeStamp;
+ }
+
+ public synchronized Resource[] getResources()
+ {
+ return (Resource[]) m_localResourceList.toArray(new Resource[m_localResourceList.size()]);
+ }
+
+ private void initialize()
+ {
+ // Create a bundle listener to list for events that
+ // change the state of the framework.
+ m_bundleListener = new SynchronousBundleListener() {
+ public void bundleChanged(BundleEvent event)
+ {
+ synchronized (LocalRepositoryImpl.this)
+ {
+ m_currentTimeStamp = new Date().getTime();
+ }
+ }
+ };
+ m_context.addBundleListener(m_bundleListener);
+
+ // Generate the resource list from the set of installed bundles.
+ // Lock so we can ensure that no bundle events arrive before we
+ // are done getting our state snapshot.
+ Bundle[] bundles = null;
+ synchronized (this)
+ {
+ m_snapshotTimeStamp = m_currentTimeStamp = new Date().getTime();
+ bundles = m_context.getBundles();
+ }
+
+ // Create a local resource object for each bundle, which will
+ // convert the bundle headers to the appropriate resource metadata.
+ for (int i = 0; (bundles != null) && (i < bundles.length); i++)
+ {
+ m_localResourceList.add(new LocalResourceImpl(bundles[i]));
+ }
+ }
+
+ public static class LocalResourceImpl extends ResourceImpl
+ {
+ private Bundle m_bundle = null;
+
+ LocalResourceImpl(Bundle bundle)
+ {
+ this(null, bundle);
+ }
+
+ LocalResourceImpl(ResourceImpl resource, Bundle bundle)
+ {
+ super(resource);
+ m_bundle = bundle;
+ initialize();
+ }
+
+ public Bundle getBundle()
+ {
+ return m_bundle;
+ }
+
+ private void initialize()
+ {
+ Dictionary dict = m_bundle.getHeaders();
+
+ // Convert bundle manifest header attributes to resource properties.
+ convertAttributesToProperties(dict);
+
+ // Convert import package declarations into requirements.
+ convertImportPackageToRequirement(dict);
+
+ // Convert import service declarations into requirements.
+ convertImportServiceToRequirement(dict);
+
+ // Convert export package declarations into capabilities.
+ convertExportPackageToCapability(dict);
+
+ // Convert export service declarations into capabilities.
+ convertExportServiceToCapability(dict);
+
+ // For the system bundle, add a special platform capability.
+ if (m_bundle.getBundleId() == 0)
+ {
+/* TODO: OBR - Fix system capabilities.
+ // 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);
+*/
+ }
+ }
+
+ private void convertAttributesToProperties(Dictionary dict)
+ {
+ for (Enumeration keys = dict.keys(); keys.hasMoreElements(); )
+ {
+ String key = (String) keys.nextElement();
+ if (key.equalsIgnoreCase(Constants.BUNDLE_SYMBOLICNAME))
+ {
+ put(Resource.SYMBOLIC_NAME, (String) dict.get(key));
+ }
+ else if (key.equalsIgnoreCase(Constants.BUNDLE_NAME))
+ {
+ put(Resource.PRESENTATION_NAME, (String) dict.get(key));
+ }
+ else if (key.equalsIgnoreCase(Constants.BUNDLE_VERSION))
+ {
+ put(Resource.VERSION, (String) dict.get(key));
+ }
+ else if (key.equalsIgnoreCase("Bundle-Source"))
+ {
+ put(Resource.SOURCE_URL, (String) dict.get(key));
+ }
+ else if (key.equalsIgnoreCase(Constants.BUNDLE_DESCRIPTION))
+ {
+ put(Resource.DESCRIPTION, (String) dict.get(key));
+ }
+ else if (key.equalsIgnoreCase(Constants.BUNDLE_DOCURL))
+ {
+ put(Resource.DOCUMENTATION_URL, (String) dict.get(key));
+ }
+ else if (key.equalsIgnoreCase(Constants.BUNDLE_COPYRIGHT))
+ {
+ put(Resource.COPYRIGHT, (String) dict.get(key));
+ }
+ else if (key.equalsIgnoreCase("Bundle-License"))
+ {
+ put(Resource.LICENSE_URL, (String) dict.get(key));
+ }
+ }
+ }
+
+ private void convertImportPackageToRequirement(Dictionary dict)
+ {
+ String target = (String) dict.get(Constants.IMPORT_PACKAGE);
+ if (target != null)
+ {
+ R4Package[] pkgs = R4Package.parseImportOrExportHeader(target);
+ R4Import[] imports = new R4Import[pkgs.length];
+ for (int i = 0; i < pkgs.length; i++)
+ {
+ imports[i] = new R4Import(pkgs[i]);
+ }
+
+ for (int impIdx = 0; impIdx < imports.length; impIdx++)
+ {
+ String low = imports[impIdx].isLowInclusive()
+ ? "(version>=" + imports[impIdx].getVersion() + ")"
+ : "(!(version<=" + imports[impIdx].getVersion() + ")";
+
+ if (imports[impIdx].getVersionHigh() != null)
+ {
+ String high = imports[impIdx].isHighInclusive()
+ ? "(version<=" + imports[impIdx].getVersionHigh() + ")"
+ : "(!(version>=" + imports[impIdx].getVersionHigh() + ")";
+ RequirementImpl req = new RequirementImpl();
+ req.setMultiple("false");
+ req.setName("package");
+ req.addText("Import package " + imports[impIdx].toString());
+ req.setFilter("(&(package="
+ + imports[impIdx].getName() + ")"
+ + low + high + ")");
+ addRequire(req);
+ }
+ else
+ {
+ RequirementImpl req = new RequirementImpl();
+ req.setMultiple("false");
+ req.setName("package");
+ req.addText("Import package " + imports[impIdx].toString());
+ req.setFilter(
+ "(&(package="
+ + imports[impIdx].getName() + ")"
+ + low + ")");
+ addRequire(req);
+ }
+ }
+ }
+ }
+
+ private void convertImportServiceToRequirement(Dictionary dict)
+ {
+ String target = (String) dict.get(Constants.IMPORT_SERVICE);
+ if (target != null)
+ {
+ R4Package[] pkgs = R4Package.parseImportOrExportHeader(target);
+ for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+ {
+ RequirementImpl req = new RequirementImpl();
+ req.setMultiple("false");
+ req.setName("service");
+ req.addText("Import service " + pkgs[pkgIdx].toString());
+ req.setFilter("(service="
+ + pkgs[pkgIdx].getName() + ")");
+ addRequire(req);
+ }
+ }
+ }
+
+ private void convertExportPackageToCapability(Dictionary dict)
+ {
+ String target = (String) dict.get(Constants.EXPORT_PACKAGE);
+ if (target != null)
+ {
+ R4Package[] pkgs = R4Package.parseImportOrExportHeader(target);
+ for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+ {
+ CapabilityImpl cap = new CapabilityImpl();
+ cap.setName("package");
+ cap.addP(new PropertyImpl("package", null, pkgs[pkgIdx].getName()));
+ cap.addP(new PropertyImpl("version", "version", pkgs[pkgIdx].getVersion().toString()));
+ addCapability(cap);
+ }
+ }
+ }
+
+ private void convertExportServiceToCapability(Dictionary dict)
+ {
+ String target = (String) dict.get(Constants.EXPORT_SERVICE);
+ if (target != null)
+ {
+ R4Package[] pkgs = R4Package.parseImportOrExportHeader(target);
+ for (int pkgIdx = 0; (pkgs != null) && (pkgIdx < pkgs.length); pkgIdx++)
+ {
+ CapabilityImpl cap = new CapabilityImpl();
+ cap.setName("service");
+ cap.addP(new PropertyImpl("service", null, pkgs[pkgIdx].getName()));
+ addCapability(cap);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/MapToDictionary.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/MapToDictionary.java
new file mode 100644
index 0000000..0bd6e80
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/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.felix.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/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ObrCommandImpl.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ObrCommandImpl.java
new file mode 100644
index 0000000..5935e92
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ObrCommandImpl.java
@@ -0,0 +1,1087 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+import java.io.*;
+import java.lang.reflect.Array;
+import java.net.*;
+import java.util.*;
+
+import org.apache.felix.shell.Command;
+import org.osgi.framework.*;
+import org.osgi.service.obr.*;
+
+public class ObrCommandImpl implements Command
+{
+ private static final String HELP_CMD = "help";
+ private static final String ADDURL_CMD = "add-url";
+ private static final String REMOVEURL_CMD = "remove-url";
+ private static final String LISTURL_CMD = "list-url";
+ 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 START_CMD = "start";
+ private static final String SOURCE_CMD = "source";
+
+ private static final String EXTRACT_SWITCH = "-x";
+
+ private BundleContext m_context = null;
+ private RepositoryAdmin m_repoAdmin = null;
+
+ public ObrCommandImpl(BundleContext context, RepositoryAdmin repoAdmin)
+ {
+ m_context = context;
+ m_repoAdmin = repoAdmin;
+ }
+
+ 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(ADDURL_CMD) ||
+ command.equals(REMOVEURL_CMD) ||
+ command.equals(LISTURL_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(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 "url" command.
+ st.nextToken();
+
+ int count = st.countTokens();
+ if (count > 0)
+ {
+ while (st.hasMoreTokens())
+ {
+ if (command.equals(ADDURL_CMD))
+ {
+ try
+ {
+ m_repoAdmin.addRepository(new URL(st.nextToken()));
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace(err);
+ }
+ }
+ else
+ {
+ m_repoAdmin.removeRepository(new URL(st.nextToken()));
+ }
+ }
+ }
+ else
+ {
+ Repository[] repos = m_repoAdmin.listRepositories();
+ if ((repos != null) && (repos.length > 0))
+ {
+ for (int i = 0; i < repos.length; i++)
+ {
+ out.println(repos[i].getURL());
+ }
+ }
+ 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;
+ }
+ }
+
+ StringBuffer sb = new StringBuffer();
+ if ((substr == null) || (substr.length() == 0))
+ {
+ sb.append("(|(presentationname=*)(symbolicname=*))");
+ }
+ else
+ {
+ sb.append("(|(presentationname=*");
+ sb.append(substr);
+ sb.append("*)(symbolicname=*");
+ sb.append(substr);
+ sb.append("*))");
+ }
+ Resource[] resources = m_repoAdmin.discoverResources(sb.toString());
+ for (int resIdx = 0; (resources != null) && (resIdx < resources.length); resIdx++)
+ {
+ String name = resources[resIdx].getPresentationName();
+ Version version = resources[resIdx].getVersion();
+ if (version != null)
+ {
+ out.println(name + " (" + version + ")");
+ }
+ else
+ {
+ out.println(name);
+ }
+ }
+
+ if (resources == null)
+ {
+ 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 cmdIdx = 0; (pc != null) && (cmdIdx < pc.getTargetCount()); cmdIdx++)
+ {
+ // Find the target's bundle resource.
+ Resource[] resources = searchRepository(pc.getTargetId(cmdIdx), pc.getTargetVersion(cmdIdx));
+ if (resources == null)
+ {
+ err.println("Unknown bundle and/or version: "
+ + pc.getTargetId(cmdIdx));
+ }
+ else
+ {
+ for (int resIdx = 0; resIdx < resources.length; resIdx++)
+ {
+ if (resIdx > 0)
+ {
+ out.println("");
+ }
+ printResource(out, resources[resIdx]);
+ }
+ }
+ }
+ }
+
+ 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
+ {
+ Resolver resolver = m_repoAdmin.resolver();
+ for (int i = 0; (pc != null) && (i < pc.getTargetCount()); i++)
+ {
+ // Find the target's bundle resource.
+ Resource resource = selectNewestVersion(
+ searchRepository(pc.getTargetId(i), pc.getTargetVersion(i)));
+ if (resource != null)
+ {
+ resolver.add(resource);
+ }
+ else
+ {
+ err.println("Unknown bundle - " + pc.getTargetId(i));
+ }
+ }
+
+ if ((resolver.getAddedResources() != null) &&
+ (resolver.getAddedResources().length > 0))
+ {
+ if (resolver.resolve())
+ {
+ out.println("Target resource(s):");
+ printUnderline(out, 19);
+ Resource[] resources = resolver.getAddedResources();
+ for (int resIdx = 0; (resources != null) && (resIdx < resources.length); resIdx++)
+ {
+ out.println(" " + resources[resIdx].getPresentationName()
+ + " (" + resources[resIdx].getVersion() + ")");
+ }
+ resources = resolver.getRequiredResources();
+ if ((resources != null) && (resources.length > 0))
+ {
+ out.println("\nRequired resource(s):");
+ printUnderline(out, 21);
+ for (int resIdx = 0; resIdx < resources.length; resIdx++)
+ {
+ out.println(" " + resources[resIdx].getPresentationName()
+ + " (" + resources[resIdx].getVersion() + ")");
+ }
+ }
+ resources = resolver.getOptionalResources();
+ if ((resources != null) && (resources.length > 0))
+ {
+ out.println("\nOptional resource(s):");
+ printUnderline(out, 21);
+ for (int resIdx = 0; resIdx < resources.length; resIdx++)
+ {
+ out.println(" " + resources[resIdx].getPresentationName()
+ + " (" + resources[resIdx].getVersion() + ")");
+ }
+ }
+
+ try
+ {
+ out.print("\nDeploying...");
+ resolver.deploy(command.equals(START_CMD));
+ out.println("done.");
+ }
+ catch (IllegalStateException ex)
+ {
+ err.println(ex);
+ }
+ }
+ else
+ {
+ Requirement[] reqs = resolver.getUnsatisfiedRequirements();
+ if ((reqs != null) && (reqs.length > 0))
+ {
+ out.println("Unsatisfied requirement(s):");
+ printUnderline(out, 27);
+ for (int reqIdx = 0; reqIdx < reqs.length; reqIdx++)
+ {
+ out.println(" " + reqs[reqIdx].getFilter());
+ Resource[] resources = resolver.getResources(reqs[reqIdx]);
+ for (int resIdx = 0; resIdx < resources.length; resIdx++)
+ {
+ out.println(" " + resources[resIdx].getPresentationName());
+ }
+ }
+ }
+ else
+ {
+ out.println("Could not resolve targets.");
+ }
+ }
+ }
+ }
+
+ 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++)
+ {
+ Resource resource = selectNewestVersion(
+ searchRepository(pc.getTargetId(i), pc.getTargetVersion(i)));
+ if (resource == null)
+ {
+ err.println("Unknown bundle and/or version: "
+ + pc.getTargetId(i));
+ }
+ else
+ {
+ URL srcURL = (URL) resource.getProperties().get(Resource.SOURCE_URL);
+ if (srcURL != null)
+ {
+ FileUtil.downloadSource(
+ out, err, srcURL, pc.getDirectory(), pc.isExtract());
+ }
+ else
+ {
+ err.println("Missing source URL: " + pc.getTargetId(i));
+ }
+ }
+ }
+ }
+
+ private Resource[] searchRepository(String targetId, String targetVersion)
+ {
+ // Try to see if the targetId is a bundle ID.
+ try
+ {
+ Bundle bundle = m_context.getBundle(Long.parseLong(targetId));
+ targetId = bundle.getSymbolicName();
+ }
+ catch (NumberFormatException ex)
+ {
+ // It was not a number, so ignore.
+ }
+
+ // The targetId may be a bundle name or a bundle symbolic name,
+ // so create the appropriate LDAP query.
+ StringBuffer sb = new StringBuffer("(|(presentationname=");
+ sb.append(targetId);
+ sb.append(")(symbolicname=");
+ sb.append(targetId);
+ sb.append("))");
+ if (targetVersion != null)
+ {
+ sb.insert(0, "(&");
+ sb.append("(version=");
+ sb.append(targetVersion);
+ sb.append("))");
+ }
+ return m_repoAdmin.discoverResources(sb.toString());
+ }
+
+ public Resource selectNewestVersion(Resource[] resources)
+ {
+ int idx = -1;
+ Version v = null;
+ for (int i = 0; (resources != null) && (i < resources.length); i++)
+ {
+ if (i == 0)
+ {
+ idx = 0;
+ v = resources[i].getVersion();
+ }
+ else
+ {
+ Version vtmp = resources[i].getVersion();
+ if (vtmp.compareTo(v) > 0)
+ {
+ idx = i;
+ v = vtmp;
+ }
+ }
+ }
+
+ return (idx < 0) ? null : resources[idx];
+ }
+
+ private void printResource(PrintStream out, Resource resource)
+ {
+ printUnderline(out, resource.getPresentationName().length());
+ out.println(resource.getPresentationName());
+ printUnderline(out, resource.getPresentationName().length());
+
+ Map map = resource.getProperties();
+ for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); )
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ if (entry.getValue().getClass().isArray())
+ {
+ out.println(entry.getKey() + ":");
+ for (int j = 0; j < Array.getLength(entry.getValue()); j++)
+ {
+ out.println(" " + Array.get(entry.getValue(), j));
+ }
+ }
+ else
+ {
+ out.println(entry.getKey() + ": " + entry.getValue());
+ }
+ }
+
+ Requirement[] reqs = resource.getRequirements();
+ if ((reqs != null) && (reqs.length > 0))
+ {
+ out.println("Requires:");
+ for (int i = 0; i < reqs.length; i++)
+ {
+ out.println(" " + reqs[i].getFilter());
+ }
+ }
+
+ Capability[] caps = resource.getCapabilities();
+ if ((caps != null) && (caps.length > 0))
+ {
+ out.println("Capabilities:");
+ for (int i = 0; i < caps.length; i++)
+ {
+ out.println(" " + caps[i].getProperties());
+ }
+ }
+ }
+
+ private static void printUnderline(PrintStream out, int length)
+ {
+ for (int i = 0; i < length; i++)
+ {
+ out.print('-');
+ }
+ out.println("");
+ }
+
+ 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 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 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('/', '/');
+ 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(ADDURL_CMD))
+ {
+ out.println("");
+ out.println("obr " + ADDURL_CMD + " [<repository-url> ...]");
+ out.println("");
+ out.println(
+ "This command adds the space-delimited list of repository URLs to\n" +
+ "the repository service.");
+ out.println("");
+ }
+ else if (command.equals(REMOVEURL_CMD))
+ {
+ out.println("");
+ out.println("obr " + REMOVEURL_CMD + " [<repository-url> ...]");
+ out.println("");
+ out.println(
+ "This command removes the space-delimited list of repository URLs\n" +
+ "from the repository service.");
+ out.println("");
+ }
+ else if (command.equals(LISTURL_CMD))
+ {
+ out.println("");
+ out.println("obr " + LISTURL_CMD);
+ out.println("");
+ out.println(
+ "This command displays the repository URLs currently associated\n" +
+ "with the repository service.");
+ 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>|<bundle-symbolic-name>|<bundle-id>[;<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
+ + " <bundle-name>|<bundle-symbolic-name>|<bundle-id>[;<version>] ... ");
+ out.println("");
+ out.println(
+ "This command tries to install or update the specified bundles\n" +
+ "and all of their dependencies. You can specify either the bundle\n" +
+ "name or the bundle identifier. If a bundle's name contains spaces,\n" +
+ "then it must be surrounded by quotes. It is also possible to\n" +
+ "specify a 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(START_CMD))
+ {
+ out.println("");
+ out.println("obr " + START_CMD
+ + " <bundle-name>|<bundle-symbolic-name>|<bundle-id>[;<version>] ...");
+ out.println("");
+ out.println(
+ "This command installs and starts the specified bundles and all\n" +
+ "of their 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(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
+ + " [" + ADDURL_CMD
+ + " | " + REMOVEURL_CMD
+ + " | " + LISTURL_CMD
+ + " | " + LIST_CMD
+ + " | " + INFO_CMD
+ + " | " + DEPLOY_CMD + " | " + START_CMD
+ + " | " + SOURCE_CMD + "]");
+ out.println("obr " + ADDURL_CMD + " [<repository-file-url> ...]");
+ out.println("obr " + REMOVEURL_CMD + " [<repository-file-url> ...]");
+ out.println("obr " + LISTURL_CMD);
+ out.println("obr " + LIST_CMD + " [<string> ...]");
+ out.println("obr " + INFO_CMD
+ + " <bundle-name>|<bundle-symbolic-name>|<bundle-id>[;<version>] ...");
+ out.println("obr " + DEPLOY_CMD
+ + " <bundle-name>|<bundle-symbolic-name>|<bundle-id>[;<version>] ...");
+ out.println("obr " + START_CMD
+ + " <bundle-name>|<bundle-symbolic-name>|<bundle-id>[;<version>] ...");
+ 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/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/PropertyImpl.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/PropertyImpl.java
new file mode 100644
index 0000000..f68e820
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/PropertyImpl.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+import java.net.*;
+
+import org.osgi.framework.Version;
+import org.osgi.service.obr.Resource;
+
+public class PropertyImpl
+{
+ private String m_name = null;
+ private String m_type = null;
+ private Object m_value = null;
+
+ public PropertyImpl()
+ {
+ }
+
+ public PropertyImpl(String name, String type, String value)
+ {
+ setN(name);
+ setT(type);
+ setV(value);
+ }
+
+ public void setN(String name)
+ {
+ m_name = name;
+ }
+
+ public String getN()
+ {
+ return m_name;
+ }
+
+ public void setT(String type)
+ {
+ m_type = type;
+
+ // If there is an existing value, then convert
+ // it based on the new type.
+ if (m_value != null)
+ {
+ m_value = convertType(m_value.toString());
+ }
+ }
+
+ public String getT()
+ {
+ return m_type;
+ }
+
+ public void setV(String value)
+ {
+ m_value = convertType(value);
+ }
+
+ public Object getV()
+ {
+ return m_value;
+ }
+
+ private Object convertType(String value)
+ {
+ if ((m_type != null) && (m_type.equalsIgnoreCase(Resource.VERSION)))
+ {
+ return new Version(value);
+ }
+ else if ((m_type != null) && (m_type.equalsIgnoreCase(Resource.URL)))
+ {
+ try
+ {
+ return new URL(value);
+ }
+ catch (MalformedURLException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Attribute.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Attribute.java
new file mode 100644
index 0000000..7afdf6d
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/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.felix.bundlerepository;
+
+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/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Directive.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Directive.java
new file mode 100644
index 0000000..fb0866d
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/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.felix.bundlerepository;
+
+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/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Export.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Export.java
new file mode 100644
index 0000000..5340286
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Export.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+import java.util.*;
+
+import org.osgi.framework.Constants;
+
+public class R4Export extends R4Package
+{
+ private String[] m_uses = null;
+ private String[][] m_includeFilter = null;
+ private String[][] m_excludeFilter = null;
+
+ public R4Export(R4Package pkg)
+ {
+ this(pkg.getName(), pkg.getDirectives(), pkg.getAttributes());
+ }
+
+ public R4Export(String name, R4Directive[] directives, R4Attribute[] attrs)
+ {
+ super(name, directives, attrs);
+
+ // Find all export directives: uses, mandatory, include, and exclude.
+ String mandatory = "", uses = "";
+ for (int i = 0; i < m_directives.length; i++)
+ {
+ if (m_directives[i].getName().equals(Constants.USES_DIRECTIVE))
+ {
+ uses = m_directives[i].getValue();
+ }
+ else if (m_directives[i].getName().equals(Constants.MANDATORY_DIRECTIVE))
+ {
+ mandatory = m_directives[i].getValue();
+ }
+ else if (m_directives[i].getName().equals(Constants.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(Constants.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.");
+ }
+ }
+ }
+
+ public String[] getUses()
+ {
+ return m_uses;
+ }
+
+ 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 = 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;
+ }
+
+ //
+ // 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/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Import.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Import.java
new file mode 100644
index 0000000..589b9d2
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Import.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+public class R4Import extends R4Package
+{
+ private VersionRange m_versionRange = null;
+ private boolean m_isOptional = false;
+
+ public R4Import(R4Package pkg)
+ {
+ this(pkg.getName(), pkg.getDirectives(), pkg.getAttributes());
+ }
+
+ public R4Import(String name, R4Directive[] directives, R4Attribute[] attrs)
+ {
+ super(name, directives, attrs);
+
+ // Find all import directives: resolution.
+ for (int i = 0; i < m_directives.length; i++)
+ {
+ if (m_directives[i].getName().equals(Constants.RESOLUTION_DIRECTIVE))
+ {
+ m_isOptional = m_directives[i].getValue().equals(Constants.RESOLUTION_OPTIONAL);
+ }
+ }
+
+ // Find and parse version attribute, if present.
+ String rangeStr = "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());
+ rangeStr = m_attrs[i].getValue();
+ break;
+ }
+ }
+
+ m_versionRange = VersionRange.parse(rangeStr);
+ m_version = m_versionRange.getLow();
+ }
+
+ public Version getVersionHigh()
+ {
+ return m_versionRange.getHigh();
+ }
+
+ public boolean isLowInclusive()
+ {
+ return m_versionRange.isLowInclusive();
+ }
+
+ public boolean isHighInclusive()
+ {
+ return m_versionRange.isHighInclusive();
+ }
+
+ public boolean isOptional()
+ {
+ return m_isOptional;
+ }
+
+ public boolean isSatisfied(R4Export export)
+ {
+ // For packages to be compatible, they must have the
+ // same name.
+ if (!getName().equals(export.getName()))
+ {
+ return false;
+ }
+
+ return m_versionRange.isInRange(export.getVersion())
+ && doAttributesMatch(export);
+ }
+
+ private boolean doAttributesMatch(R4Export export)
+ {
+ // Cycle through all attributes of this import package
+ // and make sure its values match the attribute values
+ // of the specified export package.
+ for (int impAttrIdx = 0; impAttrIdx < getAttributes().length; impAttrIdx++)
+ {
+ // Get current attribute from this import package.
+ R4Attribute impAttr = getAttributes()[impAttrIdx];
+
+ // 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 (impAttr.getName().equals(Constants.VERSION_ATTRIBUTE))
+ {
+ continue;
+ }
+
+ // Check if the export package has the same attribute.
+ boolean found = false;
+ for (int expAttrIdx = 0;
+ (!found) && (expAttrIdx < export.getAttributes().length);
+ expAttrIdx++)
+ {
+ // Get current attribute for the export package.
+ R4Attribute expAttr = export.getAttributes()[expAttrIdx];
+ // Check if the attribute names are equal.
+ if (impAttr.getName().equals(expAttr.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 (!impAttr.getValue().equals(expAttr.getValue()))
+ {
+ return false;
+ }
+ found = true;
+ }
+ }
+ // If the attribute was not found, then return false.
+ if (!found)
+ {
+ return false;
+ }
+ }
+
+ // Now, cycle through all attributes of the export package and verify that
+ // all mandatory attributes are present in this import package.
+ for (int expAttrIdx = 0; expAttrIdx < export.getAttributes().length; expAttrIdx++)
+ {
+ // Get current attribute for this package.
+ R4Attribute expAttr = export.getAttributes()[expAttrIdx];
+
+ // If the export attribute is mandatory, then make sure
+ // this import package has the attribute.
+ if (expAttr.isMandatory())
+ {
+ boolean found = false;
+ for (int impAttrIdx = 0;
+ (!found) && (impAttrIdx < getAttributes().length);
+ impAttrIdx++)
+ {
+ // Get current attribute from specified package.
+ R4Attribute impAttr = getAttributes()[impAttrIdx];
+
+ // Check if the attribute names are equal
+ // and set found flag.
+ if (expAttr.getName().equals(impAttr.getName()))
+ {
+ found = true;
+ }
+ }
+ // If not found, then return false.
+ if (!found)
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Package.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Package.java
new file mode 100644
index 0000000..2d25468
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/R4Package.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+public class R4Package
+{
+ private String m_name = "";
+ protected R4Directive[] m_directives = null;
+ protected R4Attribute[] m_attrs = null;
+ protected Version m_version = null;
+
+ public R4Package(String name, R4Directive[] directives, R4Attribute[] attrs)
+ {
+ m_name = name;
+ m_directives = (directives == null) ? new R4Directive[0] : directives;
+ m_attrs = (attrs == null) ? new R4Attribute[0] : attrs;
+
+ // Find and parse version attribute, if present.
+ String rangeStr = "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());
+ rangeStr = m_attrs[i].getValue();
+ break;
+ }
+ }
+
+ VersionRange range = VersionRange.parse(rangeStr);
+ // For now, ignore if we have a version range.
+ m_version = range.getLow();
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public R4Directive[] getDirectives()
+ {
+ return m_directives;
+ }
+
+ public R4Attribute[] getAttributes()
+ {
+ return m_attrs;
+ }
+
+ public Version getVersion()
+ {
+ return m_version;
+ }
+
+ public String toString()
+ {
+ String msg = getName();
+ 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, ",");
+ 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], ";");
+
+ // 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(":=")) >= 0)
+ {
+ sep = ":=";
+ }
+ // Check if it is an attribute.
+ else if ((idx = pieces[pieceIdx].indexOf("=")) >= 0)
+ {
+ sep = "=";
+ }
+ // 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(":="))
+ {
+ 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[] pkgs = (R4Package[])
+ completeList.toArray(new R4Package[completeList.size()]);
+ return pkgs;
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/RepositoryAdminImpl.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/RepositoryAdminImpl.java
new file mode 100644
index 0000000..343ffa9
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/RepositoryAdminImpl.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import org.apache.felix.bundlerepository.metadataparser.XmlCommonHandler;
+import org.apache.felix.bundlerepository.metadataparser.kxmlsax.KXml2SAXParser;
+import org.osgi.framework.*;
+import org.osgi.service.obr.*;
+
+public class RepositoryAdminImpl implements RepositoryAdmin
+{
+ static BundleContext m_context = null;
+ private List m_urlList = new ArrayList();
+ private Map m_repoMap = new HashMap();
+ private boolean m_initialized = false;
+
+ // Reusable comparator for sorting resources by name.
+ private Comparator m_nameComparator = new ResourceComparator();
+
+ private static final String DEFAULT_REPOSITORY_URL =
+ "http://oscar-osgi.sf.net/obr2/repository.xml";
+ public static final String REPOSITORY_URL_PROP = "obr.repository.url";
+ public static final String EXTERN_REPOSITORY_TAG = "extern-repositories";
+
+ public RepositoryAdminImpl(BundleContext context)
+ {
+ m_context = context;
+
+ // Get repository URLs.
+ String urlStr = m_context.getProperty(REPOSITORY_URL_PROP);
+ if (urlStr != null)
+ {
+ StringTokenizer st = new StringTokenizer(urlStr);
+ if (st.countTokens() > 0)
+ {
+ while (st.hasMoreTokens())
+ {
+ try
+ {
+ m_urlList.add(new URL(st.nextToken()));
+ }
+ catch (MalformedURLException ex)
+ {
+ System.err.println("RepositoryAdminImpl: " + ex);
+ }
+ }
+ }
+ }
+
+ // Use the default URL if none were specified.
+ if (m_urlList.size() == 0)
+ {
+ try
+ {
+ m_urlList.add(new URL(DEFAULT_REPOSITORY_URL));
+ }
+ catch (MalformedURLException ex)
+ {
+ System.err.println("RepositoryAdminImpl: " + ex);
+ }
+ }
+ }
+
+ public synchronized Repository addRepository(URL url) throws Exception
+ {
+ if (!m_urlList.contains(url))
+ {
+ m_urlList.add(url);
+ }
+
+ // If the repository URL is a duplicate, then we will just
+ // replace the existing repository object with a new one,
+ // which is effectively the same as refreshing the repository.
+ Repository repo = new RepositoryImpl(url);
+ m_repoMap.put(url, repo);
+ return repo;
+ }
+
+ public synchronized boolean removeRepository(URL url)
+ {
+ m_repoMap.remove(url);
+ return (m_urlList.remove(url)) ? true : false;
+ }
+
+ public synchronized Repository[] listRepositories()
+ {
+ if (!m_initialized)
+ {
+ initialize();
+ }
+ return (Repository[]) m_repoMap.values().toArray(new Repository[m_repoMap.size()]);
+ }
+
+ public synchronized Resource getResource(String respositoryId)
+ {
+ // TODO: OBR - Auto-generated method stub
+ return null;
+ }
+
+ public synchronized Resolver resolver()
+ {
+ if (!m_initialized)
+ {
+ initialize();
+ }
+
+ return new ResolverImpl(m_context, this);
+ }
+
+ public synchronized Resource[] discoverResources(String filterExpr)
+ {
+ if (!m_initialized)
+ {
+ initialize();
+ }
+
+ Filter filter = null;
+ try
+ {
+ filter = m_context.createFilter(filterExpr);
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ System.err.println(ex);
+ }
+
+ Resource[] resources = null;
+ MapToDictionary dict = new MapToDictionary(null);
+ Repository[] repos = listRepositories();
+ List matchList = new ArrayList();
+ for (int repoIdx = 0; (repos != null) && (repoIdx < repos.length); repoIdx++)
+ {
+ resources = repos[repoIdx].getResources();
+ for (int resIdx = 0; (resources != null) && (resIdx < resources.length); resIdx++)
+ {
+ dict.setSourceMap(resources[resIdx].getProperties());
+ if (filter.match(dict))
+ {
+ matchList.add(resources[resIdx]);
+ }
+ }
+ }
+
+ // Convert matching resources to an array an sort them by name.
+ resources = (Resource[]) matchList.toArray(new Resource[matchList.size()]);
+ Arrays.sort(resources, m_nameComparator);
+ return resources;
+ }
+
+ private void initialize()
+ {
+ m_initialized = true;
+ m_repoMap.clear();
+
+ for (int i = 0; i < m_urlList.size(); i++)
+ {
+ URL url = (URL) m_urlList.get(i);
+ Repository repo = new RepositoryImpl(url);
+ if (repo != null)
+ {
+ m_repoMap.put(url, repo);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/RepositoryImpl.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/RepositoryImpl.java
new file mode 100644
index 0000000..7ffa601
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/RepositoryImpl.java
@@ -0,0 +1,178 @@
+package org.apache.felix.bundlerepository;
+
+import java.io.*;
+import java.net.*;
+import java.net.URL;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+
+import org.apache.felix.bundlerepository.metadataparser.XmlCommonHandler;
+import org.apache.felix.bundlerepository.metadataparser.kxmlsax.KXml2SAXParser;
+import org.osgi.service.obr.*;
+import org.osgi.service.obr.Repository;
+import org.osgi.service.obr.Resource;
+
+public class RepositoryImpl implements Repository
+{
+ private String m_name = null;
+ private long m_lastmodified = 0;
+ private URL m_url = null;
+ private Resource[] m_resources = null;
+ private int m_hopCount = 1;
+
+ // Reusable comparator for sorting resources by name.
+ private ResourceComparator m_nameComparator = new ResourceComparator();
+
+ public RepositoryImpl(URL url)
+ {
+ m_url = url;
+ parseRepositoryFile(m_hopCount);
+ }
+
+ public URL getURL()
+ {
+ return m_url;
+ }
+
+ protected void setURL(URL url)
+ {
+ m_url = url;
+ }
+
+ public Resource[] getResources()
+ {
+ return m_resources;
+ }
+
+ // TODO: OBR - Wrong parameter type from metadata parser.
+ public void addResource(ResourceImpl resource)
+ {
+ // Set resource's repository.
+ ((ResourceImpl) resource).setRepository(this);
+
+ // Add to resource array.
+ if (m_resources == null)
+ {
+ m_resources = new Resource[] { resource };
+ }
+ else
+ {
+ Resource[] newResources = new Resource[m_resources.length + 1];
+ System.arraycopy(m_resources, 0, newResources, 0, m_resources.length);
+ newResources[m_resources.length] = resource;
+ m_resources = newResources;
+ }
+
+ Arrays.sort(m_resources, m_nameComparator);
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public void setName(String name)
+ {
+ m_name = name;
+ }
+
+ public long getLastModified()
+ {
+ return m_lastmodified;
+ }
+
+ public void setLastmodified(String s)
+ {
+ SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmss.SSS");
+ try
+ {
+ m_lastmodified = format.parse(s).getTime();
+ }
+ catch (ParseException ex)
+ {
+ }
+ }
+
+ private void parseRepositoryFile(int hopCount)
+ {
+// TODO: OBR - Implement hop count.
+ InputStream is = null;
+ BufferedReader br = null;
+
+ try
+ {
+ // Do it the manual way to have a chance to
+ // set request properties as proxy auth (EW).
+ URLConnection conn = m_url.openConnection();
+
+ // Support for http proxy authentication
+ String auth = System.getProperty("http.proxyAuth");
+ if ((auth != null) && (auth.length() > 0))
+ {
+ if ("http".equals(m_url.getProtocol()) ||
+ "https".equals(m_url.getProtocol()))
+ {
+ String base64 = Util.base64Encode(auth);
+ conn.setRequestProperty(
+ "Proxy-Authorization", "Basic " + base64);
+ }
+ }
+ is = conn.getInputStream();
+
+ // Create the parser Kxml
+ XmlCommonHandler handler = new XmlCommonHandler();
+ try
+ {
+ Object factory = new Object() {
+ public RepositoryImpl newInstance()
+ {
+ return RepositoryImpl.this;
+ }
+ };
+ handler.addType("repository", factory, Repository.class);
+ handler.addType("resource", ResourceImpl.class, Resource.class);
+ handler.addType("category", CategoryImpl.class, null);
+ handler.addType("require", RequirementImpl.class, Requirement.class);
+ handler.addType("capability", CapabilityImpl.class, Capability.class);
+ handler.addType("p", PropertyImpl.class, null);
+ handler.setDefaultType(String.class, null);
+ }
+ catch (Exception ex)
+ {
+ System.err.println("RepositoryAdminImpl: " + ex);
+ }
+
+ br = new BufferedReader(new InputStreamReader(is));
+ KXml2SAXParser parser;
+ try
+ {
+ parser = new KXml2SAXParser(br);
+ parser.parseXML(handler);
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ catch (MalformedURLException ex)
+ {
+ System.err.println("RepositoryAdminImpl: " + ex);
+ }
+ catch (IOException ex)
+ {
+ System.err.println("RepositoryAdminImpl: " + ex);
+ }
+ finally
+ {
+ try
+ {
+ if (is != null) is.close();
+ }
+ catch (IOException ex)
+ {
+ // Not much we can do.
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/RequirementImpl.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/RequirementImpl.java
new file mode 100644
index 0000000..a67ee2e
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/RequirementImpl.java
@@ -0,0 +1,114 @@
+package org.apache.felix.bundlerepository;
+
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.obr.Capability;
+import org.osgi.service.obr.Requirement;
+
+public class RequirementImpl implements Requirement
+{
+ private String m_name = null;
+ private boolean m_extend = false;
+ private boolean m_multiple = false;
+ private boolean m_optional = false;
+ private Filter m_filter = null;
+ private String m_comment = null;
+ private MapToDictionary m_dict = new MapToDictionary(null);
+
+ public RequirementImpl()
+ {
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public synchronized void setName(String name)
+ {
+ m_name = name;
+ }
+
+ public String getFilter()
+ {
+ return m_filter.toString();
+ }
+
+ public synchronized void setFilter(String filter)
+ {
+ try
+ {
+ m_filter = RepositoryAdminImpl.m_context.createFilter(filter);
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ m_filter = null;
+ System.err.println(ex);
+ }
+ }
+
+ public synchronized boolean isSatisfied(Capability capability)
+ {
+ m_dict.setSourceMap(capability.getProperties());
+ return m_filter.match(m_dict);
+ }
+
+ public boolean isExtend()
+ {
+ return m_extend;
+ }
+
+ public synchronized void setExtend(String s)
+ {
+ m_extend = Boolean.valueOf(s).booleanValue();
+ }
+
+ public boolean isMultiple()
+ {
+ return m_multiple;
+ }
+
+ public synchronized void setMultiple(String s)
+ {
+ m_multiple = Boolean.valueOf(s).booleanValue();
+ }
+
+ public boolean isOptional()
+ {
+ return m_optional;
+ }
+
+ public synchronized void setOptional(String s)
+ {
+ m_optional = Boolean.valueOf(s).booleanValue();
+ }
+
+ public String getComment()
+ {
+ return m_comment;
+ }
+
+ public synchronized void addText(String s)
+ {
+ m_comment = s;
+ }
+
+ public synchronized boolean equals(Object o)
+ {
+ if (o instanceof Requirement)
+ {
+ Requirement r = (Requirement) o;
+ return m_name.equals(r.getName()) &&
+ (m_optional == r.isOptional()) &&
+ (m_multiple == r.isMultiple()) &&
+ m_filter.toString().equals(r.getFilter()) &&
+ m_comment.equals(r.getComment());
+ }
+ return false;
+ }
+
+ public int hashCode()
+ {
+ return m_filter.toString().hashCode();
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ResolverImpl.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ResolverImpl.java
new file mode 100644
index 0000000..255ba04
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ResolverImpl.java
@@ -0,0 +1,608 @@
+package org.apache.felix.bundlerepository;
+
+import java.net.URL;
+import java.util.*;
+
+import org.apache.felix.bundlerepository.LocalRepositoryImpl.LocalResourceImpl;
+import org.osgi.framework.*;
+import org.osgi.service.obr.*;
+
+public class ResolverImpl implements Resolver
+{
+ private BundleContext m_context = null;
+ private RepositoryAdmin m_admin = null;
+ private LocalRepositoryImpl m_local = null;
+ private Set m_addedSet = new HashSet();
+ private Set m_resolveSet = new HashSet();
+ private Set m_requiredSet = new HashSet();
+ private Set m_optionalSet = new HashSet();
+ private Map m_reasonMap = new HashMap();
+ private Map m_unsatisfiedMap = new HashMap();
+ private boolean m_resolved = false;
+
+ public ResolverImpl(BundleContext context, RepositoryAdmin admin)
+ {
+ m_context = context;
+ m_admin = admin;
+ }
+
+ public synchronized void add(Resource resource)
+ {
+ m_resolved = false;
+ m_addedSet.add(resource);
+ }
+
+ public synchronized Requirement[] getUnsatisfiedRequirements()
+ {
+ if (m_resolved)
+ {
+ return (Requirement[])
+ m_unsatisfiedMap.keySet().toArray(
+ new Requirement[m_unsatisfiedMap.size()]);
+ }
+ throw new IllegalStateException("The resources have not been resolved.");
+ }
+
+ public synchronized Resource[] getOptionalResources()
+ {
+ if (m_resolved)
+ {
+ return (Resource[])
+ m_optionalSet.toArray(
+ new Resource[m_optionalSet.size()]);
+ }
+ throw new IllegalStateException("The resources have not been resolved.");
+ }
+
+ public synchronized Requirement[] getReason(Resource resource)
+ {
+ if (m_resolved)
+ {
+ return (Requirement[]) m_reasonMap.get(resource);
+ }
+ throw new IllegalStateException("The resources have not been resolved.");
+ }
+
+ public synchronized Resource[] getResources(Requirement requirement)
+ {
+ if (m_resolved)
+ {
+ return (Resource[]) m_unsatisfiedMap.get(requirement);
+ }
+ throw new IllegalStateException("The resources have not been resolved.");
+ }
+
+ public synchronized Resource[] getRequiredResources()
+ {
+ if (m_resolved)
+ {
+ return (Resource[])
+ m_requiredSet.toArray(
+ new Resource[m_requiredSet.size()]);
+ }
+ throw new IllegalStateException("The resources have not been resolved.");
+ }
+
+ public synchronized Resource[] getAddedResources()
+ {
+ return (Resource[]) m_addedSet.toArray(new Resource[m_addedSet.size()]);
+ }
+
+ public synchronized boolean resolve()
+ {
+ // Get a current local repository.
+ // TODO: OBR - We might want to make a smarter local repository
+ // that caches installed bundles rather than re-parsing them
+ // each time, since this could be costly.
+ if (m_local != null)
+ {
+ m_local.dispose();
+ }
+ m_local = new LocalRepositoryImpl(m_context);
+
+ // Reset instance values.
+ m_resolveSet.clear();
+ m_requiredSet.clear();
+ m_optionalSet.clear();
+ m_reasonMap.clear();
+ m_unsatisfiedMap.clear();
+ m_resolved = true;
+
+ boolean result = true;
+
+ // Loop through each resource in added list and resolve.
+ for (Iterator iter = m_addedSet.iterator(); iter.hasNext(); )
+ {
+ if (!resolve((Resource) iter.next()))
+ {
+ // If any resource does not resolve, then the
+ // entire result will be false.
+ result = false;
+ }
+ }
+
+ // Clean up the resulting data structures.
+ List locals = Arrays.asList(m_local.getResources());
+ m_requiredSet.removeAll(m_addedSet);
+ m_requiredSet.removeAll(locals);
+ m_optionalSet.removeAll(m_addedSet);
+ m_optionalSet.removeAll(m_requiredSet);
+ m_optionalSet.removeAll(locals);
+
+ // Return final result.
+ return result;
+ }
+
+ private boolean resolve(Resource resource)
+ {
+ boolean result = true;
+
+ // Check for a cycle.
+ if (m_resolveSet.contains(resource))
+ {
+ return result;
+ }
+
+ // Add to resolve map to avoid cycles.
+ m_resolveSet.add(resource);
+
+ // Resolve the requirements for the resource according to the
+ // search order of: added, local, resolving, and remote resources.
+ Requirement[] reqs = resource.getRequirements();
+ if (reqs != null)
+ {
+ Resource candidate = null;
+ for (int reqIdx = 0; reqIdx < reqs.length; reqIdx++)
+ {
+ candidate = searchAddedResources(reqs[reqIdx]);
+ if (candidate == null)
+ {
+ candidate = searchLocalResources(reqs[reqIdx]);
+ if (candidate == null)
+ {
+ candidate = searchResolvingResources(reqs[reqIdx]);
+ if (candidate == null)
+ {
+ candidate = searchRemoteResources(reqs[reqIdx]);
+ }
+ }
+ }
+
+ if ((candidate == null) && !reqs[reqIdx].isOptional())
+ {
+ // The resolve failed.
+ result = false;
+ // Associated the current resource to the requirement
+ // in the unsatisfied requirement map.
+ Resource[] resources = (Resource[]) m_unsatisfiedMap.get(reqs[reqIdx]);
+ if (resources == null)
+ {
+ resources = new Resource[] { resource };
+ }
+ else
+ {
+ Resource[] tmp = new Resource[resources.length + 1];
+ System.arraycopy(resources, 0, tmp, 0, resources.length);
+ tmp[resources.length] = resource;
+ resources = tmp;
+ }
+ m_unsatisfiedMap.put(reqs[reqIdx], resources);
+ }
+ else if (candidate != null)
+ {
+ // The resolved succeeded; record the candidate
+ // as either optional or required.
+ if (reqs[reqIdx].isOptional())
+ {
+ m_optionalSet.add(candidate);
+ }
+ else
+ {
+ m_requiredSet.add(candidate);
+ }
+
+ // Add the reason why the candidate was selected.
+ addReason(candidate, reqs[reqIdx]);
+
+ // Try to resolve the candidate.
+ if (!resolve(candidate))
+ {
+ result = false;
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private Resource searchAddedResources(Requirement req)
+ {
+ for (Iterator iter = m_addedSet.iterator(); iter.hasNext(); )
+ {
+ Resource resource = (Resource) iter.next();
+ Capability[] caps = resource.getCapabilities();
+ for (int capIdx = 0; (caps != null) && (capIdx < caps.length); capIdx++)
+ {
+ if (req.isSatisfied(caps[capIdx]))
+ {
+ // The requirement is already satisfied an existing
+ // resource, return the resource.
+ return resource;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private Resource searchLocalResources(Requirement req)
+ {
+ Resource[] resources = m_local.getResources();
+ for (int resIdx = 0; (resources != null) && (resIdx < resources.length); resIdx++)
+ {
+ Capability[] caps = resources[resIdx].getCapabilities();
+ for (int capIdx = 0; (caps != null) && (capIdx < caps.length); capIdx++)
+ {
+ if (req.isSatisfied(caps[capIdx]))
+ {
+ return resources[resIdx];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private Resource searchResolvingResources(Requirement req)
+ {
+ Resource[] resources = (Resource[])
+ m_resolveSet.toArray(new Resource[m_resolveSet.size()]);
+ for (int resIdx = 0; (resources != null) && (resIdx < resources.length); resIdx++)
+ {
+ Capability[] caps = resources[resIdx].getCapabilities();
+ for (int capIdx = 0; (caps != null) && (capIdx < caps.length); capIdx++)
+ {
+ if (req.isSatisfied(caps[capIdx]))
+ {
+ return resources[resIdx];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private Resource searchRemoteResources(Requirement req)
+ {
+ // For now, guess that if there is a version associated with
+ // the candidate capability that we should choose the highest
+ // version; otherwise, choose the resource with the greatest
+ // number of capabilities.
+ // TODO: OBR - This could probably be improved.
+ Resource best = null;
+ Version bestVersion = null;
+ Repository[] repos = m_admin.listRepositories();
+ for (int repoIdx = 0; (repos != null) && (repoIdx < repos.length); repoIdx++)
+ {
+ Resource[] resources = repos[repoIdx].getResources();
+ for (int resIdx = 0; (resources != null) && (resIdx < resources.length); resIdx++)
+ {
+ Capability[] caps = resources[resIdx].getCapabilities();
+ for (int capIdx = 0; (caps != null) && (capIdx < caps.length); capIdx++)
+ {
+ if (req.isSatisfied(caps[capIdx]))
+ {
+ if (best == null)
+ {
+ best = resources[resIdx];
+ Object v = caps[capIdx].getProperties().get(Resource.VERSION);
+ if ((v != null) && (v instanceof Version))
+ {
+ bestVersion = (Version) v;
+ }
+ }
+ else
+ {
+ Object v = caps[capIdx].getProperties().get(Resource.VERSION);
+
+ // If there is no version, then select the resource
+ // with the greatest number of capabilities.
+ if ((v == null) && (bestVersion == null)
+ && (best.getCapabilities().length < caps.length))
+ {
+ best = resources[resIdx];
+ bestVersion = (Version) v;
+ }
+ else if ((v != null) && (v instanceof Version))
+ {
+ // If there is no best version or if the current
+ // resource's version is lower, then select it.
+ if ((bestVersion == null) || (bestVersion.compareTo(v) < 0))
+ {
+ best = resources[resIdx];
+ bestVersion = (Version) v;
+ }
+ // If the current resource version is equal to the
+ // best, then select the one with the greatest
+ // number of capabilities.
+ else if ((bestVersion != null) && (bestVersion.compareTo(v) == 0)
+ && (best.getCapabilities().length < caps.length))
+ {
+ best = resources[resIdx];
+ bestVersion = (Version) v;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return best;
+ }
+
+ public synchronized void deploy(boolean start)
+ {
+ // Must resolve if not already resolved.
+ if (!m_resolved && !resolve())
+ {
+ // TODO: OBR - Use logger if possible.
+ System.err.println("Resolver: Cannot resolve target resources.");
+ return;
+ }
+
+ // Check to make sure that our local state cache is up-to-date
+ // and error if it is not. This is not completely safe, because
+ // the state can still change during the operation, but we will
+ // be optimistic. This could also be made smarter so that it checks
+ // to see if the local state changes overlap with the resolver.
+ if (m_local.getLastModified() != m_local.getCurrentTimeStamp())
+ {
+ throw new IllegalStateException("Framework state has changed, must resolve again.");
+ }
+
+ // Eliminate duplicates from target, required, optional resources.
+ Map deployMap = new HashMap();
+ Resource[] resources = getAddedResources();
+ for (int i = 0; (resources != null) && (i < resources.length); i++)
+ {
+ deployMap.put(resources[i], resources[i]);
+ }
+ resources = getRequiredResources();
+ for (int i = 0; (resources != null) && (i < resources.length); i++)
+ {
+ deployMap.put(resources[i], resources[i]);
+ }
+ resources = getOptionalResources();
+ for (int i = 0; (resources != null) && (i < resources.length); i++)
+ {
+ deployMap.put(resources[i], resources[i]);
+ }
+ Resource[] deployResources = (Resource[])
+ deployMap.keySet().toArray(new Resource[deployMap.size()]);
+
+ // List to hold all resources to be started.
+ List startList = new ArrayList();
+
+ // Deploy each resource, which will involve either finding a locally
+ // installed resource to update or the installation of a new version
+ // of the resource to be deployed.
+ for (int i = 0; i < deployResources.length; i++)
+ {
+ // For the resource being deployed, see if there is an older
+ // version of the resource already installed that can potentially
+ // be updated.
+ LocalRepositoryImpl.LocalResourceImpl localResource =
+ findUpdatableLocalResource(deployResources[i]);
+ // If a potentially updatable older version was found,
+ // then verify that updating the local resource will not
+ // break any of the requirements of any of the other
+ // resources being deployed.
+ if ((localResource != null) &&
+ isResourceUpdatable(localResource, deployResources[i], deployResources))
+ {
+ // Only update if it is a different version.
+ if (!localResource.equals(deployResources[i]))
+ {
+ // Update the installed bundle.
+ try
+ {
+ localResource.getBundle().update(deployResources[i].getURL().openStream());
+
+ // If necessary, save the updated bundle to be
+ // started later.
+ if (start)
+ {
+ startList.add(localResource.getBundle());
+ }
+ }
+ catch (Exception ex)
+ {
+ // TODO: OBR - Use logger if possible.
+ System.err.println("Resolver: Update error - " + Util.getBundleName(localResource.getBundle()));
+ ex.printStackTrace(System.err);
+ return;
+ }
+ }
+ }
+ else
+ {
+ // Install the bundle.
+ try
+ {
+ // 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 = deployResources[i].getURL();
+ if (url != null)
+ {
+ Bundle bundle = m_context.installBundle(
+ "obr://"
+ + deployResources[i].getSymbolicName()
+ + "/" + System.currentTimeMillis(),
+ url.openStream());
+
+ // If necessary, save the installed bundle to be
+ // started later.
+ if (start)
+ {
+ startList.add(bundle);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ // TODO: OBR - Use logger if possible.
+ System.err.println("Resolver: Install error - "
+ + deployResources[i].getSymbolicName());
+ ex.printStackTrace(System.err);
+ return;
+ }
+ }
+ }
+
+ for (int i = 0; i < startList.size(); i++)
+ {
+ try
+ {
+ ((Bundle) startList.get(i)).start();
+ }
+ catch (BundleException ex)
+ {
+ // TODO: OBR - Use logger if possible.
+ System.err.println("Resolver: Start error - " + ex);
+ }
+ }
+ }
+
+ private void addReason(Resource resource, Requirement req)
+ {
+ Requirement[] reasons = (Requirement[]) m_reasonMap.get(resource);
+ if (reasons == null)
+ {
+ reasons = new Requirement[] { req };
+ }
+ else
+ {
+ Requirement[] tmp = new Requirement[reasons.length + 1];
+ System.arraycopy(reasons, 0, tmp, 0, reasons.length);
+ tmp[reasons.length] = req;
+ reasons = tmp;
+ }
+ m_reasonMap.put(resource, reasons);
+ }
+
+ // TODO: OBR - Think about this again and make sure that deployment ordering
+ // won't impact it...we need to update the local state too.
+ private LocalResourceImpl findUpdatableLocalResource(Resource resource)
+ {
+ // Determine if any other versions of the specified resource
+ // already installed.
+ Resource[] localResources = findLocalResources(resource.getSymbolicName());
+ if (localResources != null)
+ {
+ // Since there are local resources with the same symbolic
+ // name installed, then we must determine if we can
+ // update an existing resource or if we must install
+ // another one. Loop through all local resources with same
+ // symbolic name and find the first one that can be updated
+ // without breaking constraints of existing local resources.
+ for (int i = 0; i < localResources.length; i++)
+ {
+ if (isResourceUpdatable(localResources[i], resource, m_local.getResources()))
+ {
+ return (LocalResourceImpl) localResources[i];
+ }
+ }
+ }
+ return null;
+ }
+
+ private Resource[] findLocalResources(String symName)
+ {
+ Resource[] localResources = m_local.getResources();
+
+ List matchList = new ArrayList();
+ for (int i = 0; i < localResources.length; i++)
+ {
+ String localSymName = localResources[i].getSymbolicName();
+ if ((localSymName != null) && localSymName.equals(symName))
+ {
+ matchList.add(localResources[i]);
+ }
+ }
+ return (Resource[]) matchList.toArray(new Resource[matchList.size()]);
+ }
+
+ private boolean isResourceUpdatable(
+ Resource oldVersion, Resource newVersion, Resource[] resources)
+ {
+ // Get all of the local resolvable requirements for the old
+ // version of the resource from the specified resource array.
+ Requirement[] reqs = getResolvableRequirements(oldVersion, resources);
+
+ // Now make sure that all of the requirements resolved by the
+ // old version of the resource can also be resolved by the new
+ // version of the resource.
+ Capability[] caps = newVersion.getCapabilities();
+ if (caps == null)
+ {
+ return false;
+ }
+ for (int reqIdx = 0; reqIdx < reqs.length; reqIdx++)
+ {
+ boolean satisfied = false;
+ for (int capIdx = 0; !satisfied && (capIdx < caps.length); capIdx++)
+ {
+ if (reqs[reqIdx].isSatisfied(caps[capIdx]))
+ {
+ satisfied = true;
+ }
+ }
+
+ // If any of the previously resolved requirements cannot
+ // be resolved, then the resource is not updatable.
+ if (!satisfied)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private Requirement[] getResolvableRequirements(Resource resource, Resource[] resources)
+ {
+ // For the specified resource, find all requirements that are
+ // satisfied by any of its capabilities in the specified resource
+ // array.
+ Capability[] caps = resource.getCapabilities();
+ if ((caps != null) && (caps.length > 0))
+ {
+ List reqList = new ArrayList();
+ for (int capIdx = 0; capIdx < caps.length; capIdx++)
+ {
+ boolean added = false;
+ for (int resIdx = 0; !added && (resIdx < resources.length); resIdx++)
+ {
+ Requirement[] reqs = resources[resIdx].getRequirements();
+ for (int reqIdx = 0;
+ (reqs != null) && (reqIdx < reqs.length);
+ reqIdx++)
+ {
+ if (reqs[reqIdx].isSatisfied(caps[capIdx]))
+ {
+ added = true;
+ reqList.add(reqs[reqIdx]);
+ }
+ }
+ }
+ }
+ return (Requirement[])
+ reqList.toArray(new Requirement[reqList.size()]);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ResourceComparator.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ResourceComparator.java
new file mode 100644
index 0000000..cb84c33
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ResourceComparator.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+import java.util.Comparator;
+
+import org.osgi.service.obr.Resource;
+
+class ResourceComparator implements Comparator
+{
+ public int compare(Object o1, Object o2)
+ {
+ Resource r1 = (Resource) o1;
+ Resource r2 = (Resource) o2;
+ String name1 = (String) r1.getPresentationName();
+ String name2 = (String) r2.getPresentationName();
+ return name1.compareToIgnoreCase(name2);
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ResourceImpl.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ResourceImpl.java
new file mode 100644
index 0000000..2201d87
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/ResourceImpl.java
@@ -0,0 +1,291 @@
+package org.apache.felix.bundlerepository;
+
+import java.net.*;
+import java.util.*;
+
+import org.osgi.framework.Version;
+import org.osgi.service.obr.*;
+
+public class ResourceImpl implements Resource, Map
+{
+ private final String URI = "uri";
+
+ private Repository m_repo = null;
+ private Map m_map = null;
+ private List m_catList = new ArrayList();
+ private List m_capList = new ArrayList();
+ private List m_reqList = new ArrayList();
+
+ private String m_resourceURI = "";
+ private String m_docURI = "";
+ private String m_licenseURI = "";
+ private String m_sourceURI = "";
+ private boolean m_converted = false;
+
+ public ResourceImpl()
+ {
+ this(null);
+ }
+
+ public ResourceImpl(ResourceImpl resource)
+ {
+ m_map = new TreeMap(new Comparator() {
+ public int compare(Object o1, Object o2)
+ {
+ return o1.toString().compareToIgnoreCase(o2.toString());
+ }
+ });
+
+ if (resource != null)
+ {
+ resource.putAll(resource.getProperties());
+ m_catList.addAll(resource.m_catList);
+ m_capList.addAll(resource.m_capList);
+ m_reqList.addAll(resource.m_reqList);
+ }
+ }
+
+ public boolean equals(Object o)
+ {
+ if (o instanceof Resource)
+ {
+ return ((Resource) o).getSymbolicName().equals(getSymbolicName())
+ && ((Resource) o).getVersion().equals(getVersion());
+ }
+ return false;
+ }
+
+ public int hashCode()
+ {
+ return getSymbolicName().hashCode() ^ getVersion().hashCode();
+ }
+
+ public Map getProperties()
+ {
+ if (!m_converted)
+ {
+ convertURItoURL();
+ }
+ return m_map;
+ }
+
+ public String getPresentationName()
+ {
+ return (String) m_map.get(PRESENTATION_NAME);
+ }
+
+ public String getSymbolicName()
+ {
+ return (String) m_map.get(SYMBOLIC_NAME);
+ }
+
+ public String getId()
+ {
+ return (String) m_map.get(ID);
+ }
+
+ public Version getVersion()
+ {
+ return (Version) m_map.get(VERSION);
+ }
+
+ public URL getURL()
+ {
+ if (!m_converted)
+ {
+ convertURItoURL();
+ }
+ return (URL) m_map.get(URL);
+ }
+
+ public Requirement[] getRequirements()
+ {
+ return (Requirement[]) m_reqList.toArray(new Requirement[m_reqList.size()]);
+ }
+
+ public void addRequire(Requirement req)
+ {
+System.out.println("ADDING REQUIREMENTS PROPERLY");
+ m_reqList.add(req);
+ }
+
+ public Capability[] getCapabilities()
+ {
+System.out.println("ADDING CAPABILITIES PROPERLY");
+ return (Capability[]) m_capList.toArray(new Capability[m_capList.size()]);
+ }
+
+ // TODO: OBR - Should this be a property?
+ public void addCapability(Capability cap)
+ {
+ m_capList.add(cap);
+ }
+
+ public String[] getCategories()
+ {
+ return (String[]) m_catList.toArray(new String[m_catList.size()]);
+ }
+
+ public void addCategory(CategoryImpl cat)
+ {
+ m_catList.add(cat.getId());
+ }
+
+ public Repository getRepository()
+ {
+ return m_repo;
+ }
+
+ protected void setRepository(Repository repo)
+ {
+ m_repo = repo;
+ }
+
+ //
+ // Map interface methods.
+ //
+
+ public int size()
+ {
+ return m_map.size();
+ }
+
+ public void clear()
+ {
+ m_map.clear();
+ }
+
+ public boolean isEmpty()
+ {
+ return m_map.isEmpty();
+ }
+
+ public boolean containsKey(Object key)
+ {
+ return m_map.containsKey(key);
+ }
+
+ public boolean containsValue(Object value)
+ {
+ return m_map.containsValue(value);
+ }
+
+ public Collection values()
+ {
+ return m_map.values();
+ }
+
+ public void putAll(Map t)
+ {
+ m_map.putAll(t);
+ }
+
+ public Set entrySet()
+ {
+ return m_map.entrySet();
+ }
+
+ public Set keySet()
+ {
+ return m_map.keySet();
+ }
+
+ public Object get(Object key)
+ {
+ return m_map.get(key);
+ }
+
+ public Object remove(Object key)
+ {
+ return m_map.remove(key);
+ }
+
+ public Object put(Object key, Object value)
+ {
+ // Capture the URIs since they might be relative, so we
+ // need to defer setting the actual URL value until they
+ // are used so that we will know our repository and its
+ // base URL.
+ if (key.equals(LICENSE_URL))
+ {
+ m_licenseURI = (String) value;
+ }
+ else if (key.equals(DOCUMENTATION_URL))
+ {
+ m_docURI = (String) value;
+ }
+ else if (key.equals(SOURCE_URL))
+ {
+ m_sourceURI = (String) value;
+ }
+ else if (key.equals(URI))
+ {
+ m_resourceURI = (String) value;
+ }
+ else
+ {
+ if (key.equals(VERSION))
+ {
+ value = new Version(value.toString());
+ }
+ else if (key.equals(SIZE))
+ {
+ value = Long.valueOf(value.toString());
+ }
+ // TODO: OBR - These should be handled by the "add" methods above.
+ else if (key.equals("require"))
+ {
+System.out.println("ADDING REQUIREMENTS IMPROPERLY!!!!!!!!!!!!!!!");
+ m_reqList.add(value);
+ return null;
+ }
+ else if (key.equals("capability"))
+ {
+System.out.println("ADDING CAPABILITIES IMPROPERLY!!!!!!!!!!!!!!!");
+ m_capList.add(value);
+ return null;
+ }
+ else if (key.equals("category"))
+ {
+ m_catList.add(value);
+ return null;
+ }
+
+ return m_map.put(key, value);
+ }
+
+ return null;
+ }
+
+ private void convertURItoURL()
+ {
+ if (m_repo != null)
+ {
+ try
+ {
+ URL base = m_repo.getURL();
+ if (m_resourceURI != null)
+ {
+ m_map.put(URL, new URL(base, m_resourceURI));
+ }
+ if (m_docURI != null)
+ {
+ m_map.put(DOCUMENTATION_URL, new URL(base, m_docURI));
+ }
+ if (m_licenseURI != null)
+ {
+ m_map.put(LICENSE_URL, new URL(base, m_licenseURI));
+ }
+ if (m_sourceURI != null)
+ {
+ m_map.put(SOURCE_URL, new URL(base, m_sourceURI));
+ }
+ m_converted = true;
+ }
+ catch (MalformedURLException ex)
+ {
+ ex.printStackTrace(System.err);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/Util.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/Util.java
new file mode 100644
index 0000000..217c5da
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/Util.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+import java.io.*;
+import java.util.*;
+import java.util.StringTokenizer;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+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 getBundleName(Bundle bundle)
+ {
+ String name = (String) bundle.getHeaders().get(Constants.BUNDLE_NAME);
+ return (name == null)
+ ? "Bundle " + Long.toString(bundle.getBundleId())
+ : name;
+ }
+
+ /**
+ * 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()]);
+ }
+
+ 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;
+ }
+
+ 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/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/VersionRange.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/VersionRange.java
new file mode 100644
index 0000000..381d93b
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/VersionRange.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository;
+
+import org.osgi.framework.Version;
+
+public class VersionRange
+{
+ private Version m_low = null;
+ private boolean m_isLowInclusive = false;
+ private Version m_high = null;
+ private boolean m_isHighInclusive = false;
+
+ public VersionRange(Version low, boolean isLowInclusive,
+ Version high, boolean isHighInclusive)
+ {
+ m_low = low;
+ m_isLowInclusive = isLowInclusive;
+ m_high = high;
+ m_isHighInclusive = isHighInclusive;
+ }
+
+ public Version getLow()
+ {
+ return m_low;
+ }
+
+ public boolean isLowInclusive()
+ {
+ return m_isLowInclusive;
+ }
+
+ public Version getHigh()
+ {
+ return m_high;
+ }
+
+ public boolean isHighInclusive()
+ {
+ return m_isHighInclusive;
+ }
+
+ public boolean isInRange(Version version)
+ {
+ // We might not have an upper end to the range.
+ if (m_high == null)
+ {
+ return (version.compareTo(m_low) >= 0);
+ }
+ else if (isLowInclusive() && isHighInclusive())
+ {
+ return (version.compareTo(m_low) >= 0) && (version.compareTo(m_high) <= 0);
+ }
+ else if (isHighInclusive())
+ {
+ return (version.compareTo(m_low) > 0) && (version.compareTo(m_high) <= 0);
+ }
+ else if (isLowInclusive())
+ {
+ return (version.compareTo(m_low) >= 0) && (version.compareTo(m_high) < 0);
+ }
+ return (version.compareTo(m_low) > 0) && (version.compareTo(m_high) < 0);
+ }
+
+ public static VersionRange parse(String range)
+ {
+ // Check if the version is an interval.
+ if (range.indexOf(',') >= 0)
+ {
+ String s = range.substring(1, range.length() - 1);
+ String vlo = s.substring(0, s.indexOf(','));
+ String vhi = s.substring(s.indexOf(',') + 1, s.length());
+ return new VersionRange (
+ new Version(vlo), (range.charAt(0) == '['),
+ new Version(vhi), (range.charAt(range.length() - 1) == ']'));
+ }
+ else
+ {
+ return new VersionRange(new Version(range), true, null, false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/ClassUtility.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/ClassUtility.java
new file mode 100644
index 0000000..f83b497
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/ClassUtility.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.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 minusculizes all characters in the provided string.
+ * @return resulted string
+ */
+ public static String toLowerCase(String name) {
+ int len=name.length();
+ StringBuffer sb=new StringBuffer(len);
+ for(int i=0; i<len; i++){
+ char c=name.charAt(i);
+ sb.append(Character.toLowerCase(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/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/KXml2MetadataHandler.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/KXml2MetadataHandler.java
new file mode 100644
index 0000000..120e41a
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/KXml2MetadataHandler.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository.metadataparser;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.apache.felix.bundlerepository.metadataparser.kxmlsax.KXml2SAXParser;
+
+/**
+ * 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 KXml2MetadataHandler extends MetadataHandler {
+
+ public KXml2MetadataHandler() {}
+
+ /**
+ * 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));
+ KXml2SAXParser parser;
+ parser = new KXml2SAXParser(br);
+ parser.parseXML(handler);
+ }
+}
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/MappingProcessingInstructionHandler.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/MappingProcessingInstructionHandler.java
new file mode 100644
index 0000000..51a019c
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/MappingProcessingInstructionHandler.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository.metadataparser;
+
+
+/**
+ * this class adds type of elements to the parser
+ *
+ * @author Didier Donsez (didier.donsez@imag.fr)
+ */
+public class MappingProcessingInstructionHandler {
+
+ private XmlCommonHandler handler;
+ private String name;
+ private String classname;
+
+ public MappingProcessingInstructionHandler(XmlCommonHandler handler) {
+ this.handler = handler;
+ }
+
+ public void process() throws Exception {
+ if(name==null) {
+ throw new Exception("element is missing");
+ }
+ if(classname==null) {
+ throw new Exception("class is missing");
+ }
+ handler.addType(name,this.getClass().getClassLoader().loadClass(classname),null);
+ }
+
+ public void setElement(String element) {
+ this.name=element;
+ }
+
+ public void setClass(String classname) {
+ this.classname=classname;
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/MetadataHandler.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/MetadataHandler.java
new file mode 100644
index 0000000..3fc299a
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/MetadataHandler.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository.metadataparser;
+
+import java.io.InputStream;
+
+
+/**
+ * @version 1.00 9 Jul 2004
+ * @author Didier Donsez
+ */
+public abstract class MetadataHandler {
+
+ protected XmlCommonHandler handler;
+
+ public MetadataHandler() {
+ handler = new XmlCommonHandler();
+ }
+
+ /**
+ * Called to parse the InputStream and set bundle list and package hash map
+ */
+ public abstract void parse(InputStream is) throws Exception;
+
+ /**
+ * return the metadata
+ * @return a Objet
+ */
+ public final Object getMetadata() {
+ return handler.getRoot();
+ }
+
+ public final void addType(String qname, Object instanceFactory) throws Exception {
+ handler.addType(qname, instanceFactory, null);
+ }
+
+ public final void addType(String qname, Object instanceFactory, Class castClass) throws Exception {
+ handler.addType(qname, instanceFactory, castClass);
+ }
+
+ public final void setDefaultType(Object instanceFactory) throws Exception {
+ handler.setDefaultType(instanceFactory,null);
+ }
+
+ public final void setDefaultType(Object instanceFactory, Class castClass) throws Exception {
+ handler.setDefaultType(instanceFactory, castClass);
+ }
+
+ public final void addPI(String piname, Class clazz) {
+ handler.addPI(piname, clazz);
+ }
+
+ /**
+ * set the missing PI exception flag. If during parsing, the flag is true and the processing instruction is unknown, then the parser throws a exception
+ * @param flag
+ */
+ public final void setMissingPIExceptionFlag(boolean flag) {
+ handler.setMissingPIExceptionFlag(flag);
+ }
+
+ /**
+ *
+ * @param trace
+ * @since 0.9.1
+ */
+ public final void setTrace(boolean trace) {
+ handler.setTrace(trace);
+ }
+}
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/ReplaceUtility.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/ReplaceUtility.java
new file mode 100644
index 0000000..5f5e396
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/ReplaceUtility.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository.metadataparser;
+
+import java.util.Map;
+
+/**
+ * This class provides methods to replace ${var} substring by values stored in a map
+ */
+
+public class ReplaceUtility {
+
+ /**
+ * This method replaces ${var} substring by values stored in a map.
+ * @return resulted string
+ */
+ public static String replace(String str, Map values) {
+
+ int len = str.length();
+ StringBuffer sb = new StringBuffer(len);
+
+ int prev = 0;
+ int start = str.indexOf("${");
+ int end = str.indexOf("}", start);
+ while (start != -1 && end != -1) {
+ String key = str.substring(start + 2, end);
+ Object value = values.get(key);
+ if (value != null) {
+ sb.append(str.substring(prev, start));
+ sb.append(value);
+ } else {
+ sb.append(str.substring(prev, end + 1));
+ }
+ prev = end + 1;
+ if (prev >= str.length())
+ break;
+
+ start = str.indexOf("${", prev);
+ if (start != -1)
+ end = str.indexOf("}", start);
+ }
+
+ sb.append(str.substring(prev));
+
+ return sb.toString();
+ }
+
+ // public static void main(String[] args){
+ // Map map=new HashMap();
+ // map.put("foo","FOO");
+ // map.put("bar","BAR");
+ // map.put("map",map);
+ //
+ // String str;
+ // if(args.length==0) str=""; else str=args[0];
+ //
+ // System.out.println(replace(str,map));
+ //
+ // }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/XmlCommonHandler.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/XmlCommonHandler.java
new file mode 100644
index 0000000..0dc6dc1
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/XmlCommonHandler.java
@@ -0,0 +1,829 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository.metadataparser;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.*;
+
+import org.apache.felix.bundlerepository.metadataparser.kxmlsax.KXml2SAXHandler;
+import org.xml.sax.SAXException;
+
+
+/**
+ * SAX handler for the XML file
+ *
+ * @author Didier Donsez (didier.donsez@imag.fr)
+ */
+public class XmlCommonHandler implements KXml2SAXHandler {
+
+ private static final String PI_MAPPING = "mapping";
+
+ public static final String METADATAPARSER_PIS = "METADATAPARSER_PIS";
+
+ public static final String METADATAPARSER_INSTANCEFACTORY = "METADATAPARSER_INSTANCEFACTORY";
+
+ private int columnNumber;
+
+ private int lineNumber;
+
+ private boolean traceFlag = false;
+
+ private static String VALUE = "value";
+
+ //
+ // Data
+ //
+
+ private Object root;
+
+ private Stack objectStack;
+
+ private Stack qnameStack;
+
+ private Map pis;
+
+ private boolean missingPIExceptionFlag;
+
+ private Map instanceFactories;
+
+ private Map instanceClasses;
+
+ private Map castClasses;
+
+ private Map context;
+
+ private Object defaultInstanceFactory;
+
+ private Class defaultInstanceClass;
+
+ private Class defaultCastClass;
+
+ private StringBuffer currentText;
+
+ public XmlCommonHandler() {
+ objectStack = new Stack();
+ qnameStack = new Stack();
+ pis = new HashMap();
+ missingPIExceptionFlag = false;
+ instanceFactories = new HashMap();
+ instanceClasses = new HashMap();
+ castClasses = new HashMap();
+ context = new HashMap();
+ context.put(METADATAPARSER_PIS, pis);
+ context.put(METADATAPARSER_INSTANCEFACTORY, instanceFactories);
+ }
+
+ public void addPI(String piname, Class clazz) {
+ pis.put(piname, clazz);
+ }
+
+ /**
+ * set the missing PI exception flag. If during parsing, the flag is true
+ * and the processing instruction is unknown, then the parser throws a
+ * exception
+ *
+ * @param flag
+ */
+ public void setMissingPIExceptionFlag(boolean flag) {
+ missingPIExceptionFlag = flag;
+ }
+
+ private Class checkAndGetInstanceClass(String qname,
+ Object instanceFactory, Class castClass) throws Exception {
+ String typeMsg = (qname == null) ? " for default type "
+ : (" for type " + qname);
+ try {
+ if (instanceFactory instanceof Class) {
+ if (castClass == null) {
+ castClass = (Class) instanceFactory;
+ } else {
+ if (!castClass.isAssignableFrom((Class) instanceFactory)) {
+ throw new Exception(
+ lineNumber
+ + ","
+ + columnNumber
+ + ":"
+ + "instanceFactory "
+ + instanceFactory.getClass().getName()
+ + typeMsg
+ + " could not instanciate objects assignable to "
+ + castClass.getName());
+ }
+ }
+ return (Class) instanceFactory;
+ } else {
+ Method newInstanceMethod = instanceFactory.getClass()
+ .getDeclaredMethod("newInstance", null);
+ Class returnType = newInstanceMethod.getReturnType();
+ if (castClass == null) {
+ castClass = returnType;
+ } else if (!castClass.isAssignableFrom(returnType)) {
+ throw new Exception(lineNumber + "," + columnNumber + ":"
+ + "instanceFactory "
+ + instanceFactory.getClass().getName() + typeMsg
+ + " could not instanciate objects assignable to "
+ + castClass.getName());
+ }
+ return returnType;
+ }
+ } catch (NoSuchMethodException e) {
+ throw new Exception(lineNumber + "," + columnNumber + ":"
+ + "instanceFactory " + instanceFactory.getClass().getName()
+ + " for type " + qname
+ + " should have a newInstance method");
+ }
+ }
+
+ public void addType(String qname, Object instanceFactory, Class castClass)
+ throws Exception {
+ Class instanceClass = checkAndGetInstanceClass(qname, instanceFactory,
+ castClass);
+ instanceClasses.put(qname, instanceClass);
+ instanceFactories.put(qname, instanceFactory);
+ castClasses.put(qname, castClass != null ? castClass : instanceClass);
+ trace("element "
+ + qname
+ + " : instanceFactory="
+ + (instanceFactory instanceof Class ? ((Class) instanceFactory)
+ .getName() : instanceFactory.getClass().getName())
+ + " castClass="
+ + (castClass != null ? castClass : instanceClass).getName());
+ }
+
+ public void setDefaultType(Object instanceFactory, Class castClass)
+ throws Exception {
+ defaultInstanceClass = checkAndGetInstanceClass(null, instanceFactory,
+ castClass);
+ defaultInstanceFactory = instanceFactory;
+ defaultCastClass = castClass != null ? castClass : defaultInstanceClass;
+ trace("defaut type : instanceFactory="
+ + (instanceFactory instanceof Class ? ((Class) instanceFactory)
+ .getName() : instanceFactory.getClass().getName())
+ + " castClass=" + defaultCastClass.getName());
+ }
+
+ public void setContext(Map context) {
+ this.context = context;
+ }
+
+ public Map getContext() {
+ return context;
+ }
+
+ 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);
+ }
+
+ /**
+ * set the parser context in a object
+ */
+ private void setObjectContext(Object object)
+ throws IllegalArgumentException, IllegalAccessException,
+ InvocationTargetException {
+ Method method = null;
+ try {
+ // TODO setContext from castClass or object.getClass() ?
+ method = object.getClass().getMethod("setContext",
+ new Class[] { Map.class });
+ } catch (NoSuchMethodException e) {
+ // do nothing
+ }
+ if (method != null) {
+ trace(method.getName());
+ try {
+ method.invoke(object, new Object[] { context });
+ } catch (InvocationTargetException e) {
+ e.getTargetException().printStackTrace(System.err);
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * set the parser context in a object
+ *
+ * @throws Throwable
+ */
+ private void invokeProcess(Object object) throws Throwable {
+ Method method = null;
+ try {
+ // TODO process from castClass or object.getClass() ?
+ method = object.getClass().getMethod("process", null);
+ } catch (NoSuchMethodException e) {
+ // do nothing
+ }
+ if (method != null) {
+ trace(method.getName());
+ try {
+ method.invoke(object, null);
+ } catch (InvocationTargetException e) {
+ // e.getTargetException().printStackTrace(System.err);
+ throw e.getTargetException();
+ }
+
+ }
+ }
+
+ /**
+ * set the parent in a object
+ */
+ private void setObjectParent(Object object, Object parent)
+ throws InvocationTargetException, IllegalArgumentException,
+ IllegalAccessException {
+ Method method = null;
+ try {
+ // TODO setParent from castClass or object.getClass() ?
+ method = object.getClass().getMethod("setParent",
+ new Class[] { parent.getClass() });
+ } catch (NoSuchMethodException e) {
+ // do nothing
+ }
+ if (method != null) {
+ trace(method.getName());
+ try {
+ method.invoke(object, new Object[] { parent });
+ } catch (InvocationTargetException e) {
+ e.getTargetException().printStackTrace(System.err);
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * 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);
+
+ // TODO: should add uri in the qname in the future
+ Object instanceFactory = instanceFactories.get(qName); // instanceFactory
+ // can be a
+ // java.lang.Class
+ // or a object
+ // with a
+ // newInstance()
+ // method
+ Class castClass = null;
+ Class instanceClass = null;
+
+ if (instanceFactory == null) {
+ if (defaultInstanceFactory != null) {
+ instanceFactory = defaultInstanceFactory;
+ castClass = defaultCastClass;
+ instanceClass = defaultInstanceClass;
+ }
+ } else {
+ castClass = (Class) castClasses.get(qName);
+ instanceClass = (Class) instanceClasses.get(qName);
+ }
+
+ Object obj = null;
+ if (instanceFactory != null) {
+ Method newInstanceMethod = null;
+ try {
+ newInstanceMethod = instanceFactory.getClass().getMethod(
+ "newInstance", null);
+ } catch (NoSuchMethodException e) {
+ // never catch since checked by addType and setDefaultType
+ throw new Exception(lineNumber + "," + columnNumber + ":"
+ + "instanceFactory "
+ + instanceFactory.getClass().getName()
+ + " for element " + qName
+ + " should have a newInstance method");
+ }
+
+ try {
+ newInstanceMethod.setAccessible(true);
+ obj = newInstanceMethod.invoke(instanceFactory, null);
+ } catch (Exception e) {
+ // do nothing
+ }
+
+ // set parent
+ if (!objectStack.isEmpty()) {
+ Object parent = objectStack.peek();
+ setObjectParent(obj, parent);
+ }
+
+ // set the parser context
+ setObjectContext(obj);
+
+ // set the attributes
+ Set keyset = attrib.keySet();
+ Iterator iter = keyset.iterator();
+ while (iter.hasNext()) {
+ String key = (String) iter.next();
+
+ // substitute ${property} sbustrings by context' properties
+ // values
+ String value = ReplaceUtility.replace((String) attrib.get(key),
+ context);
+
+ // Firstly, test if the getter or the adder exists
+
+ Method method = null;
+ if (!(obj instanceof String)) {
+ try {
+ // method = castClass.getMethod(setterOf(key),new
+ // Class[] { String.class });
+ method = instanceClass.getMethod(setterOf(key),
+ new Class[] { String.class });
+ } catch (NoSuchMethodException e) {
+ // do nothing
+ }
+ if (method == null)
+ try {
+ method = instanceClass.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) {
+ trace(method.getName());
+ try {
+ method.invoke(obj, new String[] { value });
+ } catch (InvocationTargetException e) {
+ e.getTargetException().printStackTrace(System.err);
+ throw e;
+ }
+ } else {
+ // Secondly, test if object if a map, a dictionary, a
+ // collection, or a string
+
+ if (obj instanceof Map) {
+ ((Map) obj).put(key, value);
+ } else if (obj instanceof Dictionary) {
+ ((Dictionary) obj).put(key, value);
+ } else if (obj instanceof Collection) {
+ throw new Exception(lineNumber + "," + columnNumber
+ + ":" + "List element " + qName
+ + " cannot have any attribute");
+ } else if (obj instanceof String) {
+ if (key.equals("value")) {
+ obj = value;
+ } else {
+ throw new Exception(lineNumber + "," + columnNumber
+ + ":" + "String element " + qName
+ + " cannot have other attribute than value");
+ }
+ } else {
+ throw new Exception(lineNumber + "," + columnNumber
+ + ":" + "class "
+ + instanceFactory.getClass().getName()
+ + " for element " + qName
+ + " does not support the attribute " + key
+ + "or List.add or Map.put");
+
+ }
+ }
+
+ }
+
+ } 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();
+ Class objClass = obj.getClass();
+
+ if (currentText != null && currentText.length() != 0) {
+
+ String currentStr = ReplaceUtility.replace(currentText.toString(),
+ context).trim();
+ // TODO: trim may be not the right choice
+ trace("current text:" + currentStr);
+
+ Method method = null;
+ try {
+ method = objClass.getMethod("addText",
+ new Class[] { String.class });
+ } catch (NoSuchMethodException e) {
+ try {
+ method = objClass.getMethod("setText",
+ new Class[] { String.class });
+ } catch (NoSuchMethodException e2) {
+ // do nothing
+ }
+ }
+ if (method != null) {
+ trace(method.getName());
+ try {
+ method.invoke(obj, new String[] { currentStr });
+ } catch (InvocationTargetException e) {
+ e.getTargetException().printStackTrace(System.err);
+ throw e;
+ }
+ } else {
+ if (Map.class.isAssignableFrom(objClass)) {
+ ((Map) obj).put(qName, currentStr);
+ } else if (Dictionary.class.isAssignableFrom(objClass)) {
+ ((Dictionary) obj).put(qName, currentStr);
+ } else if (Collection.class.isAssignableFrom(objClass)) {
+ throw new Exception(lineNumber + "," + columnNumber + ":"
+ + "List element " + qName + " cannot have PCDATAs");
+ } else if (String.class.isAssignableFrom(objClass)) {
+ 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 = currentStr;
+ }
+ }
+ }
+
+ }
+
+ currentText = null;
+
+ if (!objectStack.isEmpty()) {
+
+ Object parent = objectStack.peek();
+ String parentName = (String) qnameStack.peek();
+ // TODO Class parentClass = (castClasses(parentName)).getClass() ????
+ Class parentClass = parent.getClass();
+
+ Method method = null;
+ try {
+ method = parentClass.getMethod(adderOf(ClassUtility
+ .capitalize(qName)), new Class[] { objClass }); // instanceClass
+ } 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[] { objClass });
+ } catch (NoSuchMethodException e) {
+ trace("NoSuchMethodException: "
+ + setterOf(ClassUtility.capitalize(qName)));
+ // do nothing
+ }
+ if (method == null)
+ try {
+ method = parent.getClass().getMethod(adderOf(objClass),
+ new Class[] { objClass });
+ } catch (NoSuchMethodException e) {
+ trace("NoSuchMethodException: " + adderOf(objClass));
+ // do nothing
+ }
+ if (method == null)
+ try {
+ method = parent.getClass().getMethod(setterOf(objClass),
+ new Class[] { objClass });
+ } catch (NoSuchMethodException e) {
+ trace("NoSuchMethodException: " + setterOf(objClass));
+ // do nothing
+ }
+
+ if (method != null) {
+ trace(method.getName());
+ try {
+ method.invoke(parent, new Object[] { obj });
+ } catch (InvocationTargetException e) {
+ e.getTargetException().printStackTrace(System.err);
+ throw e;
+ }
+ } else {
+ if (Map.class.isAssignableFrom(parentClass)) {
+ ((Map) parent).put(qName, obj);
+ } else if (Dictionary.class.isAssignableFrom(parentClass)) {
+ ((Dictionary) parent).put(qName, obj);
+ } else if (Collection.class.isAssignableFrom(parentClass)) {
+ ((Collection) parent).add(obj);
+ } else {
+ throw new Exception(lineNumber + "," + columnNumber + ":"
+ + " element " + parentName
+ + " cannot have an attribute " + qName
+ + " of type " + objClass);
+ }
+ }
+
+ }
+
+ // invoke the process method
+ try {
+ invokeProcess(obj);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ throw new Exception(e);
+ }
+
+ trace("END/ (" + lineNumber + "," + columnNumber + "):" + uri + ":"
+ + qName);
+
+ }
+
+ public void setTrace(boolean trace) {
+ this.traceFlag = trace;
+ }
+
+ private void trace(String msg) {
+ if (traceFlag)
+ 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);
+ trace("ignore PI : "+data);
+/* // reuse the kXML parser methods to parser the PI data
+ Reader reader = new StringReader(data);
+ XmlParser parser = new XmlParser(reader);
+ parser.parsePIData();
+
+ target = parser.getTarget();
+ Map attributes = parser.getAttributes();
+
+ // get the class
+ Class clazz = (Class) pis.get(target);
+ if (clazz == null) {
+ if (missingPIExceptionFlag)
+ throw new Exception(lineNumber + "," + columnNumber + ":"
+ + "Unknown processing instruction");
+ else {
+ trace(lineNumber + "," + columnNumber + ":"
+ + "No class for PI " + target);
+ return;
+ }
+ }
+
+ // instanciate a object
+ Object object;
+ Constructor ctor = null;
+ try {
+ ctor = clazz.getConstructor(new Class[] { XmlCommonHandler.class });
+ } catch (NoSuchMethodException e) {
+ // do nothing
+ trace("no constructor with XmlCommonHandler parameter");
+ }
+ try {
+ if (ctor == null) {
+ object = clazz.newInstance();
+ } else {
+ object = ctor.newInstance(new Object[] { this });
+ }
+ } catch (InstantiationException e) {
+ throw new Exception(
+ lineNumber
+ + ","
+ + columnNumber
+ + ":"
+ + "class "
+ + clazz.getName()
+ + " for PI "
+ + target
+ + " should have an empty constructor or a constructor with XmlCommonHandler parameter");
+ } catch (IllegalAccessException e) {
+ throw new Exception(lineNumber + "," + columnNumber + ":"
+ + "illegal access on the constructor " + clazz.getName()
+ + " for PI " + target);
+ }
+
+ // set the context
+ setObjectContext(object);
+
+ // TODO: set the parent
+
+ // invoke setter
+ Iterator iter = attributes.keySet().iterator();
+ while (iter.hasNext()) {
+ String key = (String) iter.next();
+ String value = ReplaceUtility.replace((String) attributes.get(key),
+ context);
+ Method method = null;
+ try {
+ method = clazz.getMethod(setterOf(key),
+ new Class[] { String.class });
+ } catch (NoSuchMethodException e) {
+ // do nothing
+ }
+ if (method != null) {
+ trace(method.getName());
+ try {
+ method.invoke(object, new String[] { value });
+ } catch (InvocationTargetException e) {
+ e.getTargetException().printStackTrace(System.err);
+ throw e;
+ }
+ }
+
+ }
+
+ // invoke process
+ try {
+ invokeProcess(object);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ throw new Exception(e);
+ }
+*/ }
+
+ public void processingInstructionForMapping(String target, String data)
+ throws Exception {
+
+
+ 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, null);
+ 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);
+
+ // element cast (optional)
+ String castname = null;
+ String castatt = "cast=\"";
+ int caststart = data.indexOf(castatt);
+ if (caststart != -1) {
+ int castend = data.indexOf("\"", cstart + castatt.length());
+ if (castend == -1)
+ throw new Exception(lineNumber + "," + columnNumber + ":"
+ + " \"cast\" attribute in \"mapping\" PI is not quoted");
+
+ castname = data.substring(caststart + castatt.length(), castend);
+ }
+
+ Class clazz = null;
+ try {
+ clazz = getClass().getClassLoader().loadClass(classname);
+ } catch (ClassNotFoundException e) {
+ throw new Exception(lineNumber + "," + columnNumber + ":"
+ + " cannot found class " + classname
+ + " for \"mapping\" PI");
+ }
+
+ Class castClazz = null;
+ if (castname != null)
+ try {
+ clazz = getClass().getClassLoader().loadClass(castname);
+ } catch (ClassNotFoundException e) {
+ throw new Exception(lineNumber + "," + columnNumber + ":"
+ + " cannot found cast class " + classname
+ + " for \"mapping\" PI");
+ }
+
+ addType(element, clazz, castClazz);
+ }
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/XmlMetadataHandler.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/XmlMetadataHandler.java
new file mode 100644
index 0000000..76b78fd
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/XmlMetadataHandler.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository.metadataparser;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+
+/**
+ * handles the metadata in XML format
+ *
+ * @version 1.00 18 May 2003
+ * @author Didier Donsez
+ */
+public class XmlMetadataHandler extends MetadataHandler {
+
+ public XmlMetadataHandler() {
+ }
+
+ /**
+ * Called to parse the InputStream and set bundle list and package hash map
+ */
+ public void parse(InputStream istream) throws ParserConfigurationException, IOException, SAXException {
+ // Parse the Meta-Data
+
+ ContentHandler contenthandler = (ContentHandler) handler;
+
+ InputSource is = new InputSource(istream);
+
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ spf.setValidating(false);
+
+ SAXParser saxParser = spf.newSAXParser();
+
+ XMLReader xmlReader = null;
+ xmlReader = saxParser.getXMLReader();
+ xmlReader.setContentHandler(contenthandler);
+ xmlReader.parse(is);
+ }
+}
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/kxmlsax/KXml2SAXHandler.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/kxmlsax/KXml2SAXHandler.java
new file mode 100644
index 0000000..0014c51
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/kxmlsax/KXml2SAXHandler.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository.metadataparser.kxmlsax;
+
+import java.util.Properties;
+
+/**
+ * Interface for SAX handler with kXML
+ *
+ * @author Didier Donsez (didier.donsez@imag.fr)
+ */
+public interface KXml2SAXHandler {
+
+ /**
+ * 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;
+
+ public void processingInstruction(String target,
+ String data)
+ throws Exception;
+
+ public void setLineNumber(int lineNumber);
+
+ public void setColumnNumber(int columnNumber);
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/kxmlsax/KXml2SAXParser.java b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/kxmlsax/KXml2SAXParser.java
new file mode 100644
index 0000000..3bc3b83
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/apache/felix/bundlerepository/metadataparser/kxmlsax/KXml2SAXParser.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.felix.bundlerepository.metadataparser.kxmlsax;
+
+import java.io.Reader;
+import java.util.Properties;
+
+import org.kxml2.io.KXmlParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * The KXml2SAXParser 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 KXml2SAXParser extends KXmlParser {
+
+ public String uri="uri";
+
+ private Reader reader;
+
+ /**
+ * The constructor for a parser, it receives a java.io.Reader.
+ *
+ * @param reader The reader
+ * @throws XmlPullParserException
+ */
+ public KXml2SAXParser(Reader reader) throws XmlPullParserException {
+ super();
+ this.reader=reader;
+ setInput(reader);
+ }
+
+ /**
+ * 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(KXml2SAXHandler handler) throws Exception {
+
+ while (next() != XmlPullParser.END_DOCUMENT) {
+ handler.setLineNumber(getLineNumber());
+ handler.setColumnNumber(getColumnNumber());
+ if (getEventType() == XmlPullParser.START_TAG) {
+ Properties props = new Properties();
+ for (int i = 0; i < getAttributeCount(); i++) {
+ props.put(getAttributeName(i), getAttributeValue(i));
+ }
+ handler.startElement(
+ getNamespace(),
+ getName(),
+ getName(),
+ props);
+ } else if (getEventType() == XmlPullParser.END_TAG) {
+ handler.endElement(getNamespace(), getName(), getName());
+ } else if (getEventType() == XmlPullParser.TEXT) {
+ String text = getText();
+ handler.characters(text.toCharArray(),0,text.length());
+ } else if (getEventType() == XmlPullParser.PROCESSING_INSTRUCTION) {
+ // TODO extract the target from the evt.getText()
+ handler.processingInstruction(null,getText());
+ } else {
+ // do nothing
+ }
+ }
+ }
+}
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Capability.java b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Capability.java
new file mode 100644
index 0000000..804e5f0
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Capability.java
@@ -0,0 +1,48 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.obr/src/org/osgi/service/obr/Capability.java,v 1.3 2006/03/16 14:56:17 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2006). All Rights Reserved.
+ *
+ * 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.
+ */
+
+// This document is an experimental draft to enable interoperability
+// between bundle repositories. There is currently no commitment to
+// turn this draft into an official specification.
+package org.osgi.service.obr;
+
+import java.util.Map;
+
+/**
+ * A named set of properties representing some capability that is provided by
+ * its owner.
+ *
+ * @version $Revision: 1.3 $
+ */
+public interface Capability
+{
+ /**
+ * Return the name of the capability.
+ *
+ */
+ String getName();
+
+ /**
+ * Return the set of properties.
+ *
+ * Notice that the value of the properties is a list of values.
+ *
+ * @return a Map<String,List>
+ */
+ Map getProperties();
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/CapabilityProvider.java b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/CapabilityProvider.java
new file mode 100644
index 0000000..bc151fb
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/CapabilityProvider.java
@@ -0,0 +1,49 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.obr/src/org/osgi/service/obr/CapabilityProvider.java,v 1.3 2006/03/16 14:56:17 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2006). All Rights Reserved.
+ *
+ * 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.
+ */
+
+// This document is an experimental draft to enable interoperability
+// between bundle repositories. There is currently no commitment to
+// turn this draft into an official specification.
+package org.osgi.service.obr;
+
+/**
+ * This service interface allows third parties to provide capabilities that are
+ * present on the system but not encoded in the bundle's manifests. For example,
+ * a capability provider could provide:
+ * <ol>
+ * <li>A Set of certificates</li>
+ * <li>Dimensions of the screen</li>
+ * <li>Amount of memory</li>
+ * <li>...</li>
+ * </ol>
+ *
+ * @version $Revision: 1.3 $
+ */
+public interface CapabilityProvider
+{
+ /**
+ * Return a set of capabilities.
+ *
+ * These capabilities are considered part of the platform. Bundles can
+ * require these capabilities during selection. All capabilities from
+ * different providers are considered part of the platform.
+ *
+ * @return Set of capabilities
+ */
+ Capability[] getCapabilities();
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Repository.java b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Repository.java
new file mode 100644
index 0000000..30adeb9
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Repository.java
@@ -0,0 +1,53 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.obr/src/org/osgi/service/obr/Repository.java,v 1.3 2006/03/16 14:56:17 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2006). All Rights Reserved.
+ *
+ * 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.
+ */
+
+// This document is an experimental draft to enable interoperability
+// between bundle repositories. There is currently no commitment to
+// turn this draft into an official specification.
+package org.osgi.service.obr;
+
+import java.net.URL;
+
+/**
+ * Represents a repository.
+ *
+ * @version $Revision: 1.3 $
+ */
+public interface Repository
+{
+ /**
+ * Return the associated URL for the repository.
+ *
+ */
+ URL getURL();
+
+ /**
+ * Return the resources for this repository.
+ */
+ Resource[] getResources();
+
+ /**
+ * Return the name of this reposotory.
+ *
+ * @return a non-null name
+ */
+ String getName();
+
+ long getLastModified();
+
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/RepositoryAdmin.java b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/RepositoryAdmin.java
new file mode 100644
index 0000000..7468871
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/RepositoryAdmin.java
@@ -0,0 +1,104 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.obr/src/org/osgi/service/obr/RepositoryAdmin.java,v 1.3 2006/03/16 14:56:17 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2006). All Rights Reserved.
+ *
+ * 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.
+ */
+
+// This document is an experimental draft to enable interoperability
+// between bundle repositories. There is currently no commitment to
+// turn this draft into an official specification.
+package org.osgi.service.obr;
+
+import java.net.URL;
+
+/**
+ * Provides centralized access to the distributed repository.
+ *
+ * A repository contains a set of <i>resources</i>. A resource contains a
+ * number of fixed attributes (name, version, etc) and sets of:
+ * <ol>
+ * <li>Capabilities - Capabilities provide a named aspect: a bundle, a display,
+ * memory, etc.</li>
+ * <li>Requirements - A named filter expression. The filter must be satisfied
+ * by one or more Capabilties with the given name. These capabilities can come
+ * from other resources or from the platform. If multiple resources provide the
+ * requested capability, one is selected. (### what algorithm? ###)</li>
+ * <li>Requests - Requests are like requirements, except that a request can be
+ * fullfilled by 0..n resources. This feature can be used to link to resources
+ * that are compatible with the given resource and provide extra functionality.
+ * For example, a bundle could request all its known fragments. The UI
+ * associated with the repository could list these as optional downloads.</li>
+ *
+ * @version $Revision: 1.3 $
+ */
+public interface RepositoryAdmin
+{
+ /**
+ * Discover any resources that match the given filter.
+ *
+ * This is not a detailed search, but a first scan of applicable resources.
+ *
+ * ### Checking the capabilities of the filters is not possible because that
+ * requires a new construct in the filter.
+ *
+ * The filter expression can assert any of the main headers of the resource.
+ * The attributes that can be checked are:
+ *
+ * <ol>
+ * <li>name</li>
+ * <li>version (uses filter matching rules)</li>
+ * <li>description</li>
+ * <li>category</li>
+ * <li>copyright</li>
+ * <li>license</li>
+ * <li>source</li>
+ * </ol>
+ *
+ * @param filterExpr
+ * A standard OSGi filter
+ * @return List of resources matching the filters.
+ */
+ Resource[] discoverResources(String filterExpr);
+
+ /**
+ * Create a resolver.
+ *
+ * @param resource
+ * @return
+ */
+ Resolver resolver();
+
+ /**
+ * Add a new repository to the federation.
+ *
+ * The url must point to a repository XML file.
+ *
+ * @param repository
+ * @return
+ * @throws Exception
+ */
+ Repository addRepository(URL repository) throws Exception;
+
+ boolean removeRepository(URL repository);
+
+ /**
+ * List all the repositories.
+ *
+ * @return
+ */
+ Repository[] listRepositories();
+
+ Resource getResource(String respositoryId);
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/RepositoryPermission.java b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/RepositoryPermission.java
new file mode 100644
index 0000000..559a735
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/RepositoryPermission.java
@@ -0,0 +1,40 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.obr/src/org/osgi/service/obr/RepositoryPermission.java,v 1.3 2006/03/16 14:56:17 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2006). All Rights Reserved.
+ *
+ * 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.
+ */
+
+// This document is an experimental draft to enable interoperability
+// between bundle repositories. There is currently no commitment to
+// turn this draft into an official specification.
+package org.osgi.service.obr;
+
+import java.security.BasicPermission;
+
+/**
+ * TODO OBR - Implement repository permission.
+ *
+ * @version $Revision: 1.3 $
+ */
+public class RepositoryPermission extends BasicPermission
+{
+
+ public RepositoryPermission(String name)
+ {
+ super(name);
+
+ }
+
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Requirement.java b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Requirement.java
new file mode 100644
index 0000000..e6ea908
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Requirement.java
@@ -0,0 +1,53 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.obr/src/org/osgi/service/obr/Requirement.java,v 1.4 2006/03/16 14:56:17 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2006). All Rights Reserved.
+ *
+ * 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.
+ */
+
+// This document is an experimental draft to enable interoperability
+// between bundle repositories. There is currently no commitment to
+// turn this draft into an official specification.
+package org.osgi.service.obr;
+
+/**
+ * A named requirement specifies the need for certain capabilities with the same
+ * name.
+ *
+ * @version $Revision: 1.4 $
+ */
+public interface Requirement
+{
+
+ /**
+ * Return the name of the requirement.
+ */
+ String getName();
+
+ /**
+ * Return the filter.
+ *
+ */
+ String getFilter();
+
+ boolean isMultiple();
+
+ boolean isOptional();
+
+ boolean isExtend();
+
+ String getComment();
+
+ boolean isSatisfied(Capability capability);
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Resolver.java b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Resolver.java
new file mode 100644
index 0000000..629159b
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Resolver.java
@@ -0,0 +1,44 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.obr/src/org/osgi/service/obr/Resolver.java,v 1.3 2006/03/16 14:56:17 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2006). All Rights Reserved.
+ *
+ * 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.
+ */
+
+// This document is an experimental draft to enable interoperability
+// between bundle repositories. There is currently no commitment to
+// turn this draft into an official specification.
+package org.osgi.service.obr;
+
+public interface Resolver
+{
+
+ void add(Resource resource);
+
+ Requirement[] getUnsatisfiedRequirements();
+
+ Resource[] getOptionalResources();
+
+ Requirement[] getReason(Resource resource);
+
+ Resource[] getResources(Requirement requirement);
+
+ Resource[] getRequiredResources();
+
+ Resource[] getAddedResources();
+
+ boolean resolve();
+
+ void deploy(boolean start);
+}
\ No newline at end of file
diff --git a/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Resource.java b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Resource.java
new file mode 100644
index 0000000..e6b050b
--- /dev/null
+++ b/org.apache.felix.bundlerepository/src/main/java/org/osgi/service/obr/Resource.java
@@ -0,0 +1,86 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.obr/src/org/osgi/service/obr/Resource.java,v 1.5 2006/03/16 14:56:17 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2006). All Rights Reserved.
+ *
+ * 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.
+ */
+
+// This document is an experimental draft to enable interoperability
+// between bundle repositories. There is currently no commitment to
+// turn this draft into an official specification.
+package org.osgi.service.obr;
+
+import java.net.URL;
+import java.util.Map;
+
+import org.osgi.framework.Version;
+
+/**
+ * A resource is an abstraction of a downloadable thing, like a bundle.
+ *
+ * Resources have capabilities and requirements. All a resource's requirements
+ * must be satisfied before it can be installed.
+ *
+ * @version $Revision: 1.5 $
+ */
+public interface Resource
+{
+ final String LICENSE_URL = "license";
+
+ final String DESCRIPTION = "description";
+
+ final String DOCUMENTATION_URL = "documentation";
+
+ final String COPYRIGHT = "copyright";
+
+ final String SOURCE_URL = "source";
+
+ final String SYMBOLIC_NAME = "symbolicname";
+
+ final String PRESENTATION_NAME = "presentationname";
+
+ final String ID = "id";
+
+ final String VERSION = "version";
+
+ final String URL = "url";
+
+ final String SIZE = "size";
+
+ final static String[] KEYS = { DESCRIPTION, SIZE, ID, LICENSE_URL,
+ DOCUMENTATION_URL, COPYRIGHT, SOURCE_URL, PRESENTATION_NAME,
+ SYMBOLIC_NAME, VERSION, URL };
+
+ // get readable name
+
+ Map getProperties();
+
+ String getSymbolicName();
+
+ String getPresentationName();
+
+ Version getVersion();
+
+ String getId();
+
+ URL getURL();
+
+ Requirement[] getRequirements();
+
+ Capability[] getCapabilities();
+
+ String[] getCategories();
+
+ Repository getRepository();
+}
\ No newline at end of file