You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2014/11/13 23:23:38 UTC

[05/18] incubator-brooklyn git commit: Catalog versioning - Reference installed OSGi bundles by name+version

Catalog versioning - Reference installed OSGi bundles by name+version

Also fix failing tests from previous commits.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/268aaf6b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/268aaf6b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/268aaf6b

Branch: refs/heads/master
Commit: 268aaf6bc85bcdf04496ca17d3aaa3a8b199dc37
Parents: 1e88783
Author: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Authored: Mon Jul 21 19:08:02 2014 +0300
Committer: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Committed: Thu Nov 13 11:49:49 2014 +0200

----------------------------------------------------------------------
 .../main/java/brooklyn/catalog/CatalogItem.java |  13 +-
 .../catalog/internal/CatalogBundleDto.java      |  66 ++++++++
 .../catalog/internal/CatalogLibrariesDo.java    |   5 +-
 .../catalog/internal/CatalogLibrariesDto.java   |  62 ++++++--
 .../brooklyn/catalog/internal/CatalogUtils.java |  17 +-
 .../catalog/internal/CatalogXmlSerializer.java  |   6 +-
 .../OsgiBrooklynClassLoadingContext.java        |   8 +-
 .../brooklyn/management/ha/OsgiManager.java     | 131 ++++++++++------
 .../src/main/java/brooklyn/util/osgi/Osgis.java |  65 ++++++--
 .../brooklyn/camp/lite/CampYamlLiteTest.java    |  19 ++-
 .../catalog/internal/CatalogDtoTest.java        |  15 +-
 .../catalog/internal/CatalogLoadTest.java       |  21 ++-
 .../management/osgi/OsgiStandaloneTest.java     |   2 +
 .../camp/lite/test-app-service-blueprint.yaml   |   2 +-
 .../brooklyn/catalog/internal/osgi-catalog.xml  |   4 +-
 .../camp/brooklyn/AbstractYamlTest.java         |   6 +-
 .../camp/brooklyn/ReferencedYamlTest.java       |   7 +-
 .../CatalogOsgiVersionMoreEntityTest.java       |  14 +-
 .../brooklyn/catalog/CatalogYamlEntityTest.java | 157 +++++++++++++++++--
 .../brooklyn/catalog/CatalogYamlPolicyTest.java |   8 +-
 ...more-entity-v1-with-policy-osgi-catalog.yaml |   2 +-
 .../catalog/more-entity-v2-osgi-catalog.yaml    |   2 +-
 usage/camp/src/test/resources/yaml-ref-app.yaml |   2 +-
 .../src/test/resources/yaml-ref-catalog.yaml    |   2 +-
 .../src/test/resources/yaml-ref-entity.yaml     |   2 +-
 .../rest/resources/CatalogResourceTest.java     |  18 ++-
 26 files changed, 510 insertions(+), 146 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/api/src/main/java/brooklyn/catalog/CatalogItem.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/catalog/CatalogItem.java b/api/src/main/java/brooklyn/catalog/CatalogItem.java
index 409bd7e..1f67ccd 100644
--- a/api/src/main/java/brooklyn/catalog/CatalogItem.java
+++ b/api/src/main/java/brooklyn/catalog/CatalogItem.java
@@ -18,7 +18,7 @@
  */
 package brooklyn.catalog;
 
