BROOKLYN-136: Persist dynamically added locs in catalog

- Support CatalogItem of type location
- Location items added to catalog automatically added to
  LocationRegistry
- LocationResource.create adds new location type to Catalog


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

Branch: refs/heads/master
Commit: 09a64755e2c16c25f076af6a32f93ee0ff8c2599
Parents: d387887
Author: Aled Sage <[email protected]>
Authored: Tue Mar 17 14:46:57 2015 +0000
Committer: Aled Sage <[email protected]>
Committed: Wed Mar 18 11:02:52 2015 +0000

----------------------------------------------------------------------
 .../main/java/brooklyn/catalog/CatalogItem.java |   5 +-
 .../java/brooklyn/location/LocationSpec.java    |  27 ++++
 .../brooklyn/catalog/CatalogPredicates.java     |   4 +
 .../catalog/internal/BasicBrooklynCatalog.java  |  92 +++++++++--
 .../catalog/internal/CatalogClasspathDo.java    |   8 +
 .../catalog/internal/CatalogItemBuilder.java    |   6 +
 .../internal/CatalogLocationItemDto.java        |  43 ++++++
 .../catalog/internal/CatalogXmlSerializer.java  |   2 +
 .../location/basic/BasicLocationRegistry.java   |  87 ++++++++++-
 .../location/basic/CatalogLocationResolver.java |  79 ++++++++++
 .../services/brooklyn.location.LocationResolver |   1 +
 .../brooklyn/osgi/tests/SimpleLocation.java     |  35 +++++
 .../entity/rebind/RebindCatalogItemTest.java    |  36 ++++-
 .../entity/rebind/RebindTestFixture.java        |   9 +-
 .../osgi/brooklyn-test-osgi-entities.jar        | Bin 12419 -> 13060 bytes
 .../camp/brooklyn/AbstractYamlTest.java         |  20 +++
 .../brooklyn/EmptySoftwareProcessYamlTest.java  |  39 ++---
 .../catalog/CatalogYamlLocationTest.java        | 152 +++++++++++++++++++
 .../rest/resources/LocationResource.java        |  83 +++++-----
 .../rest/resources/LocationResourceTest.java    |  20 ++-
 .../src/main/java/brooklyn/util/yaml/Yamls.java |   9 +-
 21 files changed, 673 insertions(+), 84 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/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 4dd63fa..8abaddc 100644
--- a/api/src/main/java/brooklyn/catalog/CatalogItem.java
+++ b/api/src/main/java/brooklyn/catalog/CatalogItem.java
@@ -33,7 +33,10 @@ import com.google.common.annotations.Beta;
 public interface CatalogItem<T,SpecT> extends BrooklynObject, Rebindable {
     
     public static enum CatalogItemType {
-        TEMPLATE, ENTITY, POLICY
+        TEMPLATE, 
+        ENTITY, 
+        POLICY,
+        LOCATION;
     }
     
     public static interface CatalogBundle {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/api/src/main/java/brooklyn/location/LocationSpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/location/LocationSpec.java 
b/api/src/main/java/brooklyn/location/LocationSpec.java
index 6cd26ab..7e3c4f9 100644
--- a/api/src/main/java/brooklyn/location/LocationSpec.java
+++ b/api/src/main/java/brooklyn/location/LocationSpec.java
@@ -73,6 +73,25 @@ public class LocationSpec<T extends Location> extends 
AbstractBrooklynObjectSpec
         return LocationSpec.create(type).configure(config);
     }
     
+    /**
+     * Copies entity spec so its configuration can be overridden without 
modifying the 
+     * original entity spec.
+     */
+    public static <T extends Location> LocationSpec<T> create(LocationSpec<T> 
spec) {
+        // FIXME Why can I not use LocationSpec<T>?
+        LocationSpec<?> result = create(spec.getType())
+                .displayName(spec.getDisplayName())
+                .tags(spec.getTags())
+                .configure(spec.getConfig())
+                .configure(spec.getFlags())
+                .catalogItemId(spec.getCatalogItemId())
+                .extensions(spec.getExtensions());
+        
+        if (spec.getParent() != null) result.parent(spec.getParent());
+        
+        return (LocationSpec<T>) result;
+    }
+
     private String id;
     private Location parent;
     private final Map<String, Object> flags = Maps.newLinkedHashMap();
@@ -157,6 +176,14 @@ public class LocationSpec<T extends Location> extends 
AbstractBrooklynObjectSpec
         return this;
     }
     
