FELIX-2113: Add obr:find and obr:resolve to find matching resources and show resolution output

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@912219 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/karaf/pom.xml b/karaf/pom.xml
index d7735fa..890593c 100644
--- a/karaf/pom.xml
+++ b/karaf/pom.xml
@@ -72,7 +72,7 @@
         <depends.maven.plugin.version>1.0</depends.maven.plugin.version>
         <easymock.version>2.4</easymock.version>
         <equinox.version>3.5.1.v20090827</equinox.version>
-        <felix.bundlerepository.version>1.4.0</felix.bundlerepository.version>
+        <felix.bundlerepository.version>1.5.0-SNAPSHOT</felix.bundlerepository.version>
         <felix.compendium.version>1.2.0</felix.compendium.version>
         <felix.configadmin.version>1.2.4</felix.configadmin.version>
         <felix.fileinstall.version>2.0.8</felix.fileinstall.version>
diff --git a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/FindCommand.java b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/FindCommand.java
new file mode 100644
index 0000000..51368e2
--- /dev/null
+++ b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/FindCommand.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.shell.obr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.Command;
+import org.apache.felix.karaf.shell.obr.util.RequirementImpl;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.Version;
+import org.osgi.service.obr.Capability;
+import org.osgi.service.obr.Repository;
+import org.osgi.service.obr.RepositoryAdmin;
+import org.osgi.service.obr.Requirement;
+import org.osgi.service.obr.Resource;
+
+@Command(scope = "obr", name = "find", description = "Find OBR bundles for a given filter")
+public class FindCommand extends ObrCommandSupport {
+
+    @Argument(index = 0, name = "requirement", description = "Requirement", required = true, multiValued = false)
+    String requirement;
+
+    protected void doExecute(RepositoryAdmin admin) throws Exception {
+        List<Resource> matching = new ArrayList<Resource>();
+        Repository[] repos = admin.listRepositories();
+        Requirement req = parseRequirement(requirement);
+        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])) {
+                        matching.add(resources[resIdx]);
+                        break;
+                    }
+                }
+            }
+        }
+        if (matching.isEmpty()) {
+            System.out.println("No matching resources.");
+        } else {
+            for (Resource resource : matching) {
+                String name = resource.getPresentationName();
+                Version version = resource.getVersion();
+                System.out.println(version != null ? name + " (" + version + ")" : name);
+            }
+        }
+    }
+
+    private Requirement parseRequirement(String req) throws InvalidSyntaxException {
+        int p = req.indexOf(':');
+        String name;
+        String filter;
+        if (p > 0) {
+            name = req.substring(0, p);
+            filter = req.substring(p + 1);
+        } else {
+            if (req.contains("package")) {
+                name = "package";
+            } else {
+                name = "bundle";
+            }
+            filter = req;
+        }
+        if (!filter.startsWith("(")) {
+            filter = "(" + filter + ")";
+        }
+        Filter flt = FrameworkUtil.createFilter(filter);
+        return new RequirementImpl(name, flt);
+    }
+
+}
\ No newline at end of file
diff --git a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/InfoCommand.java b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/InfoCommand.java
index 493460c..e66a65f 100644
--- a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/InfoCommand.java
+++ b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/InfoCommand.java
@@ -88,7 +88,7 @@
             out.println("Requires:");
             for (int i = 0; i < reqs.length; i++)
             {
-                out.println("   " + reqs[i].getFilter());
+                out.println("   " + reqs[i].getName() + ":" + reqs[i].getFilter());
             }
         }
 
@@ -98,7 +98,7 @@
             out.println("Capabilities:");
             for (int i = 0; i < caps.length; i++)
             {
-                out.println("   " + caps[i].getProperties());
+                out.println("   " + caps[i].getName() + ":" + caps[i].getProperties());
             }
         }
     }