-import java.util.List;
+import java.util.Collection;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -36,10 +36,19 @@ public interface CatalogItem<T,SpecT> extends BrooklynObject, Rebindable {
     public static enum CatalogItemType {
         TEMPLATE, ENTITY, POLICY, CONFIGURATION
     }
+    
+    public static interface CatalogBundle {
+        public String getName();
+        public String getVersion();
+        public String getUrl();
+
+        /** @return true if the bundle reference contains both name and version*/
+        public boolean isNamed();
+    }
 
     @Beta
     public static interface CatalogItemLibraries {
-        List<String> getBundles();
+        Collection<CatalogBundle> getBundles();
     }
 
     public CatalogItemType getCatalogItemType();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/main/java/brooklyn/catalog/internal/CatalogBundleDto.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogBundleDto.java b/core/src/main/java/brooklyn/catalog/internal/CatalogBundleDto.java
new file mode 100644
index 0000000..6ef148e
--- /dev/null
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogBundleDto.java
@@ -0,0 +1,66 @@
+/*
+ * 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 brooklyn.catalog.internal;
+
+import com.google.common.base.Objects;
+
+import brooklyn.catalog.CatalogItem.CatalogBundle;
+
+public class CatalogBundleDto implements CatalogBundle {
+    private String name;
+    private String version;
+    private String url;
+
+    public CatalogBundleDto() {}
+
+    public CatalogBundleDto(String name, String version, String url) {
+        this.name = name;
+        this.version = version;
+        this.url = url;
+    }
+
+    @Override
+    public boolean isNamed() {
+        return name != null && version != null;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public String getUrl() {
+        return url;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this)
+                .add("name", name)
+                .add("version", version)
+                .add("url", url)
+                .toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/main/java/brooklyn/catalog/internal/CatalogLibrariesDo.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogLibrariesDo.java b/core/src/main/java/brooklyn/catalog/internal/CatalogLibrariesDo.java
index fb461f3..fa5f478 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogLibrariesDo.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogLibrariesDo.java
@@ -18,9 +18,10 @@
  */
 package brooklyn.catalog.internal;
 
-import java.util.List;
+import java.util.Collection;
 
 import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogItem.CatalogBundle;
 
 import com.google.common.base.Preconditions;
 
@@ -34,7 +35,7 @@ public class CatalogLibrariesDo implements CatalogItem.CatalogItemLibraries {
     }
 
     @Override
-    public List<String> getBundles() {
+    public Collection<CatalogBundle> getBundles() {
         return librariesDto.getBundles();
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/main/java/brooklyn/catalog/internal/CatalogLibrariesDto.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogLibrariesDto.java b/core/src/main/java/brooklyn/catalog/internal/CatalogLibrariesDto.java
index bcbd25b..646d69c 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogLibrariesDto.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogLibrariesDto.java
@@ -20,34 +20,41 @@ package brooklyn.catalog.internal;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogItem.CatalogBundle;
+
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
-import brooklyn.catalog.CatalogItem;
-
 public class CatalogLibrariesDto implements CatalogItem.CatalogItemLibraries {
 
     private static Logger LOG = LoggerFactory.getLogger(CatalogLibrariesDto.class);
 
-    // TODO: Incorporate name and version into entries
-    private List<String> bundles = new CopyOnWriteArrayList<String>();
+    private Collection<CatalogBundle> bundles = new CopyOnWriteArrayList<CatalogBundle>();
 
-    public void addBundle(String url) {
+    public void addBundle(String name, String version, String url) {
         Preconditions.checkNotNull(bundles, "Cannot add a bundle to a deserialized DTO");
-        bundles.add(Preconditions.checkNotNull(url, "url"));
+        if (name == null && version == null) {
+            Preconditions.checkNotNull(url, "url");
+        } else {
+            Preconditions.checkNotNull(name, "name");
+            Preconditions.checkNotNull(version, "version");
+        }
+        
+        bundles.add(new CatalogBundleDto(name, version, url));
     }
 
     /**
      * @return An immutable copy of the bundle URLs referenced by this object
      */
-    public List<String> getBundles() {
+    @Override
+    public Collection<CatalogBundle> getBundles() {
         if (bundles == null) {
             // can be null on deserialization
             return Collections.emptyList();
@@ -63,15 +70,37 @@ public class CatalogLibrariesDto implements CatalogItem.CatalogItemLibraries {
         CatalogLibrariesDto dto = new CatalogLibrariesDto();
         for (Object object : possibleLibraries) {
             if (object instanceof Map) {
-                @SuppressWarnings("rawtypes")
-                Map entry = (Map) object;
-                // these might be useful in the future
-//                String name = stringValOrNull(entry, "name");
-//                String version = stringValOrNull(entry, "version");
+                Map<?, ?> entry = (Map<?, ?>) object;
+                String name = stringValOrNull(entry, "name");
+                String version = stringValOrNull(entry, "version");
                 String url = stringValOrNull(entry, "url");
-                dto.addBundle(url);
+                dto.addBundle(name, version, url);
             } else if (object instanceof String) {
-                dto.addBundle((String) object);
+                String inlineRef = (String) object;
+
+                final String name;
+                final String version;
+                final String url;
+
+                //Infer reference type (heuristically)
+                if (inlineRef.contains("/") || inlineRef.contains("\\")) {
+                    //looks like an url/file path
+                    name = null;
+                    version = null;
+                    url = inlineRef;
+                } else if (inlineRef.indexOf(CatalogUtils.VERSION_DELIMITER) != -1) {
+                    //looks like a name+version ref
+                    name = CatalogUtils.getIdFromVersionedId(inlineRef);
+                    version = CatalogUtils.getVersionFromVersionedId(inlineRef);
+                    url = null;
+                } else {
+                    //assume it to be relative url
+                    name = null;
+                    version = null;
+                    url = inlineRef;
+                }
+
+                dto.addBundle(name, version, url);
             } else {
                 LOG.debug("Unexpected entry in libraries list neither string nor map: " + object);
             }
@@ -79,8 +108,7 @@ public class CatalogLibrariesDto implements CatalogItem.CatalogItemLibraries {
         return dto;
     }
 
-    @SuppressWarnings("rawtypes")
-    private static String stringValOrNull(Map map, String key) {
+    private static String stringValOrNull(Map<?, ?> map, String key) {
         Object val = map.get(key);
         return val != null ? String.valueOf(val) : null;
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java b/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
index 6f33574..312351f 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogUtils.java
@@ -18,20 +18,17 @@
  */
 package brooklyn.catalog.internal;
 
-import java.util.List;
+import java.util.Collection;
 
 import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.annotations.Beta;
-import com.google.common.base.Joiner;
-import com.google.common.base.Stopwatch;
-
 import brooklyn.basic.BrooklynObject;
 import brooklyn.basic.BrooklynObjectInternal;
 import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogItem.CatalogBundle;
 import brooklyn.catalog.CatalogItem.CatalogItemLibraries;
 import brooklyn.catalog.internal.BasicBrooklynCatalog.BrooklynLoaderTracker;
 import brooklyn.config.BrooklynLogging;
@@ -47,6 +44,10 @@ import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.util.guava.Maybe;
 import brooklyn.util.time.Time;
 
+import com.google.common.annotations.Beta;
+import com.google.common.base.Joiner;
+import com.google.common.base.Stopwatch;
+
 public class CatalogUtils {
     private static final Logger log = LoggerFactory.getLogger(CatalogUtils.class);
 
@@ -66,7 +67,7 @@ public class CatalogUtils {
         BrooklynClassLoadingContextSequential result = new BrooklynClassLoadingContextSequential(mgmt);
 
         if (libraries!=null) {
-            List<String> bundles = libraries.getBundles();
+            Collection<CatalogBundle> bundles = libraries.getBundles();
             if (bundles!=null && !bundles.isEmpty()) {
                 result.add(new OsgiBrooklynClassLoadingContext(mgmt, catalogItemId, bundles));
             }
@@ -88,7 +89,7 @@ public class CatalogUtils {
         if (libraries == null) return;
 
         ManagementContextInternal mgmt = (ManagementContextInternal) managementContext;
-        List<String> bundles = libraries.getBundles();
+        Collection<CatalogBundle> bundles = libraries.getBundles();
         if (!bundles.isEmpty()) {
             Maybe<OsgiManager> osgi = mgmt.getOsgiManager();
             if (osgi.isAbsent()) {
@@ -99,7 +100,7 @@ public class CatalogUtils {
                     "Loading bundles in {}: {}", 
                     new Object[] {managementContext, Joiner.on(", ").join(bundles)});
             Stopwatch timer = Stopwatch.createStarted();
-            for (String bundleUrl : bundles) {
+            for (CatalogBundle bundleUrl : bundles) {
                 osgi.get().registerBundle(bundleUrl);
             }
             if (log.isDebugEnabled()) 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java b/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java
index ccac60b..0948149 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java
@@ -18,6 +18,8 @@
  */
 package brooklyn.catalog.internal;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -29,6 +31,8 @@ import brooklyn.util.xstream.XmlSerializer;
 public class CatalogXmlSerializer extends XmlSerializer<Object> {
 
     public CatalogXmlSerializer() {
+        xstream.addDefaultImplementation(ArrayList.class, Collection.class);
+        
         xstream.aliasType("list", List.class);
         xstream.aliasType("map", Map.class);
 
@@ -54,7 +58,7 @@ public class CatalogXmlSerializer extends XmlSerializer<Object> {
         xstream.registerConverter(new EnumCaseForgivingSingleValueConverter(CatalogScanningModes.class));
 
         xstream.aliasType("libraries", CatalogLibrariesDto.class);
-        xstream.addImplicitCollection(CatalogLibrariesDto.class, "bundles", "bundle", String.class);
+        xstream.addImplicitCollection(CatalogLibrariesDto.class, "bundles", "bundle", CatalogBundleDto.class);
 
         // Note: the management context is being omitted because it is unnecessary for
         // representations of catalogues generated with this serializer.

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/main/java/brooklyn/management/classloading/OsgiBrooklynClassLoadingContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/classloading/OsgiBrooklynClassLoadingContext.java b/core/src/main/java/brooklyn/management/classloading/OsgiBrooklynClassLoadingContext.java
index 74cc6cf..bb763d5 100644
--- a/core/src/main/java/brooklyn/management/classloading/OsgiBrooklynClassLoadingContext.java
+++ b/core/src/main/java/brooklyn/management/classloading/OsgiBrooklynClassLoadingContext.java
@@ -19,22 +19,22 @@
 package brooklyn.management.classloading;
 
 import java.net.URL;
-import java.util.List;
+import java.util.Collection;
 
+import brooklyn.catalog.CatalogItem.CatalogBundle;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.ha.OsgiManager;
 import brooklyn.management.internal.ManagementContextInternal;
-import brooklyn.util.exceptions.Exceptions;
 import brooklyn.util.guava.Maybe;
 
 import com.google.common.base.Objects;
 
 public class OsgiBrooklynClassLoadingContext extends AbstractBrooklynClassLoadingContext {
 
-    private final List<String> bundles;
+    private final Collection<CatalogBundle> bundles;
     private final String catalogItemId;
 
-    public OsgiBrooklynClassLoadingContext(ManagementContext mgmt, String catalogItemId, List<String> bundles) {
+    public OsgiBrooklynClassLoadingContext(ManagementContext mgmt, String catalogItemId, Collection<CatalogBundle> bundles) {
         super(mgmt);
         this.bundles = bundles;
         this.catalogItemId = catalogItemId;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/main/java/brooklyn/management/ha/OsgiManager.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/management/ha/OsgiManager.java b/core/src/main/java/brooklyn/management/ha/OsgiManager.java
index 44b30af..47b0225 100644
--- a/core/src/main/java/brooklyn/management/ha/OsgiManager.java
+++ b/core/src/main/java/brooklyn/management/ha/OsgiManager.java
@@ -30,6 +30,7 @@ import org.osgi.framework.launch.Framework;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.catalog.CatalogItem.CatalogBundle;
 import brooklyn.config.BrooklynServerConfig;
 import brooklyn.config.ConfigKey;
 import brooklyn.util.collections.MutableMap;
@@ -38,6 +39,8 @@ import brooklyn.util.guava.Maybe;
 import brooklyn.util.os.Os;
 import brooklyn.util.os.Os.DeletionResult;
 import brooklyn.util.osgi.Osgis;
+import brooklyn.util.osgi.Osgis.BundleFinder;
+import brooklyn.util.osgi.Osgis.VersionedName;
 
 import com.google.common.base.Throwables;
 import com.google.common.collect.Iterables;
@@ -52,10 +55,10 @@ public class OsgiManager {
     
     protected Framework framework;
     protected File osgiTempDir;
-    
+
     // we could manage without this map but it is useful to validate what is a user-supplied url
-    protected Map<String,String> urlToBundleIdentifier = MutableMap.of();
-    
+    protected Map<String,VersionedName> urlToBundleIdentifier = MutableMap.of();
+
     public void start() {
         try {
             // TODO any extra startup args?
@@ -88,49 +91,83 @@ public class OsgiManager {
         framework = null;
     }
 
-    public void registerBundle(String bundleUrl) {
+    public synchronized void registerBundle(CatalogBundle bundle) {
         try {
-            String nv = urlToBundleIdentifier.get(bundleUrl);
+            if (checkBundleInstalledThrowIfInconsistent(bundle)) {
+                return;
+            }
+
+            Bundle b = Osgis.install(framework, bundle.getUrl());
+
+            checkCorrectlyInstalled(bundle, b);
+
+            urlToBundleIdentifier.put(bundle.getUrl(), new VersionedName(b));
+        } catch (BundleException e) {
+            log.debug("Bundle from "+bundle+" failed to install (rethrowing): "+e);
+            throw Throwables.propagate(e);
+        }
+    }
+
+    private void checkCorrectlyInstalled(CatalogBundle bundle, Bundle b) {
+        List<Bundle> matches = Osgis.bundleFinder(framework)
+                .symbolicName(b.getSymbolicName())
+                .version(b.getVersion().toString())
+                .findAll();
+        String nv = b.getSymbolicName()+":"+b.getVersion().toString();
+        if (matches.isEmpty()) {
+            log.error("OSGi could not find bundle "+nv+" in search after installing it from "+bundle.getUrl());
+        } else if (matches.size()==1) {
+            log.debug("Bundle from "+bundle.getUrl()+" successfully installed as " + nv + " ("+b+")");
+        } else {
+            log.warn("OSGi has multiple bundles matching "+nv+", when just installed from "+bundle.getUrl()+": "+matches+"; "
+                + "brooklyn will prefer the URL-based bundle for top-level references but any dependencies or "
+                + "import-packages will be at the mercy of OSGi. "
+                + "It is recommended to use distinct versions for different bundles, and the same URL for the same bundles.");
+        }
+    }
+
+    private boolean checkBundleInstalledThrowIfInconsistent(CatalogBundle bundle) {
+        String bundleUrl = bundle.getUrl();
+        if (bundleUrl != null) {
+            VersionedName nv = urlToBundleIdentifier.get(bundleUrl);
             if (nv!=null) {
-                if (Osgis.bundleFinder(framework).id(nv).requiringFromUrl(bundleUrl).find().isPresent()) {
+                if (bundle.isNamed() && !nv.equals(bundle.getName(), bundle.getVersion())) {
+                    throw new IllegalStateException("Bundle from "+bundleUrl+" already installed as "+nv+" but user explicitly requested "+bundle);
+                }
+                Maybe<Bundle> installedBundle = Osgis.bundleFinder(framework).requiringFromUrl(bundleUrl).find();
+                if (installedBundle.isPresent()) {
+                    if (bundle.isNamed()) {
+                        Bundle b = installedBundle.get();
+                        if (!nv.equals(b.getSymbolicName(), b.getVersion().toString())) {
+                            log.error("Bundle from "+bundleUrl+" already installed as "+nv+" but reports "+b.getSymbolicName()+":"+b.getVersion());
+                        }
+                    }
                     log.trace("Bundle from "+bundleUrl+" already installed as "+nv+"; not re-registering");
-                    return;
+                    return true;
                 } else {
-                    log.debug("Bundle "+nv+" from "+bundleUrl+" is known in map but not installed; perhaps in the process of installing?");
+                    log.error("Bundle "+nv+" from "+bundleUrl+" is known in map but not installed; perhaps in the process of installing?");
                 }
             }
-            
-            Bundle b = Osgis.install(framework, bundleUrl);
-            nv = b.getSymbolicName()+":"+b.getVersion().toString();
-            
-            List<Bundle> matches = Osgis.bundleFinder(framework).id(nv).findAll();
-            if (matches.isEmpty()) {
-                log.error("OSGi could not find bundle "+nv+" in search after installing it from "+bundleUrl);
-            } else if (matches.size()==1) {
-                log.debug("Bundle from "+bundleUrl+" successfully installed as " + nv + " ("+b+")");
+        } else {
+            Maybe<Bundle> installedBundle = Osgis.bundleFinder(framework).symbolicName(bundle.getName()).version(bundle.getVersion()).find();
+            if (installedBundle.isPresent()) {
+                log.trace("Bundle "+bundle+" installed from "+installedBundle.get().getLocation());
             } else {
-                log.warn("OSGi has multiple bundles matching "+nv+", when just installed from "+bundleUrl+": "+matches+"; "
-                    + "brooklyn will prefer the URL-based bundle for top-level references but any dependencies or "
-                    + "import-packages will be at the mercy of OSGi. "
-                    + "It is recommended to use distinct versions for different bundles, and the same URL for the same bundles.");
+                throw new IllegalStateException("Bundle "+bundle+" not previously registered, but URL is empty.");
             }
-            urlToBundleIdentifier.put(bundleUrl, nv);
-            
-        } catch (BundleException e) {
-            log.debug("Bundle from "+bundleUrl+" failed to install (rethrowing): "+e);
-            throw Throwables.propagate(e);
+            return true;
         }
+        return false;
     }
 
-    public <T> Maybe<Class<T>> tryResolveClass(String type, String... bundleUrlsOrNameVersionString) {
-        return tryResolveClass(type, Arrays.asList(bundleUrlsOrNameVersionString));
+    public <T> Maybe<Class<T>> tryResolveClass(String type, CatalogBundle... catalogBundles) {
+        return tryResolveClass(type, Arrays.asList(catalogBundles));
     }
-    public <T> Maybe<Class<T>> tryResolveClass(String type, Iterable<String> bundleUrlsOrNameVersionString) {
-        Map<String,Throwable> bundleProblems = MutableMap.of();
-        for (String bundleUrlOrNameVersionString: bundleUrlsOrNameVersionString) {
+    public <T> Maybe<Class<T>> tryResolveClass(String type, Iterable<CatalogBundle> catalogBundles) {
+        Map<CatalogBundle,Throwable> bundleProblems = MutableMap.of();
+        for (CatalogBundle catalogBundle: catalogBundles) {
             try {
-                Maybe<Bundle> bundle = findBundle(bundleUrlOrNameVersionString);
-                
+                Maybe<Bundle> bundle = findBundle(catalogBundle);
                 if (bundle.isPresent()) {
                     Bundle b = bundle.get();
                     Class<T> clazz;
@@ -147,17 +184,17 @@ public class OsgiManager {
                     }
                     return Maybe.of(clazz);
                 } else {
-                    bundleProblems.put(bundleUrlOrNameVersionString, ((Maybe.Absent<?>)bundle).getException());
+                    bundleProblems.put(catalogBundle, ((Maybe.Absent<?>)bundle).getException());
                 }
                 
             } catch (Exception e) {
                 // should come from classloading now; name formatting or missing bundle errors will be caught above 
                 Exceptions.propagateIfFatal(e);
-                bundleProblems.put(bundleUrlOrNameVersionString, e);
+                bundleProblems.put(catalogBundle, e);
 
                 Throwable cause = e.getCause();
                 if (cause != null && cause.getMessage().contains("Unresolved constraint in bundle")) {
-                    log.warn("Unresolved constraint resolving OSGi bundle "+bundleUrlOrNameVersionString+" to load "+type+": "+cause.getMessage());
+                    log.warn("Unresolved constraint resolving OSGi bundle "+catalogBundle+" to load "+type+": "+cause.getMessage());
                     if (log.isDebugEnabled()) log.debug("Trace for OSGi resolution failure", e);
                 }
             }
@@ -173,22 +210,20 @@ public class OsgiManager {
         }
     }
 
-    /** finds an installed bundle with the given URL or OSGi identifier ("symbolicName:version" string) */
-    public Maybe<Bundle> findBundle(String bundleUrlOrNameVersionString) {
-        String bundleNameVersion = urlToBundleIdentifier.get(bundleUrlOrNameVersionString);
-        if (bundleNameVersion==null) {
-            Maybe<String[]> nv = Osgis.parseOsgiIdentifier(bundleUrlOrNameVersionString);
-            if (nv.isPresent())
-                bundleNameVersion = bundleUrlOrNameVersionString;
+    public Maybe<Bundle> findBundle(CatalogBundle catalogBundle) {
+        BundleFinder bundleFinder = Osgis.bundleFinder(framework);
+        if (catalogBundle.isNamed()) {
+            bundleFinder.symbolicName(catalogBundle.getName()).version(catalogBundle.getVersion());
+        } else {
+            bundleFinder.requiringFromUrl(catalogBundle.getUrl());
         }
-        Maybe<Bundle> bundle = Osgis.bundleFinder(framework).id(bundleNameVersion).preferringFromUrl(bundleUrlOrNameVersionString).find();
-        return bundle;
+        return bundleFinder.find();
     }
 
-    public URL getResource(String name, Iterable<String> bundleUrlsOrNameVersionString) {
-        for (String bundleUrlOrNameVersionString: bundleUrlsOrNameVersionString) {
+    public URL getResource(String name, Iterable<CatalogBundle> catalogBundles) {
+        for (CatalogBundle catalogBundle: catalogBundles) {
             try {
-                Maybe<Bundle> bundle = findBundle(bundleUrlOrNameVersionString);
+                Maybe<Bundle> bundle = findBundle(catalogBundle);
                 if (bundle.isPresent()) {
                     URL result = bundle.get().getResource(name);
                     if (result!=null) return result;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/main/java/brooklyn/util/osgi/Osgis.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/osgi/Osgis.java b/core/src/main/java/brooklyn/util/osgi/Osgis.java
index f7508f6..98720a6 100644
--- a/core/src/main/java/brooklyn/util/osgi/Osgis.java
+++ b/core/src/main/java/brooklyn/util/osgi/Osgis.java
@@ -57,6 +57,7 @@ import org.osgi.framework.wiring.BundleCapability;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.catalog.CatalogItem.CatalogBundle;
 import brooklyn.util.ResourceUtils;
 import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
@@ -91,6 +92,34 @@ public class Osgis {
     private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF";
     private static final Set<String> SYSTEM_BUNDLES = MutableSet.of();
 
+    public static class VersionedName {
+        private String symbolicName;
+        private Version version;
+        public VersionedName(Bundle b) {
+            this.symbolicName = b.getSymbolicName();
+            this.version = b.getVersion();
+        }
+        public VersionedName(String symbolicName, Version version) {
+            this.symbolicName = symbolicName;
+            this.version = version;
+        }
+        @Override public String toString() {
+            return symbolicName + ":" + Strings.toString(version);
+        }
+        public boolean equals(String sn, String v) {
+            return symbolicName.equals(sn) && (version == null && v == null || version != null && version.toString().equals(v));
+        }
+        public boolean equals(String sn, Version v) {
+            return symbolicName.equals(sn) && (version == null && v == null || version != null && version.equals(v));
+        }
+        protected String getSymbolicName() {
+            return symbolicName;
+        }
+        protected Version getVersion() {
+            return version;
+        }
+    }
+    
     public static class BundleFinder {
         protected final Framework framework;
         protected String symbolicName;
@@ -117,14 +146,29 @@ public class Osgis {
             if (Strings.isBlank(symbolicNameOptionallyWithVersion))
                 return this;
             
-            Maybe<String[]> partsM = parseOsgiIdentifier(symbolicNameOptionallyWithVersion);
-            if (partsM.isAbsent())
+            Maybe<VersionedName> nv = parseOsgiIdentifier(symbolicNameOptionallyWithVersion);
+            if (nv.isAbsent())
                 throw new IllegalArgumentException("Cannot parse symbolic-name:version string '"+symbolicNameOptionallyWithVersion+"'");
-            String[] parts = partsM.get();
-            
-            symbolicName(parts[0]);
-            if (parts.length >= 2) version(parts[1]);
-            
+
+            return id(nv.get());
+        }
+
+        private BundleFinder id(VersionedName nv) {
+            symbolicName(nv.getSymbolicName());
+            if (nv.getVersion() != null) {
+                version(nv.getVersion().toString());
+            }
+            return this;
+        }
+
+        public BundleFinder bundle(CatalogBundle bundle) {
+            if (bundle.isNamed()) {
+                symbolicName(bundle.getName());
+                version(bundle.getVersion());
+            }
+            if (bundle.getUrl() != null) {
+                requiringFromUrl(bundle.getUrl());
+            }
             return this;
         }
 
@@ -567,7 +611,7 @@ public class Osgis {
     /** Takes a string which might be of the form "symbolic-name" or "symbolic-name:version" (or something else entirely)
      * and returns an array of 1 or 2 string items being the symbolic name or symbolic name and version if possible
      * (or returning {@link Maybe#absent()} if not, with a suitable error message). */
-    public static Maybe<String[]> parseOsgiIdentifier(String symbolicNameOptionalWithVersion) {
+    public static Maybe<VersionedName> parseOsgiIdentifier(String symbolicNameOptionalWithVersion) {
         if (Strings.isBlank(symbolicNameOptionalWithVersion))
             return Maybe.absent("OSGi identifier is blank");
         
@@ -575,13 +619,14 @@ public class Osgis {
         if (parts.length>2)
             return Maybe.absent("OSGi identifier has too many parts; max one ':' symbol");
         
+        Version v = null;
         try {
-            Version.parseVersion(parts[1]);
+            v = Version.parseVersion(parts[1]);
         } catch (IllegalArgumentException e) {
             return Maybe.absent("OSGi identifier has invalid version string");
         }
         
-        return Maybe.of(parts);
+        return Maybe.of(new VersionedName(parts[0], v));
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java b/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java
index 9eb56fd..84e3fc5 100644
--- a/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java
+++ b/core/src/test/java/brooklyn/camp/lite/CampYamlLiteTest.java
@@ -28,9 +28,9 @@ import io.brooklyn.camp.test.mock.web.MockWebPlatform;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.Reader;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -40,6 +40,7 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogItem.CatalogBundle;
 import brooklyn.catalog.CatalogPredicates;
 import brooklyn.catalog.internal.BasicBrooklynCatalog;
 import brooklyn.catalog.internal.CatalogDto;
@@ -58,7 +59,6 @@ import brooklyn.test.entity.LocalManagementContextForTests;
 import brooklyn.test.entity.TestApplication;
 import brooklyn.test.entity.TestEntity;
 import brooklyn.util.ResourceUtils;
-import brooklyn.util.collections.MutableList;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.config.ConfigBag;
 import brooklyn.util.stream.Streams;
@@ -66,7 +66,6 @@ import brooklyn.util.stream.Streams;
 import com.google.common.base.Joiner;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
 
 /** Tests of lightweight CAMP integration. Since the "real" integration is in brooklyn-camp project,
  * but some aspects of CAMP we want to be able to test here. */
@@ -161,9 +160,11 @@ public class CampYamlLiteTest {
         CatalogItem<Object, Object> retrievedItem = Iterables.getOnlyElement(retrievedItems);
         Assert.assertEquals(retrievedItem, realItem);
 
-        Set<String> expectedBundles = Sets.newHashSet(OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL);
-        Assert.assertEquals(retrievedItem.getLibraries().getBundles(), expectedBundles);
-        // Assert.assertEquals(retrievedItem.getVersion(), "0.9");
+        Collection<CatalogBundle> bundles = retrievedItem.getLibraries().getBundles();
+        Assert.assertEquals(bundles.size(), 1);
+        CatalogBundle bundle = Iterables.getOnlyElement(bundles);
+        Assert.assertEquals(bundle.getUrl(), OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL);
+        Assert.assertEquals(bundle.getVersion(), "0.1.0");
 
         EntitySpec<?> spec1 = (EntitySpec<?>) mgmt.getCatalog().createSpec(retrievedItem);
         assertNotNull(spec1);
@@ -231,8 +232,10 @@ public class CampYamlLiteTest {
         assertEquals(item.getId(), registeredTypeName);
 
         // and let's check we have libraries
-        List<String> libs = item.getLibraries().getBundles();
-        assertEquals(libs, MutableList.of(bundleUrl));
+        Collection<CatalogBundle> libs = item.getLibraries().getBundles();
+        assertEquals(libs.size(), 1);
+        CatalogBundle bundle = Iterables.getOnlyElement(libs);
+        assertEquals(bundle.getUrl(), bundleUrl);
 
         // now let's check other things on the item
         assertEquals(item.getDisplayName(), "My Catalog App");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/test/java/brooklyn/catalog/internal/CatalogDtoTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/catalog/internal/CatalogDtoTest.java b/core/src/test/java/brooklyn/catalog/internal/CatalogDtoTest.java
index 8e6d704..e232116 100644
--- a/core/src/test/java/brooklyn/catalog/internal/CatalogDtoTest.java
+++ b/core/src/test/java/brooklyn/catalog/internal/CatalogDtoTest.java
@@ -91,19 +91,24 @@ public class CatalogDtoTest {
                         "example for what can be expressed, and how", "contents-built-in-test"));
         root.setClasspathScanForEntities(CatalogScanningModes.NONE);
 
+        String bundleUrl = MavenRetriever.localUrl(BrooklynMavenArtifacts.artifact("", "brooklyn-core", "jar", "tests"));
         CatalogDo testEntitiesJavaCatalog = new CatalogDo(
                 managementContext,
                 CatalogDto.newNamedInstance("Test Entities from Java", null, "test-java"));
         testEntitiesJavaCatalog.setClasspathScanForEntities(CatalogScanningModes.NONE);
-        testEntitiesJavaCatalog.addToClasspath(MavenRetriever.localUrl(BrooklynMavenArtifacts.artifact("", "brooklyn-core", "jar", "tests")));
-        testEntitiesJavaCatalog.addEntry(CatalogItemBuilder.newTemplate(TestApplication.class.getCanonicalName(), "Test App from JAR").build());
-        testEntitiesJavaCatalog.addEntry(CatalogItemBuilder.newEntity(TestEntity.class.getCanonicalName(), "Test Entity from JAR").build());
+        testEntitiesJavaCatalog.addToClasspath(bundleUrl);
+        testEntitiesJavaCatalog.addEntry(CatalogItemBuilder.newTemplate(TestApplication.class.getCanonicalName(), "Test App from JAR")
+                .javaType(TestApplication.class.getCanonicalName())
+                .build());
+        testEntitiesJavaCatalog.addEntry(CatalogItemBuilder.newEntity(TestEntity.class.getCanonicalName(), "Test Entity from JAR")
+                .javaType(TestEntity.class.getCanonicalName())
+                .build());
         root.addCatalog(testEntitiesJavaCatalog.dto);
 
         CatalogDo testEntitiesJavaCatalogScanning = new CatalogDo(
                 managementContext,
                 CatalogDto.newNamedInstance("Test Entities from Java Scanning", null, "test-java-scan"));
-        testEntitiesJavaCatalogScanning.addToClasspath(MavenRetriever.localUrl(BrooklynMavenArtifacts.artifact("", "brooklyn-core", "jar", "tests")));
+        testEntitiesJavaCatalogScanning.addToClasspath(bundleUrl);
         testEntitiesJavaCatalogScanning.setClasspathScanForEntities(CatalogScanningModes.ANNOTATIONS);
         root.addCatalog(testEntitiesJavaCatalogScanning.dto);
 
@@ -114,7 +119,7 @@ public class CatalogDtoTest {
         osgiCatalog.setClasspathScanForEntities(CatalogScanningModes.NONE);
         CatalogEntityItemDto osgiEntity = CatalogItemBuilder.newEntity(TestEntity.class.getCanonicalName(), "Test Entity from OSGi").build();
         // NB: this is not actually an OSGi bundle, but it's okay as we don't instantiate the bundles ahead of time (currently)
-        osgiEntity.libraries.addBundle(MavenRetriever.localUrl(BrooklynMavenArtifacts.artifact("", "brooklyn-core", "jar", "tests")));
+        osgiEntity.libraries.addBundle(null, null, bundleUrl);
         testEntitiesJavaCatalog.addEntry(osgiEntity);
         root.addCatalog(osgiCatalog.dto);
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/test/java/brooklyn/catalog/internal/CatalogLoadTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/catalog/internal/CatalogLoadTest.java b/core/src/test/java/brooklyn/catalog/internal/CatalogLoadTest.java
index e9102c2..976223e 100644
--- a/core/src/test/java/brooklyn/catalog/internal/CatalogLoadTest.java
+++ b/core/src/test/java/brooklyn/catalog/internal/CatalogLoadTest.java
@@ -20,15 +20,16 @@ package brooklyn.catalog.internal;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
 
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import brooklyn.catalog.CatalogItem.CatalogBundle;
+import brooklyn.util.ResourceUtils;
+
 import com.google.common.base.Joiner;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-
-import brooklyn.util.ResourceUtils;
 
 public class CatalogLoadTest {
 
@@ -57,8 +58,18 @@ public class CatalogLoadTest {
         assertEquals(template.getJavaType(), "com.example.ExampleApp");
         assertEquals(template.getLibraries().getBundles().size(), 2,
                 "Template bundles=" + Joiner.on(", ").join(template.getLibraries().getBundles()));
-        assertEquals(Sets.newHashSet(template.getLibraries().getBundles()),
-                Sets.newHashSet("file://path/to/bundle.jar", "http://www.url.com/for/bundle.jar"));
+        
+        boolean foundBundle1 = false, foundBundle2 = false;
+        for (CatalogBundle bundle : template.getLibraries().getBundles()) {
+            if (bundle.getUrl().equals("file://path/to/bundle.jar")) {
+                foundBundle1 = true;
+            }
+            if (bundle.getUrl().equals("http://www.url.com/for/bundle.jar")) {
+                foundBundle2 = true;
+            }
+        }
+        assertTrue(foundBundle1);
+        assertTrue(foundBundle2);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/test/java/brooklyn/management/osgi/OsgiStandaloneTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/management/osgi/OsgiStandaloneTest.java b/core/src/test/java/brooklyn/management/osgi/OsgiStandaloneTest.java
index 94b9e9e..6d2cbc8 100644
--- a/core/src/test/java/brooklyn/management/osgi/OsgiStandaloneTest.java
+++ b/core/src/test/java/brooklyn/management/osgi/OsgiStandaloneTest.java
@@ -62,6 +62,8 @@ public class OsgiStandaloneTest {
 
     public static final String BROOKLYN_TEST_OSGI_ENTITIES_PATH = OsgiTestResources.BROOKLYN_TEST_OSGI_ENTITIES_PATH;
     public static final String BROOKLYN_TEST_OSGI_ENTITIES_URL = "classpath:"+BROOKLYN_TEST_OSGI_ENTITIES_PATH;
+    public static final String BROOKLYN_TEST_OSGI_ENTITIES_NAME = "org.apache.brooklyn.test.resources.osgi.brooklyn-test-osgi-entities";
+    public static final String BROOKLYN_TEST_OSGI_ENTITIES_VERSION = "0.1.0";
 
     protected Framework framework = null;
     private File storageTempDir;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/test/resources/brooklyn/camp/lite/test-app-service-blueprint.yaml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/brooklyn/camp/lite/test-app-service-blueprint.yaml b/core/src/test/resources/brooklyn/camp/lite/test-app-service-blueprint.yaml
index 8740ca5..846f299 100644
--- a/core/src/test/resources/brooklyn/camp/lite/test-app-service-blueprint.yaml
+++ b/core/src/test/resources/brooklyn/camp/lite/test-app-service-blueprint.yaml
@@ -34,5 +34,5 @@ brooklyn.catalog:
   version: 0.9
   libraries:
   - name: lib1
-    version: v1
+    version: 0.1.0
     url: classpath:/brooklyn/osgi/brooklyn-test-osgi-entities.jar
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/core/src/test/resources/brooklyn/catalog/internal/osgi-catalog.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/brooklyn/catalog/internal/osgi-catalog.xml b/core/src/test/resources/brooklyn/catalog/internal/osgi-catalog.xml
index 52a9764..651dfc5 100644
--- a/core/src/test/resources/brooklyn/catalog/internal/osgi-catalog.xml
+++ b/core/src/test/resources/brooklyn/catalog/internal/osgi-catalog.xml
@@ -24,8 +24,8 @@
     <template name="Entity name" version="9.1.3" type="com.example.ExampleApp">
         <description>An example application</description>
         <libraries>
-            <bundle>file://path/to/bundle.jar</bundle>
-            <bundle>http://www.url.com/for/bundle.jar</bundle>
+            <bundle><url>file://path/to/bundle.jar</url></bundle>
+            <bundle><url>http://www.url.com/for/bundle.jar</url></bundle>
         </libraries>
     </template>
 </catalog>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
index 61e3e4f..e5c233a 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlTest.java
@@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 
+import brooklyn.catalog.internal.CatalogUtils;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.BrooklynTaskTags;
 import brooklyn.entity.basic.Entities;
@@ -148,5 +149,8 @@ public abstract class AbstractYamlTest {
     private String join(String[] catalogYaml) {
         return Joiner.on("\n").join(catalogYaml);
     }
-    
+
+    protected String ver(String id) {
+        return id + CatalogUtils.VERSION_DELIMITER + TEST_VERSION;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ReferencedYamlTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ReferencedYamlTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ReferencedYamlTest.java
index 05bb8c0..048fa8f 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ReferencedYamlTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/ReferencedYamlTest.java
@@ -95,6 +95,7 @@ public class ReferencedYamlTest extends AbstractYamlTest {
         addCatalogItem(
             "brooklyn.catalog:",
             "  id: yaml.reference",
+            "  version: " + TEST_VERSION,
             "services:",
             "- type: classpath://yaml-ref-entity.yaml");
         
@@ -102,7 +103,7 @@ public class ReferencedYamlTest extends AbstractYamlTest {
         Entity app = createAndStartApplication(
             "services:",
             "- name: " + entityName,
-            "  type: yaml.reference");
+            "  type: " + ver("yaml.reference"));
         
         checkChildEntitySpec(app, entityName);
     }
@@ -112,6 +113,7 @@ public class ReferencedYamlTest extends AbstractYamlTest {
         addCatalogItem(
             "brooklyn.catalog:",
             "  id: yaml.basic",
+            "  version: " + TEST_VERSION,
             "services:",
             "- type: brooklyn.entity.basic.BasicEntity");
         
@@ -134,6 +136,7 @@ public class ReferencedYamlTest extends AbstractYamlTest {
         addCatalogItem(
             "brooklyn.catalog:",
             "  id: " + parentCatalogId,
+            "  version: " + TEST_VERSION,
             "  libraries:",
             "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
             "",
@@ -142,7 +145,7 @@ public class ReferencedYamlTest extends AbstractYamlTest {
 
         Entity app = createAndStartApplication(
             "services:",
-                "- type: " + parentCatalogId);
+                "- type: " + ver(parentCatalogId));
         
         Collection<Entity> children = app.getChildren();
         Assert.assertEquals(children.size(), 1);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
index 9281e61..d4b6474 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogOsgiVersionMoreEntityTest.java
@@ -42,7 +42,7 @@ public class CatalogOsgiVersionMoreEntityTest extends AbstractYamlTest {
     @Test
     public void testMoreEntityV1() throws Exception {
         addCatalogItem(getLocalResource("more-entity-v1-osgi-catalog.yaml"));
-        Entity app = createAndStartApplication("services: [ { type: more-entity } ]");
+        Entity app = createAndStartApplication("services: [ { type: 'more-entity:1.0' } ]");
         Entity moreEntity = Iterables.getOnlyElement(app.getChildren());
         
         Assert.assertEquals(moreEntity.getCatalogItemId(), "more-entity");
@@ -56,7 +56,7 @@ public class CatalogOsgiVersionMoreEntityTest extends AbstractYamlTest {
     public void testMoreEntityV1WithPolicy() throws Exception {
         addCatalogItem(getLocalResource("simple-policy-osgi-catalog.yaml"));
         addCatalogItem(getLocalResource("more-entity-v1-with-policy-osgi-catalog.yaml"));
-        Entity app = createAndStartApplication("services: [ { type: more-entity } ]");
+        Entity app = createAndStartApplication("services: [ { type: 'more-entity:1.0' } ]");
         Entity moreEntity = Iterables.getOnlyElement(app.getChildren());
         
         Assert.assertEquals(moreEntity.getCatalogItemId(), "more-entity");
@@ -70,7 +70,7 @@ public class CatalogOsgiVersionMoreEntityTest extends AbstractYamlTest {
     @Test
     public void testMoreEntityV2() throws Exception {
         addCatalogItem(getLocalResource("more-entity-v2-osgi-catalog.yaml"));
-        Entity app = createAndStartApplication("services: [ { type: more-entity } ]");
+        Entity app = createAndStartApplication("services: [ { type: 'more-entity:1.0' } ]");
         Entity moreEntity = Iterables.getOnlyElement(app.getChildren());
         
         Assert.assertEquals(moreEntity.getCatalogItemId(), "more-entity");
@@ -88,7 +88,7 @@ public class CatalogOsgiVersionMoreEntityTest extends AbstractYamlTest {
     public void testMoreEntityV2ThenV1GivesV1() throws Exception {
         addCatalogItem(getLocalResource("more-entity-v2-osgi-catalog.yaml"));
         addCatalogItem(getLocalResource("more-entity-v1-osgi-catalog.yaml"));
-        Entity app = createAndStartApplication("services: [ { type: more-entity } ]");
+        Entity app = createAndStartApplication("services: [ { type: 'more-entity:1.0' } ]");
         Entity moreEntity = Iterables.getOnlyElement(app.getChildren());
         
         OsgiVersionMoreEntityTest.assertV1EffectorCall(moreEntity);
@@ -102,7 +102,7 @@ public class CatalogOsgiVersionMoreEntityTest extends AbstractYamlTest {
     public void testMoreEntityV1ThenV2GivesV2() throws Exception {
         addCatalogItem(getLocalResource("more-entity-v1-osgi-catalog.yaml"));
         addCatalogItem(getLocalResource("more-entity-v2-osgi-catalog.yaml"));
-        Entity app = createAndStartApplication("services: [ { type: more-entity } ]");
+        Entity app = createAndStartApplication("services: [ { type: 'more-entity:1.0' } ]");
         Entity moreEntity = Iterables.getOnlyElement(app.getChildren());
         
         OsgiVersionMoreEntityTest.assertV2EffectorCall(moreEntity);
@@ -113,8 +113,8 @@ public class CatalogOsgiVersionMoreEntityTest extends AbstractYamlTest {
     public void testMoreEntityBothV1AndV2() throws Exception {
         addCatalogItem(getLocalResource("more-entity-v1-called-v1-osgi-catalog.yaml"));
         addCatalogItem(getLocalResource("more-entity-v2-osgi-catalog.yaml"));
-        Entity v1 = createAndStartApplication("services: [ { type: more-entity-v1 } ]");
-        Entity v2 = createAndStartApplication("services: [ { type: more-entity } ]");
+        Entity v1 = createAndStartApplication("services: [ { type: 'more-entity-v1:1.0' } ]");
+        Entity v2 = createAndStartApplication("services: [ { type: 'more-entity:1.0' } ]");
         
         Entity moreEntityV1 = Iterables.getOnlyElement(v1.getChildren());
         Entity moreEntityV2 = Iterables.getOnlyElement(v2.getChildren());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
index dfbc96b..f5ad95c 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlEntityTest.java
@@ -25,10 +25,10 @@ import io.brooklyn.camp.brooklyn.AbstractYamlTest;
 
 import java.util.Collection;
 
+import org.junit.Assert;
 import org.testng.annotations.Test;
 
 import brooklyn.catalog.CatalogItem;
-import brooklyn.catalog.internal.CatalogUtils;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.BasicEntity;
 import brooklyn.management.osgi.OsgiStandaloneTest;
@@ -79,7 +79,7 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             String yaml = "name: simple-app-yaml\n" +
                           "location: localhost\n" +
                           "services: \n" +
-                          "  - serviceType: " + registeredTypeName;
+                          "  - serviceType: " + ver(registeredTypeName);
             try {
                 createAndStartApplication(yaml);
             } catch (UnsupportedOperationException e) {
@@ -115,7 +115,7 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
         String referencedRegisteredTypeName = "my.catalog.app.id.child.referenced";
         String referrerRegisteredTypeName = "my.catalog.app.id.child.referring";
         addCatalogOSGiEntity(referencedRegisteredTypeName, SIMPLE_ENTITY_TYPE);
-        addCatalogChildOSGiEntity(referrerRegisteredTypeName, referencedRegisteredTypeName);
+        addCatalogChildOSGiEntity(referrerRegisteredTypeName, ver(referencedRegisteredTypeName));
 
         Entity app = createAndStartApplication(
             "name: simple-app-yaml",
@@ -123,7 +123,7 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             "services:",
             "- serviceType: "+BasicEntity.class.getName(),
             "  brooklyn.children:",
-            "  - type: " + referrerRegisteredTypeName);
+            "  - type: " + ver(referrerRegisteredTypeName));
 
         Collection<Entity> children = app.getChildren();
         assertEquals(children.size(), 1);
@@ -160,19 +160,159 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
     public void testLaunchApplicationChildLoopCatalogIdFails() throws Exception {
         String referrerRegisteredTypeName = "my.catalog.app.id.child.referring";
         try {
-            addCatalogChildOSGiEntity(referrerRegisteredTypeName, referrerRegisteredTypeName);
+            addCatalogChildOSGiEntity(referrerRegisteredTypeName, ver(referrerRegisteredTypeName));
             fail("Expected to throw IllegalStateException");
         } catch (IllegalStateException e) {
             assertTrue(e.getMessage().contains("Could not find "+referrerRegisteredTypeName));
         }
     }
 
+    @Test
+    public void testReferenceInstalledBundleByName() {
+        String firstItemId = "my.catalog.app.id.register_bundle";
+        String secondItemId = "my.catalog.app.id.reference_bundle";
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: " + firstItemId,
+            "  version: " + TEST_VERSION,
+            "  libraries:",
+            "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
+            "",
+            "services:",
+            "- type: " + SIMPLE_ENTITY_TYPE);
+        deleteCatalogEntity(firstItemId);
+
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: " + secondItemId,
+            "  version: " + TEST_VERSION,
+            "  libraries:",
+            "  - name: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_NAME,
+            "    version: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_VERSION,
+            "",
+            "services:",
+            "- type: " + SIMPLE_ENTITY_TYPE);
+
+        deleteCatalogEntity(secondItemId);
+    }
+
+    @Test
+    public void testReferenceNonInstalledBundledByNameFails() {
+        String nonExistentId = "none-existent-id";
+        String nonExistentVersion = "9.9.9";
+        try {
+            addCatalogItem(
+                "brooklyn.catalog:",
+                "  id: my.catalog.app.id.non_existing.ref",
+                "  version: " + TEST_VERSION,
+                "  libraries:",
+                "  - name: " + nonExistentId,
+                "    version: " + nonExistentVersion,
+                "",
+                "services:",
+                "- type: " + SIMPLE_ENTITY_TYPE);
+            fail();
+        } catch (IllegalStateException e) {
+            Assert.assertEquals(e.getMessage(), "Bundle CatalogBundleDto{name=" + nonExistentId + ", version=" + nonExistentVersion + ", url=null} not already registered by name:version, but URL is empty.");
+        }
+    }
+
+    @Test
+    public void testPartialBundleReferenceFails() {
+        try {
+            addCatalogItem(
+                "brooklyn.catalog:",
+                "  id: my.catalog.app.id.non_existing.ref",
+                "  version: " + TEST_VERSION,
+                "  libraries:",
+                "  - name: io.brooklyn.brooklyn-test-osgi-entities",
+                "",
+                "services:",
+                "- type: " + SIMPLE_ENTITY_TYPE);
+            fail();
+        } catch (NullPointerException e) {
+            Assert.assertEquals(e.getMessage(), "version");
+        }
+        try {
+            addCatalogItem(
+                "brooklyn.catalog:",
+                "  id: my.catalog.app.id.non_existing.ref",
+                "  version: " + TEST_VERSION,
+                "  libraries:",
+                "  - version: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_VERSION,
+                "",
+                "services:",
+                "- type: " + SIMPLE_ENTITY_TYPE);
+            fail();
+        } catch (NullPointerException e) {
+            Assert.assertEquals(e.getMessage(), "name");
+        }
+    }
+
+    @Test
+    public void testFullBundleReference() {
+        String itemId = "my.catalog.app.id.full_ref";
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: " + itemId,
+            "  version: " + TEST_VERSION,
+            "  libraries:",
+            "  - name: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_NAME,
+            "    version: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_VERSION,
+            "    url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
+            "",
+            "services:",
+            "- type: " + SIMPLE_ENTITY_TYPE);
+        deleteCatalogEntity(itemId);
+    }
+
+    /**
+     * Test that the name:version contained in the OSGi bundle will
+     * override the values supplied in the YAML.
+     */
+    @Test
+    public void testFullBundleReferenceUrlMetaOverridesLocalNameVersion() {
+        String firstItemId = "my.catalog.app.id.register_bundle";
+        String secondItemId = "my.catalog.app.id.reference_bundle";
+        String nonExistentId = "non_existent_id";
+        String nonExistentVersion = "9.9.9";
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: " + firstItemId,
+            "  version: " + TEST_VERSION,
+            "  libraries:",
+            "  - name: " + nonExistentId,
+            "    version: " + nonExistentVersion,
+            "    url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
+            "",
+            "services:",
+            "- type: " + SIMPLE_ENTITY_TYPE);
+        deleteCatalogEntity(firstItemId);
+
+        try {
+            addCatalogItem(
+                "brooklyn.catalog:",
+                "  id: " + secondItemId,
+                "  version: " + TEST_VERSION,
+                "  libraries:",
+                "  - name: " + nonExistentId,
+                "    version: " + nonExistentVersion,
+                "",
+                "services:",
+                "- type: " + SIMPLE_ENTITY_TYPE);
+            fail();
+        } catch (IllegalStateException e) {
+            assertEquals(e.getMessage(), "Bundle CatalogBundleDto{name=" + nonExistentId + ", version=" + nonExistentVersion + ", url=null} " +
+                    "not already registered by name:version, but URL is empty.");
+        }
+    }
+
     private void registerAndLaunchAndAssertSimpleEntity(String registeredTypeName, String serviceType) throws Exception {
         addCatalogOSGiEntity(registeredTypeName, serviceType);
         String yaml = "name: simple-app-yaml\n" +
                       "location: localhost\n" +
                       "services: \n" +
-                      "  - serviceType: "+registeredTypeName;
+                      "  - serviceType: "+ver(registeredTypeName);
         Entity app = createAndStartApplication(yaml);
 
         Entity simpleEntity = Iterables.getOnlyElement(app.getChildren());
@@ -207,7 +347,7 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             "  name: My Catalog App",
             "  description: My description",
             "  icon_url: classpath://path/to/myicon.jpg",
-            "  version: 0.1.2",
+            "  version: " + TEST_VERSION,
             "  libraries:",
             "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
             "",
@@ -217,7 +357,4 @@ public class CatalogYamlEntityTest extends AbstractYamlTest {
             "  - type: " + serviceType);
     }
 
-    private String ver(String id) {
-        return id + CatalogUtils.VERSION_DELIMITER + TEST_VERSION;
-    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
index e704ea1..9ba5bee 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlPolicyTest.java
@@ -60,7 +60,7 @@ public class CatalogYamlPolicyTest extends AbstractYamlTest {
             "services: ",
             "  - type: brooklyn.entity.basic.BasicEntity\n" +
             "    brooklyn.policies:\n" +
-            "    - type: " + registeredTypeName,
+            "    - type: " + ver(registeredTypeName),
             "      brooklyn.config:",
             "        config2: config2 override",
             "        config3: config3");
@@ -87,19 +87,19 @@ public class CatalogYamlPolicyTest extends AbstractYamlTest {
             "  name: My Catalog App",
             "  description: My description",
             "  icon_url: classpath://path/to/myicon.jpg",
-            "  version: 0.1.2",
+            "  version: " + TEST_VERSION,
             "  libraries:",
             "  - url: " + OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL,
             "",
             "services:",
             "- type: " + SIMPLE_ENTITY_TYPE,
             "  brooklyn.policies:",
-            "  - type: " + referencedRegisteredTypeName);
+            "  - type: " + ver(referencedRegisteredTypeName));
 
         String yaml = "name: simple-app-yaml\n" +
                       "location: localhost\n" +
                       "services: \n" +
-                      "  - serviceType: "+referrerRegisteredTypeName;
+                      "  - serviceType: "+ ver(referrerRegisteredTypeName);
 
         Entity app = createAndStartApplication(yaml);
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/usage/camp/src/test/resources/io/brooklyn/camp/brooklyn/catalog/more-entity-v1-with-policy-osgi-catalog.yaml
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/io/brooklyn/camp/brooklyn/catalog/more-entity-v1-with-policy-osgi-catalog.yaml b/usage/camp/src/test/resources/io/brooklyn/camp/brooklyn/catalog/more-entity-v1-with-policy-osgi-catalog.yaml
index 79657d8..3c03bd0 100644
--- a/usage/camp/src/test/resources/io/brooklyn/camp/brooklyn/catalog/more-entity-v1-with-policy-osgi-catalog.yaml
+++ b/usage/camp/src/test/resources/io/brooklyn/camp/brooklyn/catalog/more-entity-v1-with-policy-osgi-catalog.yaml
@@ -19,7 +19,7 @@
 services:
 - type: brooklyn.osgi.tests.more.MoreEntity
   brooklyn.policies:
-  - type: simple-policy
+  - type: simple-policy:1.0
 
 brooklyn.catalog:
   id: more-entity

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/usage/camp/src/test/resources/io/brooklyn/camp/brooklyn/catalog/more-entity-v2-osgi-catalog.yaml
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/io/brooklyn/camp/brooklyn/catalog/more-entity-v2-osgi-catalog.yaml b/usage/camp/src/test/resources/io/brooklyn/camp/brooklyn/catalog/more-entity-v2-osgi-catalog.yaml
index 7b29439..74323fa 100644
--- a/usage/camp/src/test/resources/io/brooklyn/camp/brooklyn/catalog/more-entity-v2-osgi-catalog.yaml
+++ b/usage/camp/src/test/resources/io/brooklyn/camp/brooklyn/catalog/more-entity-v2-osgi-catalog.yaml
@@ -21,7 +21,7 @@ services:
 
 brooklyn.catalog:
   id: more-entity
-  version: 2.0
+  version: 1.0
   # see OsgiTestResources
   libraries:
   - classpath:/brooklyn/osgi/brooklyn-test-osgi-more-entities_0.2.0.jar

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/usage/camp/src/test/resources/yaml-ref-app.yaml
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/yaml-ref-app.yaml b/usage/camp/src/test/resources/yaml-ref-app.yaml
index a26a4ab..8e1f740 100644
--- a/usage/camp/src/test/resources/yaml-ref-app.yaml
+++ b/usage/camp/src/test/resources/yaml-ref-app.yaml
@@ -18,4 +18,4 @@
 name: Basic app
 services:
 - name: service
-  type: brooklyn.entity.basic.BasicApplication
\ No newline at end of file
+  type: brooklyn.entity.basic.BasicApplication

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/usage/camp/src/test/resources/yaml-ref-catalog.yaml
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/yaml-ref-catalog.yaml b/usage/camp/src/test/resources/yaml-ref-catalog.yaml
index 220deb9..545fd1a 100644
--- a/usage/camp/src/test/resources/yaml-ref-catalog.yaml
+++ b/usage/camp/src/test/resources/yaml-ref-catalog.yaml
@@ -18,4 +18,4 @@
 name: Basic app
 services:
 - name: service
-  type: yaml.basic
\ No newline at end of file
+  type: yaml.basic:0.1.2

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/usage/camp/src/test/resources/yaml-ref-entity.yaml
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/resources/yaml-ref-entity.yaml b/usage/camp/src/test/resources/yaml-ref-entity.yaml
index 8d27427..69d311f 100644
--- a/usage/camp/src/test/resources/yaml-ref-entity.yaml
+++ b/usage/camp/src/test/resources/yaml-ref-entity.yaml
@@ -18,4 +18,4 @@
 name: Basic entity
 services:
 - name: service
-  type: brooklyn.entity.basic.BasicEntity
\ No newline at end of file
+  type: brooklyn.entity.basic.BasicEntity

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/268aaf6b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java
----------------------------------------------------------------------
diff --git a/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java
index 771f8ff..8e33ebb 100644
--- a/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java
+++ b/usage/rest-server/src/test/java/brooklyn/rest/resources/CatalogResourceTest.java
@@ -25,6 +25,7 @@ import java.awt.Image;
 import java.awt.Toolkit;
 import java.io.IOException;
 import java.net.URI;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
@@ -39,14 +40,16 @@ import org.testng.annotations.Test;
 import org.testng.reporters.Files;
 
 import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogItem.CatalogBundle;
+import brooklyn.catalog.internal.CatalogUtils;
 import brooklyn.management.osgi.OsgiStandaloneTest;
 import brooklyn.policy.autoscaling.AutoScalerPolicy;
 import brooklyn.rest.domain.CatalogEntitySummary;
 import brooklyn.rest.domain.CatalogItemSummary;
 import brooklyn.rest.domain.CatalogPolicySummary;
 import brooklyn.rest.testing.BrooklynRestResourceTest;
-import brooklyn.util.collections.MutableList;
 
+import com.google.common.collect.Iterables;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.GenericType;
 
@@ -99,14 +102,16 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
     Assert.assertNotNull(entityItem.getPlanYaml());
     Assert.assertTrue(entityItem.getPlanYaml().contains("brooklyn.test.entity.TestEntity"));
     
+    assertEquals(entityItem.getId(), ver(registeredTypeName));
     assertEquals(entityItem.getSymbolicName(), registeredTypeName);
     assertEquals(entityItem.getVersion(), TEST_VERSION);
     
     // and internally let's check we have libraries
     CatalogItem<?, ?> item = getManagementContext().getCatalog().getCatalogItem(registeredTypeName, TEST_VERSION);
     Assert.assertNotNull(item);
-    List<String> libs = item.getLibraries().getBundles();
-    assertEquals(libs, MutableList.of(bundleUrl));
+    Collection<CatalogBundle> libs = item.getLibraries().getBundles();
+    assertEquals(libs.size(), 1);
+    assertEquals(Iterables.getOnlyElement(libs).getUrl(), bundleUrl);
 
     // now let's check other things on the item
     assertEquals(entityItem.getName(), "My Catalog App");
@@ -129,7 +134,7 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
         "  id: " + registeredTypeName + "\n"+
         "  name: My Catalog App\n"+
         "  description: My description\n"+
-        "  version: 0.1.2\n"+
+        "  version: " + TEST_VERSION + "\n" +
         "  libraries:\n"+
         "  - url: " + bundleUrl + "\n"+
         "\n"+
@@ -142,6 +147,7 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
     assertEquals(entityItem.getRegisteredType(), registeredTypeName);
     Assert.assertNotNull(entityItem.getPlanYaml());
     Assert.assertTrue(entityItem.getPlanYaml().contains(policyType));
+    assertEquals(entityItem.getId(), ver(registeredTypeName));
     assertEquals(entityItem.getSymbolicName(), registeredTypeName);
     assertEquals(entityItem.getVersion(), TEST_VERSION);
   }
@@ -260,4 +266,8 @@ public class CatalogResourceTest extends BrooklynRestResourceTest {
             .get(ClientResponse.class);
     assertEquals(getPostDeleteResponse.getStatus(), Response.Status.NOT_FOUND.getStatusCode());
   }
+  
+  private static String ver(String id) {
+      return id + CatalogUtils.VERSION_DELIMITER + TEST_VERSION;
+  }
 }