+    @SuppressWarnings("unchecked")
+    public <E> LocationSpec<T> extensions(Map<Class<?>, ?> extensions) {
+        for (Map.Entry<Class<?>, ?> entry : extensions.entrySet()) {
+            extension((Class)entry.getKey(), entry.getValue());
+        }
+        return this;
+    }
+    
     /**
      * @return The id of the location to be created, or null if brooklyn can 
auto-generate an id
      * 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/catalog/CatalogPredicates.java 
b/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
index 82ab689..4331a1a 100644
--- a/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
+++ b/core/src/main/java/brooklyn/catalog/CatalogPredicates.java
@@ -24,6 +24,8 @@ import brooklyn.catalog.CatalogItem.CatalogItemType;
 import brooklyn.entity.Application;
 import brooklyn.entity.Entity;
 import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.entitlement.Entitlements;
 import brooklyn.policy.Policy;
@@ -58,6 +60,8 @@ public class CatalogPredicates {
             
CatalogPredicates.<Entity,EntitySpec<?>>isCatalogItemType(CatalogItemType.ENTITY);
     public static final Predicate<CatalogItem<Policy,PolicySpec<?>>> IS_POLICY 
= 
             
CatalogPredicates.<Policy,PolicySpec<?>>isCatalogItemType(CatalogItemType.POLICY);
+    public static final Predicate<CatalogItem<Location,LocationSpec<?>>> 
IS_LOCATION = 
+            
CatalogPredicates.<Location,LocationSpec<?>>isCatalogItemType(CatalogItemType.LOCATION);
     
     public static final Function<CatalogItem<?,?>,String> 
ID_OF_ITEM_TRANSFORMER = new Function<CatalogItem<?,?>, String>() {
         @Override @Nullable

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java 
b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
index 5c5e05f..96760aa 100644
--- a/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
+++ b/core/src/main/java/brooklyn/catalog/internal/BasicBrooklynCatalog.java
@@ -31,6 +31,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Set;
 
 import javax.annotation.Nullable;
 
@@ -38,13 +39,18 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import brooklyn.basic.AbstractBrooklynObjectSpec;
+import brooklyn.basic.BrooklynObjectInternal.ConfigurationSupportInternal;
 import brooklyn.camp.brooklyn.api.AssemblyTemplateSpecInstantiator;
 import brooklyn.catalog.BrooklynCatalog;
 import brooklyn.catalog.CatalogItem;
 import brooklyn.catalog.CatalogItem.CatalogBundle;
+import brooklyn.catalog.CatalogItem.CatalogItemType;
 import brooklyn.catalog.CatalogPredicates;
 import brooklyn.config.BrooklynServerConfig;
 import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
+import brooklyn.location.basic.BasicLocationRegistry;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.classloading.BrooklynClassLoadingContext;
 import brooklyn.management.internal.EntityManagementUtils;
@@ -75,6 +81,7 @@ import com.google.common.collect.Iterables;
 
 public class BasicBrooklynCatalog implements BrooklynCatalog {
     private static final String POLICIES_KEY = "brooklyn.policies";
+    private static final String LOCATIONS_KEY = "brooklyn.locations";
     public static final String NO_VERSION = "0.0.0.SNAPSHOT";
 
     private static final Logger log = 
LoggerFactory.getLogger(BasicBrooklynCatalog.class);
@@ -253,6 +260,11 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
         if (log.isTraceEnabled()) {
             log.trace("Scheduling item for persistence removal: {}", 
itemDto.getId());
         }
+        if (itemDto.getCatalogItemType() == CatalogItemType.LOCATION) {
+            @SuppressWarnings("unchecked")
+            CatalogItem<Location,LocationSpec<?>> locationItem = 
(CatalogItem<Location, LocationSpec<?>>) itemDto;
+            
((BasicLocationRegistry)mgmt.getLocationRegistry()).removeDefinedLocation(locationItem);
+        }
         mgmt.getRebindManager().getChangeListener().onUnmanaged(itemDto);
 
     }
@@ -318,7 +330,10 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
                 case POLICY:
                     spec = createPolicySpec(plan, loader);
                     break;
-                default: throw new RuntimeException("Only entity & policy 
catalog items are supported. Unsupported catalog item type " + 
item.getCatalogItemType());
+                case LOCATION:
+                    spec = createLocationSpec(plan, loader);
+                    break;
+                default: throw new RuntimeException("Only entity, policy & 
location catalog items are supported. Unsupported catalog item type " + 
item.getCatalogItemType());
             }
             ((AbstractBrooklynObjectSpec<?, 
?>)spec).catalogItemId(item.getId());
             return spec;
@@ -366,7 +381,6 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
         }
     }
 
-
     @SuppressWarnings("unchecked")
     private <T, SpecT> SpecT createPolicySpec(DeploymentPlan plan, 
BrooklynClassLoadingContext loader) {
         //Would ideally re-use 
io.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityDecorationResolver.PolicySpecResolver
@@ -378,23 +392,70 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
 
         Object policy = Iterables.getOnlyElement((Iterable<?>)policies);
 
-        Map<String, Object> policyConfig;
+        Map<String, Object> config;
         if (policy instanceof String) {
-            policyConfig = ImmutableMap.<String, Object>of("type", policy);
+            config = ImmutableMap.<String, Object>of("type", policy);
         } else if (policy instanceof Map) {
-            policyConfig = (Map<String, Object>) policy;
+            config = (Map<String, Object>) policy;
         } else {
-            throw new IllegalStateException("Policy exepcted to be string or 
map. Unsupported object type " + policy.getClass().getName() + " (" + 
policy.toString() + ")");
+            throw new IllegalStateException("Policy expected to be string or 
map. Unsupported object type " + policy.getClass().getName() + " (" + 
policy.toString() + ")");
         }
 
-        String policyType = (String) 
checkNotNull(Yamls.getMultinameAttribute(policyConfig, "policy_type", 
"policyType", "type"), "policy type");
-        Map<String, Object> brooklynConfig = (Map<String, Object>) 
policyConfig.get("brooklyn.config");
-        PolicySpec<? extends Policy> spec = 
PolicySpec.create(loader.loadClass(policyType, Policy.class));
+        String type = (String) 
checkNotNull(Yamls.getMultinameAttribute(config, "policy_type", "policyType", 
"type"), "policy type");
+        Map<String, Object> brooklynConfig = (Map<String, Object>) 
config.get("brooklyn.config");
+        PolicySpec<? extends Policy> spec = 
PolicySpec.create(loader.loadClass(type, Policy.class));
         if (brooklynConfig != null) {
             spec.configure(brooklynConfig);
         }
         return (SpecT) spec;
     }
+    
+    @SuppressWarnings("unchecked")
+    private <T, SpecT> SpecT createLocationSpec(DeploymentPlan plan, 
BrooklynClassLoadingContext loader) {
+        // See #createPolicySpec; this impl is modeled on that.
+        // spec.catalogItemId is set by caller
+        Object locations = 
checkNotNull(plan.getCustomAttributes().get(LOCATIONS_KEY), "location config");
+        if (!(locations instanceof Iterable<?>)) {
+            throw new IllegalStateException("The value of " + LOCATIONS_KEY + 
" must be an Iterable.");
+        }
+
+        Object location = Iterables.getOnlyElement((Iterable<?>)locations);
+
+        Map<String, Object> config;
+        if (location instanceof String) {
+            config = ImmutableMap.<String, Object>of("type", location);
+        } else if (location instanceof Map) {
+            config = (Map<String, Object>) location;
+        } else {
+            throw new IllegalStateException("Location expected to be string or 
map. Unsupported object type " + location.getClass().getName() + " (" + 
location.toString() + ")");
+        }
+
+        String type = (String) 
checkNotNull(Yamls.getMultinameAttribute(config, "location_type", 
"locationType", "type"), "location type");
+        Map<String, Object> brooklynConfig = (Map<String, Object>) 
config.get("brooklyn.config");
+        Maybe<Class<? extends Location>> javaClass = loader.tryLoadClass(type, 
Location.class);
+        if (javaClass.isPresent()) {
+            LocationSpec<?> spec = LocationSpec.create(javaClass.get());
+            if (brooklynConfig != null) {
+                spec.configure(brooklynConfig);
+            }
+            return (SpecT) spec;
+        } else {
+            Maybe<Location> loc = mgmt.getLocationRegistry().resolve(type, 
false, brooklynConfig);
+            if (loc.isPresent()) {
+                // TODO extensions?
+                Map<String, Object> locConfig = 
((ConfigurationSupportInternal)loc.get().config()).getBag().getAllConfig();
+                Class<? extends Location> locType = loc.get().getClass();
+                Set<Object> locTags = loc.get().tags().getTags();
+                String locDisplayName = loc.get().getDisplayName();
+                return (SpecT) LocationSpec.create(locType)
+                        .configure(locConfig)
+                        .displayName(locDisplayName)
+                        .tags(locTags);
+            } else {
+                throw new IllegalStateException("No class or resolver found 
for location type "+type);
+            }
+        } 
+    }
 
     @SuppressWarnings("unchecked")
     @Override
@@ -557,6 +618,8 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
     private AbstractBrooklynObjectSpec<?,?> createSpec(String symbolicName, 
DeploymentPlan plan, BrooklynClassLoadingContext loader) {
         if (isPolicyPlan(plan)) {
             return createPolicySpec(plan, loader);
+        } else if (isLocationPlan(plan)) {
+            return createLocationSpec(plan, loader);
         } else {
             return createEntitySpec(symbolicName, plan, loader);
         }
@@ -571,6 +634,8 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
             }
         } else if (spec instanceof PolicySpec) {
             return CatalogItemBuilder.newPolicy(itemId, version);
+        } else if (spec instanceof LocationSpec) {
+            return CatalogItemBuilder.newLocation(itemId, version);
         } else {
             throw new IllegalStateException("Unknown spec type " + 
spec.getClass().getName() + " (" + spec + ")");
         }
@@ -584,6 +649,10 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
         return plan.getCustomAttributes().containsKey(POLICIES_KEY);
     }
 
+    private boolean isLocationPlan(DeploymentPlan plan) {
+        return plan.getCustomAttributes().containsKey(LOCATIONS_KEY);
+    }
+
     private DeploymentPlan makePlanFromYaml(String yaml) {
         CampPlatform camp = BrooklynServerConfig.getCampPlatform(mgmt).get();
         return 
camp.pdp().parseDeploymentPlan(Streams.newReaderWithContents(yaml));
@@ -611,6 +680,11 @@ public class BasicBrooklynCatalog implements 
BrooklynCatalog {
         if (log.isTraceEnabled()) {
             log.trace("Scheduling item for persistence addition: {}", 
itemDto.getId());
         }
+        if (itemDto.getCatalogItemType() == CatalogItemType.LOCATION) {
+            @SuppressWarnings("unchecked")
+            CatalogItem<Location,LocationSpec<?>> locationItem = 
(CatalogItem<Location, LocationSpec<?>>) itemDto;
+            
((BasicLocationRegistry)mgmt.getLocationRegistry()).updateDefinedLocation(locationItem);
+        }
         mgmt.getRebindManager().getChangeListener().onManaged(itemDto);
 
         return itemDto;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java 
b/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java
index 6ec073f..095ebfd 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogClasspathDo.java
@@ -36,6 +36,7 @@ import brooklyn.entity.Application;
 import brooklyn.entity.Entity;
 import brooklyn.entity.basic.ApplicationBuilder;
 import brooklyn.entity.proxying.ImplementedBy;
+import brooklyn.location.Location;
 import brooklyn.management.internal.ManagementContextInternal;
 import brooklyn.policy.Policy;
 import brooklyn.util.exceptions.Exceptions;
@@ -193,6 +194,12 @@ public class CatalogClasspathDo {
                     addCatalogEntry(new CatalogPolicyItemDto(), c);
                     count++;
                 }
+                
+                Iterable<Class<? extends Location>> locations = 
this.excludeInvalidClasses(scanner.getSubTypesOf(Location.class));
+                for (Class<?> c: locations) {
+                    addCatalogEntry(new CatalogLocationItemDto(), c);
+                    count++;
+                }
             } else {
                 throw new IllegalStateException("Unsupported catalog scan mode 
"+scanMode+" for "+this);
             }
@@ -233,6 +240,7 @@ public class CatalogClasspathDo {
         if (ApplicationBuilder.class.isAssignableFrom(c)) return 
addCatalogEntry(new CatalogTemplateItemDto(), c);
         if (Entity.class.isAssignableFrom(c)) return addCatalogEntry(new 
CatalogEntityItemDto(), c);
         if (Policy.class.isAssignableFrom(c)) return addCatalogEntry(new 
CatalogPolicyItemDto(), c);
+        if (Location.class.isAssignableFrom(c)) return addCatalogEntry(new 
CatalogLocationItemDto(), c);
         throw new IllegalStateException("Cannot add "+c+" to catalog: 
unsupported type "+c.getName());
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java 
b/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java
index 722220b..2b16cc6 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogItemBuilder.java
@@ -46,6 +46,12 @@ public class CatalogItemBuilder<CatalogItemType extends 
CatalogItemDtoAbstract<?
                 .version(version);
     }
 
+    public static CatalogItemBuilder<CatalogLocationItemDto> 
newLocation(String symbolicName, String version) {
+        return new CatalogItemBuilder<CatalogLocationItemDto>(new 
CatalogLocationItemDto())
+                .symbolicName(symbolicName)
+                .version(version);
+    }
+
     public CatalogItemBuilder(CatalogItemType dto) {
         this.dto = dto;
         this.dto.setLibraries(Collections.<CatalogBundle>emptyList());

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/catalog/internal/CatalogLocationItemDto.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/brooklyn/catalog/internal/CatalogLocationItemDto.java 
b/core/src/main/java/brooklyn/catalog/internal/CatalogLocationItemDto.java
new file mode 100644
index 0000000..e8bf2ec
--- /dev/null
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogLocationItemDto.java
@@ -0,0 +1,43 @@
+/*
+ * 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 brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
+
+
+public class CatalogLocationItemDto extends 
CatalogItemDtoAbstract<Location,LocationSpec<?>> {
+    
+    @Override
+    public CatalogItemType getCatalogItemType() {
+        return CatalogItemType.LOCATION;
+    }
+
+    @Override
+    public Class<Location> getCatalogItemJavaType() {
+        return Location.class;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Override
+    public Class<LocationSpec<?>> getSpecType() {
+        return (Class)LocationSpec.class;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/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 46c014f..dbfd3fc 100644
--- a/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java
+++ b/core/src/main/java/brooklyn/catalog/internal/CatalogXmlSerializer.java
@@ -45,10 +45,12 @@ public class CatalogXmlSerializer extends 
XmlSerializer<Object> {
         xstream.addImplicitCollection(CatalogDto.class, "entries", 
CatalogTemplateItemDto.class);
         xstream.addImplicitCollection(CatalogDto.class, "entries", 
CatalogEntityItemDto.class);
         xstream.addImplicitCollection(CatalogDto.class, "entries", 
CatalogPolicyItemDto.class);
+        xstream.addImplicitCollection(CatalogDto.class, "entries", 
CatalogLocationItemDto.class);
 
         xstream.aliasType("template", CatalogTemplateItemDto.class);
         xstream.aliasType("entity", CatalogEntityItemDto.class);
         xstream.aliasType("policy", CatalogPolicyItemDto.class);
+        xstream.aliasType("location", CatalogPolicyItemDto.class);
 
         xstream.aliasField("registeredType", CatalogItemDtoAbstract.class, 
"symbolicName");
         xstream.aliasAttribute(CatalogItemDtoAbstract.class, "displayName", 
"name");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java 
b/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
index 365b347..f0b466e 100644
--- a/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
+++ b/core/src/main/java/brooklyn/location/basic/BasicLocationRegistry.java
@@ -33,6 +33,9 @@ import java.util.Set;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.catalog.BrooklynCatalog;
+import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogPredicates;
 import brooklyn.config.ConfigMap;
 import brooklyn.config.ConfigPredicates;
 import brooklyn.config.ConfigUtils;
@@ -41,6 +44,7 @@ import brooklyn.location.LocationDefinition;
 import brooklyn.location.LocationRegistry;
 import brooklyn.location.LocationResolver;
 import brooklyn.location.LocationResolver.EnableableLocationResolver;
+import brooklyn.location.LocationSpec;
 import brooklyn.management.ManagementContext;
 import brooklyn.management.internal.LocalLocationManager;
 import brooklyn.util.collections.MutableMap;
@@ -59,9 +63,65 @@ import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 
+/**
+ * See {@link LocationRegistry} for general description.
+ * <p>
+ * TODO The relationship between the catalog and the location registry is a 
bit messy.
+ * For all existing code, the location registry is the definitive way to 
resolve
+ * locations. 
+ * <p>
+ * Any location item added to the catalog must therefore be registered here.
+ * Whenever an item is added to the catalog, it will automatically call 
+ * {@link #updateDefinedLocation(CatalogItem)}. Similarly, when a location
+ * is deleted from the catalog it will call {@link 
#removeDefinedLocation(CatalogItem)}.
+ * <p>
+ * However, the location item in the catalog has an unparsed blob of YAML, 
which contains
+ * important things like the type and the config of the location. This is only 
parsed when 
+ * {@link BrooklynCatalog#createSpec(CatalogItem)} is called. We therefore 
jump through 
+ * some hoops to wire together the catalog and the registry.
+ * <p>
+ * To add a location to the catalog, and then to resolve a location that is in 
the catalog, 
+ * it goes through the following steps:
+ * 
+ * <ol>
+ *   <li>Call {@link BrooklynCatalog#addItem(String)}
+ *     <ol>
+ *       <li>This automatically calls {@link 
#updateDefinedLocation(CatalogItem)}
+ *       <li>A LocationDefinition is creating, using as its id the {@link 
CatalogItem#getSymbolicName()}.
+ *           The definition's spec is {@code 
brooklyn.catalog:<symbolicName>:<version>},
+ *     </ol>
+ *   <li>A blueprint can reference the catalog item using its symbolic name, 
+ *       such as the YAML {@code location: my-new-location}.
+ *       (this feels similar to the "named locations").
+ *     <ol>
+ *       <li>This automatically calls {@link #resolve(String)}.
+ *       <li>The LocationDefinition is found by lookig up this name.
+ *       <li>The {@link LocationDefiniton.getSpec()} is retrieved; the right 
{@link LocationResolver} is 
+ *           found for it.
+ *       <li>This uses the {@link CatalogLocationResolver}, because the spec 
starts with {@code brooklyn.catalog:}.
+ *       <li>This resolver extracts from the spec the 
<symobolicName>:<version>, and looks up the 
+ *           catalog item using {@link BrooklynCatalog#getCatalogItem(String, 
String)}.
+ *       <li>It then creates a {@link LocationSpec} by calling {@link 
BrooklynCatalog#createSpec(CatalogItem)}.
+ *         <ol>
+ *           <li>This first tries to use the type (that is in the YAML) as a 
simple Java class.
+ *           <li>If that fails, it will resolve the type using {@link 
#resolve(String, Boolean, Map)}, which
+ *               returns an actual location object.
+ *           <li>It extracts from that location object the appropriate 
metadata to create a {@link LocationSpec},
+ *               returns the spec and discards the location object.
+ *         </ol>
+ *       <li>The resolver creates the {@link Location} from the {@link 
LocationSpec}
+ *     </ol>
+ * </ol>
+ * 
+ * There is no concept of a location version in this registry. The version
+ * in the catalog is generally ignored.
+ */
 @SuppressWarnings({"rawtypes","unchecked"})
 public class BasicLocationRegistry implements LocationRegistry {
 
+    // TODO save / serialize
+    // (we persist live locations, ie those in the LocationManager, but not 
"catalog" locations, ie those in this Registry)
+    
     public static final Logger log = 
