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