diff --git a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/ObrCommandSupport.java b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/ObrCommandSupport.java
index 94b16c1..80ec89e 100644
--- a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/ObrCommandSupport.java
+++ b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/ObrCommandSupport.java
@@ -114,7 +114,7 @@
         String[] target;
         int idx = bundle.indexOf(VERSION_DELIM);
         if (idx > 0) {
-            target = new String[] { bundle.substring(0, idx), bundle.substring(idx) };
+            target = new String[] { bundle.substring(0, idx), bundle.substring(idx+1) };
         }
         else
         {
diff --git a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/ResolveCommand.java b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/ResolveCommand.java
new file mode 100644
index 0000000..a64a6f7
--- /dev/null
+++ b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/ResolveCommand.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.shell.obr;
+
+import java.util.List;
+
+import org.apache.felix.gogo.commands.Argument;
+import org.apache.felix.gogo.commands.Command;
+import org.apache.felix.gogo.commands.Option;
+import org.apache.felix.karaf.shell.obr.util.RequirementImpl;
+import org.apache.felix.karaf.shell.obr.util.ResourceImpl;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.obr.RepositoryAdmin;
+import org.osgi.service.obr.Requirement;
+import org.osgi.service.obr.Resolver;
+import org.osgi.service.obr.Resource;
+
+@Command(scope = "obr", name = "resolve", description = "Show the resolution output for a given set of requirements")
+public class ResolveCommand extends ObrCommandSupport {
+
+    @Option(name = "-w", aliases = "--why", description = "Display the reason if the inclusion of the resource")
+    boolean why;
+
+    @Argument(index = 0, name = "requirements", description = "Requirements", required = true, multiValued = true)
+    List<String> requirements;
+
+    protected void doExecute(RepositoryAdmin admin) throws Exception {
+        Resource resource = new ResourceImpl(null, null, null, null, null, null, getRequirements(), null, null, null);
+        Resolver resolver = admin.resolver();
+        resolver.add(resource);
+        if (resolver.resolve()) {
+            Resource[] resources;
+            resources = resolver.getRequiredResources();
+            if ((resources != null) && (resources.length > 0)) {
+                System.out.println("Required resource(s):");
+                printUnderline(System.out, 21);
+                for (int resIdx = 0; resIdx < resources.length; resIdx++) {
+                    System.out.println("   " + resources[resIdx].getPresentationName()
+                        + " (" + resources[resIdx].getVersion() + ")");
+                    if (why) {
+                        Requirement[] r = resolver.getReason(resources[resIdx]);
+                        for (int reqIdx = 0; r != null && reqIdx < r.length; reqIdx++) {
+                            System.out.println("      - " + r[reqIdx].getName() + ":" + r[reqIdx].getFilter());
+                        }
+                    }
+                }
+            }
+            resources = resolver.getOptionalResources();
+            if ((resources != null) && (resources.length > 0)) {
+                System.out.println();
+                System.out.println("Optional resource(s):");
+                printUnderline(System.out, 21);
+                for (int resIdx = 0; resIdx < resources.length; resIdx++) {
+                    System.out.println("   " + resources[resIdx].getPresentationName()
+                        + " (" + resources[resIdx].getVersion() + ")");
+                    if (why) {
+                        Requirement[] r = resolver.getReason(resources[resIdx]);
+                        for (int reqIdx = 0; r != null && reqIdx < r.length; reqIdx++) {
+                            System.out.println("      - " + r[reqIdx].getName() + ":" + r[reqIdx].getFilter());
+                        }
+                    }
+                }
+            }
+        } else {
+            Requirement[] reqs = resolver.getUnsatisfiedRequirements();
+            if ((reqs != null) && (reqs.length > 0)) {
+                System.out.println("Unsatisfied requirement(s):");
+                printUnderline(System.out, 27);
+                for (int reqIdx = 0; reqIdx < reqs.length; reqIdx++) {
+                    System.out.println("   " + reqs[reqIdx].getName() + ":" + reqs[reqIdx].getFilter());
+                    Resource[] resources = resolver.getResources(reqs[reqIdx]);
+                    for (int resIdx = 0; resIdx < resources.length; resIdx++) {
+                        System.out.println("      " + resources[resIdx].getPresentationName());
+                    }
+                }
+            } else {
+                System.out.println("Could not resolve targets.");
+            }
+        }
+    }
+
+    private Requirement[] getRequirements() throws InvalidSyntaxException {
+        Requirement[] reqs = new Requirement[requirements.size()];
+        for (int i = 0; i < reqs.length; i++) {
+            reqs[i] = parseRequirement(requirements.get(i));
+        }
+        return reqs;
+    }
+
+    private Requirement parseRequirement(String req) throws InvalidSyntaxException {
+        int p = req.indexOf(':');
+        String name;
+        String filter;
+        if (p > 0) {
+            name = req.substring(0, p);
+            filter = req.substring(p + 1);
+        } else {
+            if (req.contains("package")) {
+                name = "package";
+            } else {
+                name = "bundle";
+            }
+            filter = req;
+        }
+        if (!filter.startsWith("(")) {
+            filter = "(" + filter + ")";
+        }
+        Filter flt = FrameworkUtil.createFilter(filter);
+        return new RequirementImpl(name, flt);
+    }
+
+}
\ No newline at end of file
diff --git a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/SourceCommand.java b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/SourceCommand.java
index 0c81967..3e8dcfd 100644
--- a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/SourceCommand.java
+++ b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/SourceCommand.java
@@ -22,6 +22,7 @@
 import org.apache.felix.gogo.commands.Option;
 import org.apache.felix.gogo.commands.Argument;
 import org.apache.felix.gogo.commands.Command;
+import org.apache.felix.karaf.shell.obr.util.FileUtil;
 import org.osgi.service.obr.RepositoryAdmin;
 import org.osgi.service.obr.Resource;
 
diff --git a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/FileUtil.java b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/FileUtil.java
similarity index 98%
rename from karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/FileUtil.java
rename to karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/FileUtil.java
index b70e15a..7ebfbb4 100644
--- a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/FileUtil.java
+++ b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/FileUtil.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.felix.karaf.shell.obr;
+package org.apache.felix.karaf.shell.obr.util;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
diff --git a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/IteratorToEnumeration.java b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/IteratorToEnumeration.java
new file mode 100644
index 0000000..fc29ca9
--- /dev/null
+++ b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/IteratorToEnumeration.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.shell.obr.util;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+
+public class IteratorToEnumeration implements Enumeration
+{
+    private Iterator m_iter = null;
+
+    public IteratorToEnumeration(Iterator iter)
+    {
+        m_iter = iter;
+    }
+
+    public boolean hasMoreElements()
+    {
+        if (m_iter == null)
+            return false;
+        return m_iter.hasNext();
+    }
+
+    public Object nextElement()
+    {
+        if (m_iter == null)
+            return null;
+        return m_iter.next();
+    }
+}
diff --git a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/MapToDictionary.java b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/MapToDictionary.java
new file mode 100644
index 0000000..15283fa
--- /dev/null
+++ b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/MapToDictionary.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.karaf.shell.obr.util;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Map;
+
+/**
+ * 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/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/RequirementImpl.java b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/RequirementImpl.java
new file mode 100644
index 0000000..7ccf6e8
--- /dev/null
+++ b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/RequirementImpl.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.karaf.shell.obr.util;
+
+import org.osgi.framework.Filter;
+import org.osgi.service.obr.Capability;
+import org.osgi.service.obr.Requirement;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class RequirementImpl implements Requirement {
+    private final String name;
+    private final Filter filter;
+    private final boolean multiple;
+    private final boolean optional;
+    private final boolean extend;
+    private final String comment;
+
+    public RequirementImpl(String name, Filter filter) {
+        this(name, filter, false, false, false, null);
+    }
+
+    public RequirementImpl(String name, Filter filter, boolean multiple, boolean optional, boolean extend, String comment) {
+        this.name = name;
+        this.filter = filter;
+        this.multiple = multiple;
+        this.optional = optional;
+        this.extend = extend;
+        this.comment = comment;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getFilter() {
+        return filter.toString();
+    }
+
+    public boolean isMultiple() {
+        return multiple;
+    }
+
+    public boolean isOptional() {
+        return optional;
+    }
+
+    public boolean isExtend() {
+        return extend;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public boolean isSatisfied(Capability capability) {
+        return filter.match(new MapToDictionary(capability.getProperties()));
+    }
+}
diff --git a/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/ResourceImpl.java b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/ResourceImpl.java
new file mode 100644
index 0000000..cdb7f45
--- /dev/null
+++ b/karaf/shell/obr/src/main/java/org/apache/felix/karaf/shell/obr/util/ResourceImpl.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.karaf.shell.obr.util;
+
+import org.osgi.framework.Version;
+import org.osgi.service.obr.Capability;
+import org.osgi.service.obr.Repository;
+import org.osgi.service.obr.Requirement;
+import org.osgi.service.obr.Resource;
+
+import java.net.URL;
+import java.util.Map;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class ResourceImpl implements Resource
+{
+
+  private final Map properties;
+  private final String symbolicName;
+  private final String presentationName;
+  private final Version version;
+  private final String id;
+  private final URL url;
+  private final Requirement[] requirements;
+  private final Capability[] capabilities;
+  private final String[] categories;
+  private final Repository repository;
+
+
+  public ResourceImpl(Map properties, String symbolicName, String presentationName, Version version, String id, URL url, Requirement[] requirements, Capability[] capabilities, String[] categories, Repository repository)
+  {
+    this.properties = properties;
+    this.symbolicName = symbolicName;
+    this.presentationName = presentationName;
+    this.version = version;
+    this.id = id;
+    this.url = url;
+    this.requirements = requirements;
+    this.capabilities = capabilities;
+    this.categories = categories;
+    this.repository = repository;
+  }
+
+  public Map getProperties()
+  {
+    return properties;
+  }
+
+  public String getSymbolicName()
+  {
+    return symbolicName;
+  }
+
+  public String getPresentationName()
+  {
+    return presentationName;
+  }
+
+  public Version getVersion()
+  {
+    return version;
+  }
+
+  public String getId()
+  {
+    return id;
+  }
+
+  public URL getURL()
+  {
+    return url;
+  }
+
+  public Requirement[] getRequirements()
+  {
+    return requirements;
+  }
+
+  public Capability[] getCapabilities()
+  {
+    return capabilities;
+  }
+
+  public String[] getCategories()
+  {
+    return categories;
+  }
+
+  public Repository getRepository()
+  {
+    return repository;
+  }
+}
diff --git a/karaf/shell/obr/src/main/resources/OSGI-INF/blueprint/shell-obr.xml b/karaf/shell/obr/src/main/resources/OSGI-INF/blueprint/shell-obr.xml
index 096cc00..b56ed03 100644
--- a/karaf/shell/obr/src/main/resources/OSGI-INF/blueprint/shell-obr.xml
+++ b/karaf/shell/obr/src/main/resources/OSGI-INF/blueprint/shell-obr.xml
@@ -26,6 +26,9 @@
         <command name="obr/deploy">
             <action class="org.apache.felix.karaf.shell.obr.DeployCommand"/>
         </command>
+        <command name="obr/find">
+            <action class="org.apache.felix.karaf.shell.obr.FindCommand"/>
+        </command>
         <command name="obr/info">
             <action class="org.apache.felix.karaf.shell.obr.InfoCommand"/>
         </command>
@@ -35,11 +38,14 @@
         <command name="obr/listUrl">
             <action class="org.apache.felix.karaf.shell.obr.ListUrlCommand"/>
         </command>
+        <command name="obr/refreshUrl">
+            <action class="org.apache.felix.karaf.shell.obr.RefreshUrlCommand"/>
+        </command>
         <command name="obr/removeUrl">
             <action class="org.apache.felix.karaf.shell.obr.RemoveUrlCommand"/>
         </command>
-        <command name="obr/refreshUrl">
-            <action class="org.apache.felix.karaf.shell.obr.RefreshUrlCommand"/>
+        <command name="obr/resolve">
+            <action class="org.apache.felix.karaf.shell.obr.ResolveCommand"/>
         </command>
         <command name="obr/source">
             <action class="org.apache.felix.karaf.shell.obr.SourceCommand"/>