LoggerFactory.getLogger(BasicLocationRegistry.class);
 
     /**
@@ -139,6 +199,25 @@ public class BasicLocationRegistry implements 
LocationRegistry {
         }
     }
 
+    /**
+     * Converts the given item from the catalog into a LocationDefinition, and 
adds it
+     * to the registry (overwriting anything already registered with the id
+     * {@link CatalogItem#getCatalogItemId()}.
+     */
+    public void updateDefinedLocation(CatalogItem<Location, LocationSpec<?>> 
item) {
+        String id = item.getCatalogItemId();
+        String symbolicName = item.getSymbolicName();
+        String spec = CatalogLocationResolver.NAME + ":" + id;
+        Map<String, Object> config = ImmutableMap.<String, Object>of();
+        BasicLocationDefinition locDefinition = new 
BasicLocationDefinition(symbolicName, symbolicName, spec, config);
+        
+        updateDefinedLocation(locDefinition);
+    }
+
+    public void removeDefinedLocation(CatalogItem<Location, LocationSpec<?>> 
item) {
+        removeDefinedLocation(item.getSymbolicName());
+    }
+    
     @Override
     public void removeDefinedLocation(String id) {
         LocationDefinition removed;
@@ -183,12 +262,14 @@ public class BasicLocationRegistry implements 
LocationRegistry {
                 definedLocations.put(id, localhost(id));
                 definedLocations.putAll(oldDefined);
             }
+            
+            for (CatalogItem<Location, LocationSpec<?>> item : 
mgmt.getCatalog().getCatalogItems(CatalogPredicates.IS_LOCATION)) {
+                updateDefinedLocation(item);
+                count++;
+            }
         }
     }
     
-    // TODO save / serialize
-    // (we persist live locations, ie those in the LocationManager, but not 
"catalog" locations, ie those in this Registry)
-    
     @VisibleForTesting
     void disablePersistence() {
         // persistence isn't enabled yet anyway (have to manually save things,

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java 
b/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java
new file mode 100644
index 0000000..4823b05
--- /dev/null
+++ b/core/src/main/java/brooklyn/location/basic/CatalogLocationResolver.java
@@ -0,0 +1,79 @@
+/*
+ * 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.location.basic;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.internal.CatalogUtils;
+import brooklyn.location.Location;
+import brooklyn.location.LocationRegistry;
+import brooklyn.location.LocationResolver;
+import brooklyn.location.LocationSpec;
+import brooklyn.management.ManagementContext;
+
+/**
+ * Given a location spec in the form {@code 
brooklyn.catalog:<symbolicName>:<version>}, 
+ * looks up the catalog to get its definition and creates such a location.
+ */
+public class CatalogLocationResolver implements LocationResolver {
+
+    private static final Logger log = 
LoggerFactory.getLogger(CatalogLocationResolver.class);
+
+    public static final String NAME = "brooklyn.catalog";
+
+    private ManagementContext managementContext;
+
+    @Override
+    public void init(ManagementContext managementContext) {
+        this.managementContext = checkNotNull(managementContext, 
"managementContext");
+    }
+    
+    @Override
+    @SuppressWarnings({ "rawtypes" })
+    public Location newLocationFromString(Map locationFlags, String spec, 
brooklyn.location.LocationRegistry registry) {
+        String id = spec.substring(NAME.length()+1);
+        CatalogItem<?, ?> item = 
CatalogUtils.getCatalogItemOptionalVersion(managementContext, id);
+        LocationSpec<?> origLocSpec = 
managementContext.getCatalog().createSpec((CatalogItem<Location, 
LocationSpec<?>>)item);
+        LocationSpec<?> locSpec = LocationSpec.create(origLocSpec)
+                .configure(locationFlags);
+        return managementContext.getLocationManager().createLocation(locSpec);
+    }
+
+    @Override
+    public String getPrefix() {
+        return NAME;
+    }
+    
+    /**
+     * accepts anything that looks like it will be a YAML catalog item (e.g. 
starting "brooklyn.locations")
+     */
+    @Override
+    public boolean accepts(String spec, LocationRegistry registry) {
+        if (BasicLocationRegistry.isResolverPrefixForSpec(this, spec, false)) 
return true;
+        if (registry.getDefinedLocationByName(spec)!=null) return true;
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/main/resources/META-INF/services/brooklyn.location.LocationResolver
----------------------------------------------------------------------
diff --git 
a/core/src/main/resources/META-INF/services/brooklyn.location.LocationResolver 
b/core/src/main/resources/META-INF/services/brooklyn.location.LocationResolver
index 74df3da..fd14323 100644
--- 
a/core/src/main/resources/META-INF/services/brooklyn.location.LocationResolver
+++ 
b/core/src/main/resources/META-INF/services/brooklyn.location.LocationResolver
@@ -1,5 +1,6 @@
 brooklyn.location.basic.DefinedLocationByIdResolver
 brooklyn.location.basic.NamedLocationResolver
+brooklyn.location.basic.CatalogLocationResolver
 brooklyn.location.basic.LocalhostLocationResolver
 brooklyn.location.basic.ByonLocationResolver
 brooklyn.location.basic.SingleMachineLocationResolver

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/test/dependencies/osgi/entities/src/main/java/brooklyn/osgi/tests/SimpleLocation.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/dependencies/osgi/entities/src/main/java/brooklyn/osgi/tests/SimpleLocation.java
 
b/core/src/test/dependencies/osgi/entities/src/main/java/brooklyn/osgi/tests/SimpleLocation.java
new file mode 100644
index 0000000..253a2b0
--- /dev/null
+++ 
b/core/src/test/dependencies/osgi/entities/src/main/java/brooklyn/osgi/tests/SimpleLocation.java
@@ -0,0 +1,35 @@
+/*
+ * 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.osgi.tests;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.location.basic.AbstractLocation;
+import brooklyn.util.flags.SetFromFlag;
+
+public class SimpleLocation extends AbstractLocation {
+    @SetFromFlag("config1")
+    public static final ConfigKey<String> CONFIG1 = 
ConfigKeys.newStringConfigKey("config1");
+
+    @SetFromFlag("config2")
+    public static final ConfigKey<String> CONFIG2 = 
ConfigKeys.newStringConfigKey("config2");
+
+    @SetFromFlag("config3")
+    public static final ConfigKey<String> CONFIG3 = 
ConfigKeys.newStringConfigKey("config3");
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java 
b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
index 4454354..a64b269 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindCatalogItemTest.java
@@ -34,6 +34,7 @@ import org.testng.annotations.Test;
 import brooklyn.camp.lite.CampPlatformWithJustBrooklynMgmt;
 import brooklyn.camp.lite.TestAppAssemblyInstantiator;
 import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogItem.CatalogItemType;
 import brooklyn.catalog.CatalogLoadMode;
 import brooklyn.catalog.internal.BasicBrooklynCatalog;
 import brooklyn.catalog.internal.CatalogDto;
@@ -41,11 +42,15 @@ import brooklyn.config.BrooklynProperties;
 import brooklyn.config.BrooklynServerConfig;
 import brooklyn.entity.proxying.EntitySpec;
 import brooklyn.internal.BrooklynFeatureEnablement;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.management.ManagementContext;
 import brooklyn.management.internal.LocalManagementContext;
 import brooklyn.policy.basic.AbstractPolicy;
 import brooklyn.test.entity.TestEntity;
 
+import com.google.common.base.Joiner;
 import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 
 public class RebindCatalogItemTest extends RebindTestFixtureWithApp {
@@ -112,8 +117,7 @@ public class RebindCatalogItemTest extends 
RebindTestFixtureWithApp {
                 "  version: " + TEST_VERSION + "\n" +
                 "services:\n" +
                 "- type: io.camp.mock:AppServer";
-        CatalogItem<?, ?> added = 
origManagementContext.getCatalog().addItem(yaml);
-        LOG.info("Added item to catalog: {}, id={}", added, added.getId());
+        addItem(origManagementContext, yaml);
         rebindAndAssertCatalogsAreEqual();
     }
 
@@ -135,8 +139,24 @@ public class RebindCatalogItemTest extends 
RebindTestFixtureWithApp {
                 "  brooklyn.config:\n" +
                 "    cfg1: 111\n" +
                 "    cfg2: 222";
-        CatalogItem<?, ?> added = 
origManagementContext.getCatalog().addItem(yaml);
-        LOG.info("Added item to catalog: {}, id={}", added, added.getId());
+        addItem(origManagementContext, yaml);
+        rebindAndAssertCatalogsAreEqual();
+    }
+
+    @Test
+    public void testAddAndRebindLocation() {
+        String yaml = Joiner.on("\n").join(ImmutableList.of(
+                "name: Test Location",
+                "brooklyn.catalog:",
+                "  id: sample_location",
+                "  version: " + TEST_VERSION,
+                "brooklyn.locations:",
+                "- type: 
"+LocalhostMachineProvisioningLocation.class.getName(),
+                "  brooklyn.config:",
+                "    cfg1: 111",
+                "    cfg2: 222"));
+        CatalogItem<?, ?> added = addItem(origManagementContext, yaml);
+        assertEquals(added.getCatalogItemType(), CatalogItemType.LOCATION);
         rebindAndAssertCatalogsAreEqual();
     }
 
@@ -210,6 +230,13 @@ public class RebindCatalogItemTest extends 
RebindTestFixtureWithApp {
         assertTrue(catalogItemAfterRebind.isDeprecated(), "Expected item to be 
deprecated");
     }
 
+    protected CatalogItem<?, ?> addItem(ManagementContext mgmt, String yaml) {
+        CatalogItem<?, ?> added = mgmt.getCatalog().addItem(yaml);
+        LOG.info("Added item to catalog: {}, id={}", added, added.getId());
+        assertCatalogContains(mgmt.getCatalog(), added);
+        return added;
+    }
+    
     private void rebindAndAssertCatalogsAreEqual() {
         try {
             rebind();
@@ -218,5 +245,4 @@ public class RebindCatalogItemTest extends 
RebindTestFixtureWithApp {
         }
         assertCatalogsEqual(newManagementContext.getCatalog(), 
origManagementContext.getCatalog());
     }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java 
b/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java
index 8f1c1c6..21ab69f 100644
--- a/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java
+++ b/core/src/test/java/brooklyn/entity/rebind/RebindTestFixture.java
@@ -19,6 +19,7 @@
 package brooklyn.entity.rebind;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
 
 import java.io.File;
 import java.util.List;
@@ -53,9 +54,9 @@ import brooklyn.util.task.BasicExecutionManager;
 import brooklyn.util.text.Identifiers;
 import brooklyn.util.time.Duration;
 
-import com.google.common.collect.Sets;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
 
 public abstract class RebindTestFixture<T extends StartableApplication> {
 
@@ -303,4 +304,10 @@ public abstract class RebindTestFixture<T extends 
StartableApplication> {
         assertEquals(actual.getSymbolicName(), expected.getSymbolicName());
         assertEquals(actual.getLibraries(), expected.getLibraries());
     }
+    
+    protected void assertCatalogContains(BrooklynCatalog catalog, 
CatalogItem<?, ?> item) {
+        CatalogItem<?, ?> found = 
catalog.getCatalogItem(item.getSymbolicName(), item.getVersion());
+        assertNotNull(found);
+        assertCatalogItemsEqual(found, item);
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar
----------------------------------------------------------------------
diff --git 
a/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar 
b/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar
index 008150a..5a3b052 100644
Binary files 
a/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar and 
b/core/src/test/resources/brooklyn/osgi/brooklyn-test-osgi-entities.jar differ

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/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 aa1922c..dd7ad58 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
@@ -39,8 +39,12 @@ import brooklyn.management.Task;
 import brooklyn.management.internal.LocalManagementContext;
 import brooklyn.test.entity.LocalManagementContextForTests;
 import brooklyn.util.ResourceUtils;
+import brooklyn.util.config.ConfigBag;
 
 import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
 
 public abstract class AbstractYamlTest {
 
@@ -119,6 +123,14 @@ public abstract class AbstractYamlTest {
         getLogger().info("Test - created " + assembly);
         final Entity app = 
brooklynMgmt.getEntityManager().getEntity(assembly.getId());
         getLogger().info("App - " + app);
+        
+        // wait for app to have started
+        Set<Task<?>> tasks = 
brooklynMgmt.getExecutionManager().getTasksWithAllTags(ImmutableList.of(
+                BrooklynTaskTags.EFFECTOR_TAG, 
+                BrooklynTaskTags.tagForContextEntity(app), 
+                BrooklynTaskTags.tagForEffectorCall(app, "start", 
ConfigBag.newInstance(ImmutableMap.of("locations", ImmutableMap.of())))));
+        Iterables.getOnlyElement(tasks).get();
+        
         return app;
     }
 
@@ -132,6 +144,10 @@ public abstract class AbstractYamlTest {
         return app;
     }
 
+    protected void addCatalogItem(Iterable<String> catalogYaml) {
+        addCatalogItem(join(catalogYaml));
+    }
+
     protected void addCatalogItem(String... catalogYaml) {
         addCatalogItem(join(catalogYaml));
     }
@@ -148,6 +164,10 @@ public abstract class AbstractYamlTest {
         return LOG;
     }
 
+    private String join(Iterable<String> catalogYaml) {
+        return Joiner.on("\n").join(catalogYaml);
+    }
+
     private String join(String[] catalogYaml) {
         return Joiner.on("\n").join(catalogYaml);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java
----------------------------------------------------------------------
diff --git 
a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java
 
b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java
index eb2358d..9b7bc7b 100644
--- 
a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java
+++ 
b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/EmptySoftwareProcessYamlTest.java
@@ -32,17 +32,19 @@ import brooklyn.entity.basic.Entities;
 import brooklyn.location.Location;
 import brooklyn.location.basic.SshMachineLocation;
 import brooklyn.util.collections.Jsonya;
-import brooklyn.util.stream.Streams;
 
 @Test
 public class EmptySoftwareProcessYamlTest extends AbstractYamlTest {
     private static final Logger log = 
LoggerFactory.getLogger(EnrichersYamlTest.class);
 
-    @Test
+    @Test(groups="Integration")
     public void testProvisioningProperties() throws Exception {
-        Entity app = createAndStartApplication(Streams.newReaderWithContents(
-            "services: [ { serviceType: 
"+EmptySoftwareProcess.class.getName()+","
-                + " provisioning.properties: { minRam: 16384 } } ]"));
+        Entity app = createAndStartApplication(
+            "location: localhost",
+            "services:",
+            "- type: "+EmptySoftwareProcess.class.getName(),
+            "  provisioning.properties:",
+            "    minRam: 16384");
         waitForApplicationTasks(app);
 
         log.info("App started:");
@@ -53,14 +55,15 @@ public class EmptySoftwareProcessYamlTest extends 
AbstractYamlTest {
         Assert.assertEquals(pp.get("minRam"), 16384);
     }
 
-    @Test
+    @Test(groups="Integration")
     public void testProvisioningPropertiesViaJsonya() throws Exception {
-        Entity app = createAndStartApplication(Streams.newReaderWithContents(
-            Jsonya.newInstance().at("services").list()
-                .put("serviceType", EmptySoftwareProcess.class.getName())
+        Entity app = createAndStartApplication(
+            Jsonya.newInstance()
+                .put("location", "localhost")
+                .at("services").list()
+                .put("type", EmptySoftwareProcess.class.getName())
                 .at("provisioning.properties").put("minRam", 16384)
-                .root().toString()
-        ));
+                .root().toString());
         waitForApplicationTasks(app);
 
         log.info("App started:");
@@ -71,13 +74,14 @@ public class EmptySoftwareProcessYamlTest extends 
AbstractYamlTest {
         Assert.assertEquals(pp.get("minRam"), 16384);
     }
 
-    @Test
-    // for issue #1377
+    // for https://github.com/brooklyncentral/brooklyn/issues/1377
+    @Test(groups="Integration")
     public void testWithAppAndEntityLocations() throws Exception {
-        Entity app = 
createAndStartApplication(Streams.newReaderWithContents("services:\n"+
-            "- serviceType: "+EmptySoftwareProcess.class.getName()+"\n"+
-            "  location: localhost:(name=localhost on entity)"+"\n"+
-            "location: byon:(hosts=\"127.0.0.1\", name=loopback on app)"));
+        Entity app = createAndStartApplication(
+                "services:",
+                "- type: "+EmptySoftwareProcess.class.getName(),
+                "  location: localhost:(name=localhost on entity)",
+                "location: byon:(hosts=\"127.0.0.1\", name=loopback on app)");
         waitForApplicationTasks(app);
         Entities.dumpInfo(app);
         
@@ -96,5 +100,4 @@ public class EmptySoftwareProcessYamlTest extends 
AbstractYamlTest {
         // TODO this, below, probably should be 'localhost on entity', see 
#1377
         Assert.assertEquals(actualMachine.getParent().getDisplayName(), 
"loopback on app");
     }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
----------------------------------------------------------------------
diff --git 
a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
 
b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
new file mode 100644
index 0000000..ef5aa0f
--- /dev/null
+++ 
b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlLocationTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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 io.brooklyn.camp.brooklyn.catalog;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import io.brooklyn.camp.brooklyn.AbstractYamlTest;
+
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogPredicates;
+import brooklyn.entity.Entity;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.location.Location;
+import brooklyn.location.LocationDefinition;
+import brooklyn.location.basic.LocalhostMachineProvisioningLocation;
+import brooklyn.management.osgi.OsgiStandaloneTest;
+import brooklyn.test.TestResourceUnavailableException;
+import brooklyn.util.text.StringFunctions;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+public class CatalogYamlLocationTest extends AbstractYamlTest {
+    private static final String LOCALHOST_LOCATION_SPEC = "localhost";
+    private static final String LOCALHOST_LOCATION_TYPE = 
LocalhostMachineProvisioningLocation.class.getName();
+    private static final String SIMPLE_LOCATION_TYPE = 
"brooklyn.osgi.tests.SimpleLocation";
+
+    @Test
+    public void testAddCatalogItem() throws Exception {
+        assertEquals(countCatalogLocations(), 0);
+
+        String symbolicName = "my.catalog.location.id.load";
+        addCatalogOSGiLocation(symbolicName, SIMPLE_LOCATION_TYPE);
+
+        CatalogItem<?, ?> item = 
mgmt().getCatalog().getCatalogItem(symbolicName, TEST_VERSION);
+        assertEquals(item.getSymbolicName(), symbolicName);
+        assertEquals(countCatalogLocations(), 1);
+
+        // Item added to catalog should automatically be available in location 
registry
+        LocationDefinition def = 
mgmt().getLocationRegistry().getDefinedLocationByName(symbolicName);
+        assertEquals(def.getId(), symbolicName);
+        assertEquals(def.getName(), symbolicName);
+
+        // Deleting item: should be gone from catalog, and from location 
registry
+        deleteCatalogEntity(symbolicName);
+
+        assertEquals(countCatalogLocations(), 0);
+        
assertNull(mgmt().getLocationRegistry().getDefinedLocationByName(symbolicName));
+    }
+
+    @Test
+    public void testLaunchApplicationReferencingLocationClass() throws 
Exception {
+        String symbolicName = "my.catalog.location.id.launch";
+        addCatalogLocation(symbolicName, LOCALHOST_LOCATION_TYPE);
+        runLaunchApplicationReferencingLocation(symbolicName, 
LOCALHOST_LOCATION_TYPE);
+
+        deleteCatalogEntity(symbolicName);
+    }
+
+    @Test
+    public void testLaunchApplicationReferencingLocationSpec() throws 
Exception {
+        String symbolicName = "my.catalog.location.id.launch";
+        addCatalogLocation(symbolicName, LOCALHOST_LOCATION_SPEC);
+        runLaunchApplicationReferencingLocation(symbolicName, 
LOCALHOST_LOCATION_TYPE);
+
+        deleteCatalogEntity(symbolicName);
+    }
+
+    @Test
+    public void testLaunchApplicationReferencingOsgiLocation() throws 
Exception {
+        String symbolicName = "my.catalog.location.id.launch";
+        addCatalogOSGiLocation(symbolicName, SIMPLE_LOCATION_TYPE);
+        runLaunchApplicationReferencingLocation(symbolicName, 
SIMPLE_LOCATION_TYPE);
+        
+        deleteCatalogEntity(symbolicName);
+    }
+    
+    protected void runLaunchApplicationReferencingLocation(String 
locTypeInYaml, String locType) throws Exception {
+        Entity app = createAndStartApplication(
+            "name: simple-app-yaml",
+            "location: ",
+            "  "+locTypeInYaml+":",
+            "    config2: config2 override",
+            "    config3: config3",
+            "services: ",
+            "  - type: brooklyn.entity.basic.BasicStartable");
+
+        Entity simpleEntity = Iterables.getOnlyElement(app.getChildren());
+        Location location = 
Iterables.getOnlyElement(simpleEntity.getLocations());
+        assertEquals(location.getClass().getName(), locType);
+        assertEquals(location.getConfig(new 
BasicConfigKey<String>(String.class, "config1")), "config1");
+        assertEquals(location.getConfig(new 
BasicConfigKey<String>(String.class, "config2")), "config2 override");
+        assertEquals(location.getConfig(new 
BasicConfigKey<String>(String.class, "config3")), "config3");
+    }
+
+    private void addCatalogLocation(String symbolicName, String serviceType) {
+        addCatalogLocation(symbolicName, serviceType, 
ImmutableList.<String>of());
+    }
+
+    private void addCatalogOSGiLocation(String symbolicName, String 
serviceType) {
+        
TestResourceUnavailableException.throwIfResourceUnavailable(getClass(), 
OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_PATH);
+        addCatalogLocation(symbolicName, serviceType, 
ImmutableList.of(OsgiStandaloneTest.BROOKLYN_TEST_OSGI_ENTITIES_URL));
+    }
+    
+    private void addCatalogLocation(String symbolicName, String serviceType, 
List<String> libraries) {
+        ImmutableList.Builder<String> yaml = 
ImmutableList.<String>builder().add(
+                "brooklyn.catalog:",
+                "  id: " + symbolicName,
+                "  name: My Catalog Location",
+                "  description: My description",
+                "  version: " + TEST_VERSION);
+        if (libraries.size() > 0) {
+            yaml.add("  libraries:")
+                .addAll(Lists.transform(libraries, StringFunctions.prepend("  
- url: ")));
+        }
+        yaml.add(
+                "",
+                "brooklyn.locations:",
+                "- type: " + serviceType,
+                "  brooklyn.config:",
+                "    config1: config1",
+                "    config2: config2");
+        
+        
+        addCatalogItem(yaml.build());
+    }
+
+    private int countCatalogLocations() {
+        return 
Iterables.size(mgmt().getCatalog().getCatalogItems(CatalogPredicates.IS_LOCATION));
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java 
b/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java
index d4f004c..aa73215 100644
--- 
a/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java
+++ 
b/usage/rest-server/src/main/java/brooklyn/rest/resources/LocationResource.java
@@ -29,9 +29,9 @@ import javax.ws.rs.core.Response;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.catalog.CatalogItem;
 import brooklyn.location.Location;
 import brooklyn.location.LocationDefinition;
-import brooklyn.location.basic.BasicLocationDefinition;
 import brooklyn.location.basic.LocationConfigKeys;
 import brooklyn.rest.api.LocationApi;
 import brooklyn.rest.domain.LocationSpec;
@@ -43,10 +43,11 @@ import brooklyn.rest.util.EntityLocationUtils;
 import brooklyn.rest.util.WebResourceUtils;
 import brooklyn.util.collections.MutableMap;
 import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.text.Identifiers;
 
 import com.google.common.base.Function;
+import com.google.common.base.Joiner;
 import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 
 public class LocationResource extends AbstractBrooklynRestResource implements 
LocationApi {
@@ -101,40 +102,52 @@ public class LocationResource extends 
AbstractBrooklynRestResource implements Lo
       return result;
     }
 
-  /** @deprecated since 0.7.0; REST call now handled by below (optional query 
parameter added) */
-  public LocationSummary get(String locationId) {
-      return get(locationId, false);
-  }
-
-  @Override
-  public LocationSummary get(String locationId, String fullConfig) {
-      return get(locationId, Boolean.valueOf(fullConfig));
-  }
-
-  public LocationSummary get(String locationId, boolean fullConfig) {
-      LocationDetailLevel configLevel = fullConfig ? 
LocationDetailLevel.FULL_EXCLUDING_SECRET : 
LocationDetailLevel.LOCAL_EXCLUDING_SECRET;
-      Location l1 = mgmt().getLocationManager().getLocation(locationId);
-      if (l1!=null) {
-        return LocationTransformer.newInstance(mgmt(), l1, configLevel);
+    /** @deprecated since 0.7.0; REST call now handled by below (optional 
query parameter added) */
+    public LocationSummary get(String locationId) {
+        return get(locationId, false);
     }
 
-      LocationDefinition l2 = 
brooklyn().getLocationRegistry().getDefinedLocationById(locationId);
-      if (l2==null) throw WebResourceUtils.notFound("No location matching %s", 
locationId);
-      return LocationTransformer.newInstance(mgmt(), l2, configLevel);
-  }
-
-  @Override
-  public Response create(LocationSpec locationSpec) {
-      String id = Identifiers.makeRandomId(8);
-      LocationDefinition l = new BasicLocationDefinition(id, 
locationSpec.getName(), locationSpec.getSpec(), locationSpec.getConfig());
-      brooklyn().getLocationRegistry().updateDefinedLocation(l);
-      return Response.created(URI.create(id))
-              .entity(LocationTransformer.newInstance(mgmt(), l, 
LocationDetailLevel.LOCAL_EXCLUDING_SECRET))
-              .build();
-  }
-
-  public void delete(String locationId) {
-      brooklyn().getLocationRegistry().removeDefinedLocation(locationId);
-  }
+    @Override
+    public LocationSummary get(String locationId, String fullConfig) {
+        return get(locationId, Boolean.valueOf(fullConfig));
+    }
+
+    public LocationSummary get(String locationId, boolean fullConfig) {
+        LocationDetailLevel configLevel = fullConfig ? 
LocationDetailLevel.FULL_EXCLUDING_SECRET : 
LocationDetailLevel.LOCAL_EXCLUDING_SECRET;
+        Location l1 = mgmt().getLocationManager().getLocation(locationId);
+        if (l1!=null) {
+            return LocationTransformer.newInstance(mgmt(), l1, configLevel);
+        }
+
+        LocationDefinition l2 = 
brooklyn().getLocationRegistry().getDefinedLocationById(locationId);
+        if (l2==null) throw WebResourceUtils.notFound("No location matching 
%s", locationId);
+        return LocationTransformer.newInstance(mgmt(), l2, configLevel);
+    }
 
+    @Override
+    public Response create(LocationSpec locationSpec) {
+        String name = locationSpec.getName();
+        ImmutableList.Builder<String> yaml = 
ImmutableList.<String>builder().add(
+                "brooklyn.catalog:",
+                "  symbolicName: "+name,
+                "",
+                "brooklyn.locations:",
+                "- type: "+locationSpec.getSpec());
+          if (locationSpec.getConfig().size() > 0) {
+              yaml.add("  brooklyn.config:");
+              for (Map.Entry<String, ?> entry : 
locationSpec.getConfig().entrySet()) {
+                  yaml.add("    "+entry.getKey()+": "+entry.getValue());
+              }
+          }
+
+          CatalogItem<?, ?> item = 
brooklyn().getCatalog().addItem(Joiner.on("\n").join(yaml.build()));
+          LocationDefinition l = 
brooklyn().getLocationRegistry().getDefinedLocationByName(name);
+          return Response.created(URI.create(name))
+                  .entity(LocationTransformer.newInstance(mgmt(), l, 
LocationDetailLevel.LOCAL_EXCLUDING_SECRET))
+                  .build();
+    }
+
+    public void delete(String locationId) {
+        brooklyn().getLocationRegistry().removeDefinedLocation(locationId);
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/usage/rest-server/src/test/java/brooklyn/rest/resources/LocationResourceTest.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/test/java/brooklyn/rest/resources/LocationResourceTest.java
 
b/usage/rest-server/src/test/java/brooklyn/rest/resources/LocationResourceTest.java
index 0511ca4..5ccf591 100644
--- 
a/usage/rest-server/src/test/java/brooklyn/rest/resources/LocationResourceTest.java
+++ 
b/usage/rest-server/src/test/java/brooklyn/rest/resources/LocationResourceTest.java
@@ -20,7 +20,6 @@ package brooklyn.rest.resources;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
-import static org.testng.Assert.assertFalse;
 
 import java.net.URI;
 import java.util.Map;
@@ -57,24 +56,23 @@ public class LocationResourceTest extends 
BrooklynRestResourceTest {
     public void testAddNewLocation() {
         Map<String, String> expectedConfig = ImmutableMap.of(
                 "identity", "bob",
-                "credential", "CR3dential",
-                "location", "us-east-1");
+                "credential", "CR3dential");
         ClientResponse response = client().resource("/v1/locations")
                 .type(MediaType.APPLICATION_JSON_TYPE)
-                .post(ClientResponse.class, new LocationSpec("my-jungle", 
"aws-ec2", expectedConfig));
+                .post(ClientResponse.class, new LocationSpec("my-jungle", 
"aws-ec2:us-east-1", expectedConfig));
 
         addedLocationUri = response.getLocation();
         log.info("added, at: " + addedLocationUri);
         LocationSummary location = 
client().resource(response.getLocation()).get(LocationSummary.class);
         log.info(" contents: " + location);
-        assertThat(location.getSpec(), is("aws-ec2"));
-
-        assertThat(location.getConfig().get("identity"), is((Object) "bob"));
-        assertFalse(location.getConfig().containsKey("CR3dential"));
+        Assert.assertEquals(location.getSpec(), 
"brooklyn.catalog:my-jungle:0.0.0.SNAPSHOT");
         
Assert.assertTrue(addedLocationUri.toString().startsWith("/v1/locations/"));
 
-        JcloudsLocation l = (JcloudsLocation) 
getManagementContext().getLocationRegistry().resolve(location.getId());
+        JcloudsLocation l = (JcloudsLocation) 
getManagementContext().getLocationRegistry().resolve("my-jungle");
         Assert.assertEquals(l.getProvider(), "aws-ec2");
+        Assert.assertEquals(l.getRegion(), "us-east-1");
+        Assert.assertEquals(l.getIdentity(), "bob");
+        Assert.assertEquals(l.getCredential(), "CR3dential");
     }
 
     @Test(dependsOnMethods = { "testAddNewLocation" })
@@ -88,14 +86,14 @@ public class LocationResourceTest extends 
BrooklynRestResourceTest {
             }
         });
         LocationSummary location = Iterables.getOnlyElement(matching);
-        assertThat(location.getSpec(), is("aws-ec2"));
+        assertThat(location.getSpec(), 
is("brooklyn.catalog:my-jungle:0.0.0.SNAPSHOT"));
         Assert.assertEquals(location.getLinks().get("self"), addedLocationUri);
     }
 
     @Test(dependsOnMethods = { "testListAllLocations" })
     public void testGetASpecificLocation() {
         LocationSummary location = 
client().resource(addedLocationUri.toString()).get(LocationSummary.class);
-        assertThat(location.getSpec(), is("aws-ec2"));
+        assertThat(location.getSpec(), 
is("brooklyn.catalog:my-jungle:0.0.0.SNAPSHOT"));
     }
 
     @Test(dependsOnMethods = { "testGetASpecificLocation" })

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/09a64755/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java 
b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
index 7c946dd..ad56bfe 100644
--- a/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
+++ b/utils/common/src/main/java/brooklyn/util/yaml/Yamls.java
@@ -29,6 +29,9 @@ import java.util.Map.Entry;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import brooklyn.util.collections.Jsonya;
+
+import com.google.common.annotations.Beta;
 import com.google.common.collect.Iterables;
 
 public class Yamls {
@@ -71,6 +74,7 @@ public class Yamls {
      * 
      * @see #getAt(Object, List)
      */
+    @Beta
     public static Object getAt(String yaml, List<String> path) {
         Iterable<Object> result = new org.yaml.snakeyaml.Yaml().loadAll(yaml);
         Object current = result.iterator().next();
@@ -85,8 +89,11 @@ public class Yamls {
      *   <li>A string in the form like "[0]" is assumed to be an index into a 
list
      * </ul>
      * 
-     * Returns {@code null} if that path does not exist. 
+     * Also see {@link Jsonya}, such as {@code 
Jsonya.of(current).at(path).get()}.
+     * 
+     * @return The object at the given path, or {@code null} if that path does 
not exist.
      */
+    @Beta
     @SuppressWarnings("unchecked")
     public static Object getAtPreParsed(Object current, List<String> path) {
         for (String pathPart : path) {

Reply via email to