http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/AbstractLocation.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/location/basic/AbstractLocation.java 
b/core/src/main/java/org/apache/brooklyn/location/basic/AbstractLocation.java
new file mode 100644
index 0000000..eee375b
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/location/basic/AbstractLocation.java
@@ -0,0 +1,708 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import static brooklyn.util.GroovyJavaMethods.elvis;
+import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Closeable;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.brooklyn.api.entity.rebind.RebindSupport;
+import org.apache.brooklyn.api.entity.trait.Configurable;
+import org.apache.brooklyn.api.management.Task;
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.mementos.LocationMemento;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.basic.AbstractBrooklynObject;
+import brooklyn.config.ConfigInheritance;
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigKey.HasConfigKey;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.rebind.BasicLocationRebindSupport;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.internal.BrooklynFeatureEnablement;
+import brooklyn.internal.storage.BrooklynStorage;
+import brooklyn.internal.storage.Reference;
+import brooklyn.internal.storage.impl.BasicReference;
+import org.apache.brooklyn.location.LocationSpec;
+import org.apache.brooklyn.location.geo.HasHostGeoInfo;
+import org.apache.brooklyn.location.geo.HostGeoInfo;
+import brooklyn.management.internal.LocalLocationManager;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.util.collections.SetFromLiveMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.flags.FlagUtils;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.stream.Streams;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * A basic implementation of the {@link Location} interface.
+ *
+ * This provides an implementation which works according to the requirements of
+ * the interface documentation, and is ready to be extended to make more 
specialized locations.
+ * 
+ * Override {@link #configure(Map)} to add special initialization logic.
+ */
+public abstract class AbstractLocation extends AbstractBrooklynObject 
implements LocationInternal, HasHostGeoInfo, Configurable {
+    
+    private static final long serialVersionUID = -7495805474138619830L;
+
+    /** @deprecated since 0.7.0 shouldn't be public */
+    @Deprecated
+    public static final Logger LOG = 
LoggerFactory.getLogger(AbstractLocation.class);
+
+    public static final ConfigKey<Location> PARENT_LOCATION = new 
BasicConfigKey<Location>(Location.class, "parentLocation");
+
+    public static final ConfigKey<Boolean> TEMPORARY_LOCATION = 
ConfigKeys.newBooleanConfigKey("temporaryLocation",
+            "Indicates that the location is a temporary location that has been 
created to test connectivity, and that" +
+            "the location's events should not be recorded by usage listeners", 
false);
+
+    private final AtomicBoolean configured = new AtomicBoolean();
+    
+    private Reference<Long> creationTimeUtc = new 
BasicReference<Long>(System.currentTimeMillis());
+    
+    // _not_ set from flag; configured explicitly in configure, because we 
also need to update the parent's list of children
+    private Reference<Location> parent = new BasicReference<Location>();
+    
+    // NB: all accesses should be synchronized
+    private Set<Location> children = Sets.newLinkedHashSet();
+
+    private Reference<String> name = new BasicReference<String>();
+    private boolean displayNameAutoGenerated = true;
+
+    private Reference<HostGeoInfo> hostGeoInfo = new 
BasicReference<HostGeoInfo>();
+
+    private BasicConfigurationSupport config = new BasicConfigurationSupport();
+    
+    private ConfigBag configBag = new ConfigBag();
+
+    private volatile boolean managed;
+
+    private boolean inConstruction;
+
+    private Reference<Map<Class<?>, Object>> extensions = new 
BasicReference<Map<Class<?>, Object>>(Maps.<Class<?>, 
Object>newConcurrentMap());
+
+    private final LocationDynamicType locationType;
+
+    /**
+     * Construct a new instance of an AbstractLocation.
+     */
+    public AbstractLocation() {
+        this(Maps.newLinkedHashMap());
+    }
+    
+    /**
+     * Construct a new instance of an AbstractLocation.
+     *
+     * The properties map recognizes the following keys:
+     * <ul>
+     * <li>name - a name for the location
+     * <li>parentLocation - the parent {@link Location}
+     * </ul>
+     * 
+     * Other common properties (retrieved via get/findLocationProperty) 
include:
+     * <ul>
+     * <li>latitude
+     * <li>longitude
+     * <li>displayName
+     * <li>iso3166 - list of iso3166-2 code strings
+     * <li>timeZone
+     * <li>abbreviatedName
+     * </ul>
+     */
+    public AbstractLocation(Map<?,?> properties) {
+        super(properties);
+        inConstruction = true;
+        
+        // When one calls getConfig(key), we want to use the default value 
specified on *this* location
+        // if it overrides the default config, by using the type object 
+        locationType = new LocationDynamicType(this);
+        
+        if (isLegacyConstruction()) {
+            AbstractBrooklynObject checkWeGetThis = configure(properties);
+            assert this.equals(checkWeGetThis) : this+" configure method does 
not return itself; returns "+checkWeGetThis+" instead of "+this;
+
+            boolean deferConstructionChecks = 
(properties.containsKey("deferConstructionChecks") && 
TypeCoercions.coerce(properties.get("deferConstructionChecks"), Boolean.class));
+            if (!deferConstructionChecks) {
+                FlagUtils.checkRequiredFields(this);
+            }
+        }
+        
+        inConstruction = false;
+    }
+
+    protected void assertNotYetManaged() {
+        if (!inConstruction && Locations.isManaged(this)) {
+            LOG.warn("Configuration being made to {} after deployment; may not 
be supported in future versions", this);
+        }
+        //throw new IllegalStateException("Cannot set configuration "+key+" on 
active location "+this)
+    }
+
+    public void setManagementContext(ManagementContextInternal 
managementContext) {
+        super.setManagementContext(managementContext);
+        if (displayNameAutoGenerated && getId() != null) 
name.set(getClass().getSimpleName()+":"+getId().substring(0, 
Math.min(getId().length(),4)));
+
+        if 
(BrooklynFeatureEnablement.isEnabled(BrooklynFeatureEnablement.FEATURE_USE_BROOKLYN_LIVE_OBJECTS_DATAGRID_STORAGE))
 {
+            Location oldParent = parent.get();
+            Set<Location> oldChildren = children;
+            Map<String, Object> oldConfig = configBag.getAllConfig();
+            Long oldCreationTimeUtc = creationTimeUtc.get();
+            String oldDisplayName = name.get();
+            HostGeoInfo oldHostGeoInfo = hostGeoInfo.get();
+
+            parent = 
managementContext.getStorage().getReference(getId()+"-parent");
+            children = 
SetFromLiveMap.create(managementContext.getStorage().<Location,Boolean>getMap(getId()+"-children"));
+            creationTimeUtc = 
managementContext.getStorage().getReference(getId()+"-creationTime");
+            hostGeoInfo = 
managementContext.getStorage().getReference(getId()+"-hostGeoInfo");
+            name = 
managementContext.getStorage().getReference(getId()+"-displayName");
+
+            // Only override stored defaults if we have actual values. We 
might be in setManagementContext
+            // because we are reconstituting an existing entity in a new 
brooklyn management-node (in which
+            // case believe what is already in the storage), or we might be in 
the middle of creating a new 
+            // entity. Normally for a new entity (using EntitySpec creation 
approach), this will get called
+            // before setting the parent etc. However, for backwards 
compatibility we still support some
+            // things calling the entity's constructor directly.
+            if (oldParent != null) parent.set(oldParent);
+            if (oldChildren.size() > 0) children.addAll(oldChildren);
+            if (creationTimeUtc.isNull()) 
creationTimeUtc.set(oldCreationTimeUtc);
+            if (hostGeoInfo.isNull()) hostGeoInfo.set(oldHostGeoInfo);
+            if (name.isNull()) {
+                name.set(oldDisplayName);
+            } else {
+                displayNameAutoGenerated = false;
+            }
+
+            configBag = 
ConfigBag.newLiveInstance(managementContext.getStorage().<String,Object>getMap(getId()+"-config"));
+            if (oldConfig.size() > 0) {
+                configBag.putAll(oldConfig);
+            }
+        }
+    }
+
+    /**
+     * @deprecated since 0.7.0; only used for legacy brooklyn types where 
constructor is called directly;
+     * see overridden method for more info
+     */
+    @SuppressWarnings("serial")
+    @Override
+    @Deprecated
+    public AbstractLocation configure(Map<?,?> properties) {
+        assertNotYetManaged();
+        
+        boolean firstTime = !configured.getAndSet(true);
+            
+        configBag.putAll(properties);
+        
+        if (properties.containsKey(PARENT_LOCATION.getName())) {
+            // need to ensure parent's list of children is also updated
+            setParent(configBag.get(PARENT_LOCATION));
+            
+            // don't include parentLocation in configBag, as breaks rebind
+            configBag.remove(PARENT_LOCATION);
+        }
+
+        // NB: flag-setting done here must also be done in 
BasicLocationRebindSupport 
+        FlagUtils.setFieldsFromFlagsWithBag(this, properties, configBag, 
firstTime);
+        FlagUtils.setAllConfigKeys(this, configBag, false);
+
+        if (properties.containsKey("displayName")) {
+            name.set((String) removeIfPossible(properties, "displayName"));
+            displayNameAutoGenerated = false;
+        } else if (properties.containsKey("name")) {
+            name.set((String) removeIfPossible(properties, "name"));
+            displayNameAutoGenerated = false;
+        } else if (isLegacyConstruction()) {
+            name.set(getClass().getSimpleName()+":"+getId().substring(0, 
Math.min(getId().length(),4)));
+            displayNameAutoGenerated = true;
+        }
+
+        // TODO Explicitly dealing with iso3166 here because want custom 
splitter rule comma-separated string.
+        // Is there a better way to do it (e.g. more similar to latitude, 
where configKey+TypeCoercion is enough)?
+        if (groovyTruth(properties.get("iso3166"))) {
+            Object rawCodes = removeIfPossible(properties, "iso3166");
+            Set<String> codes;
+            if (rawCodes instanceof CharSequence) {
+                codes = 
ImmutableSet.copyOf(Splitter.on(",").trimResults().split((CharSequence)rawCodes));
+            } else {
+                codes = TypeCoercions.coerce(rawCodes, new 
TypeToken<Set<String>>() {});
+            }
+            configBag.put(LocationConfigKeys.ISO_3166, codes);
+        }
+        
+        return this;
+    }
+
+    // TODO ensure no callers rely on 'remove' semantics, and don't remove;
+    // or perhaps better use a config bag so we know what is used v unused
+    private static Object removeIfPossible(Map<?,?> map, Object key) {
+        try {
+            return map.remove(key);
+        } catch (Exception e) {
+            return map.get(key);
+        }
+    }
+    
+    public boolean isManaged() {
+        return getManagementContext() != null && managed;
+    }
+
+    public void onManagementStarted() {
+        if (displayNameAutoGenerated) 
name.set(getClass().getSimpleName()+":"+getId().substring(0, 
Math.min(getId().length(),4)));
+        this.managed = true;
+    }
+    
+    public void onManagementStopped() {
+        this.managed = false;
+        if (getManagementContext().isRunning()) {
+            BrooklynStorage storage = 
((ManagementContextInternal)getManagementContext()).getStorage();
+            storage.remove(getId()+"-parent");
+            storage.remove(getId()+"-children");
+            storage.remove(getId()+"-creationTime");
+            storage.remove(getId()+"-hostGeoInfo");
+            storage.remove(getId()+"-displayName");
+            storage.remove(getId()+"-config");
+        }
+    }
+    
+    @Override
+    public String getDisplayName() {
+        return name.get();
+    }
+    
+    protected boolean isDisplayNameAutoGenerated() {
+        return displayNameAutoGenerated;
+    }
+    
+    @Override
+    public Location getParent() {
+        return parent.get();
+    }
+    
+    @Override
+    public Collection<Location> getChildren() {
+        synchronized (children) {
+            return ImmutableList.copyOf(children);
+        }
+    }
+
+    @Override
+    public void setParent(Location newParent) {
+        setParent(newParent, true);
+    }
+    
+    public void setParent(Location newParent, boolean updateChildListParents) {
+        if (newParent == this) {
+            throw new IllegalArgumentException("Location cannot be its own 
parent: "+this);
+        }
+        if (newParent == parent.get()) {
+            return; // no-op; already have desired parent
+        }
+        
+        if (parent.get() != null) {
+            Location oldParent = parent.get();
+            parent.set(null);
+            if (updateChildListParents)
+                ((AbstractLocation)oldParent).removeChild(this);
+        }
+        // TODO Should we support a location changing parent? The resulting 
unmanage/manage might cause problems.
+        // The code above suggests we do, but maybe we should warn or throw 
error, or at least test it!
+        
+        parent.set(newParent);
+        if (newParent != null) {
+            if (updateChildListParents)
+                ((AbstractLocation)newParent).addChild(this);
+        }
+        
+        onChanged();
+    }
+
+    @Override
+    public ConfigurationSupportInternal config() {
+        return config ;
+    }
+
+    private class BasicConfigurationSupport implements 
ConfigurationSupportInternal {
+
+        @Override
+        public <T> T get(ConfigKey<T> key) {
+            if (hasConfig(key, false)) return getLocalBag().get(key);
+            if (getParent() != null && isInherited(key)) {
+                return getParent().getConfig(key);
+            }
+            
+            // In case this entity class has overridden the given key (e.g. to 
set default), then retrieve this entity's key
+            // TODO when locations become entities, the duplication of this 
compared to EntityConfigMap.getConfig will disappear.
+            @SuppressWarnings("unchecked")
+            ConfigKey<T> ownKey = (ConfigKey<T>) 
elvis(locationType.getConfigKey(key.getName()), key);
+
+            return ownKey.getDefaultValue();
+        }
+
+        @Override
+        public <T> T get(HasConfigKey<T> key) {
+            return get(key.getConfigKey());
+        }
+
+        @Override
+        public <T> T set(ConfigKey<T> key, T val) {
+            T result = configBag.put(key, val);
+            onChanged();
+            return result;
+        }
+
+        @Override
+        public <T> T set(HasConfigKey<T> key, T val) {
+            return set(key.getConfigKey(), val);
+        }
+
+        @Override
+        public <T> T set(ConfigKey<T> key, Task<T> val) {
+            // TODO Support for locations
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public <T> T set(HasConfigKey<T> key, Task<T> val) {
+            // TODO Support for locations
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public ConfigBag getBag() {
+            ConfigBag result = ConfigBag.newInstanceExtending(configBag, 
ImmutableMap.of());
+            Location p = getParent();
+            if (p!=null) 
result.putIfAbsent(((LocationInternal)p).config().getBag());
+            return result;
+        }
+
+        @Override
+        public ConfigBag getLocalBag() {
+            return configBag;
+        }
+
+        @Override
+        public Maybe<Object> getRaw(ConfigKey<?> key) {
+            if (hasConfig(key, false)) return 
Maybe.of(getLocalBag().getStringKey(key.getName()));
+            if (getParent() != null && isInherited(key)) return 
((LocationInternal)getParent()).config().getRaw(key);
+            return Maybe.absent();
+        }
+
+        @Override
+        public Maybe<Object> getRaw(HasConfigKey<?> key) {
+            return getRaw(key.getConfigKey());
+        }
+
+        @Override
+        public Maybe<Object> getLocalRaw(ConfigKey<?> key) {
+            if (hasConfig(key, false)) return 
Maybe.of(getLocalBag().getStringKey(key.getName()));
+            return Maybe.absent();
+        }
+
+        @Override
+        public Maybe<Object> getLocalRaw(HasConfigKey<?> key) {
+            return getLocalRaw(key.getConfigKey());
+        }
+
+        @Override
+        public void addToLocalBag(Map<String, ?> vals) {
+            configBag.putAll(vals);
+        }
+
+        @Override
+        public void removeFromLocalBag(String key) {
+            configBag.remove(key);
+        }
+
+        @Override
+        public void refreshInheritedConfig() {
+            // no-op for location
+        }
+        
+        @Override
+        public void refreshInheritedConfigOfChildren() {
+            // no-op for location
+        }
+        
+        private boolean hasConfig(ConfigKey<?> key, boolean includeInherited) {
+            if (includeInherited && isInherited(key)) {
+                return getBag().containsKey(key);
+            } else {
+                return getLocalBag().containsKey(key);
+            }
+        }
+        
+        private boolean isInherited(ConfigKey<?> key) {
+            ConfigInheritance inheritance = key.getInheritance();
+            if (inheritance==null) inheritance = getDefaultInheritance();
+            return inheritance.isInherited(key, getParent(), 
AbstractLocation.this);
+        }
+
+        private ConfigInheritance getDefaultInheritance() {
+            return ConfigInheritance.ALWAYS;
+        }
+    }
+    
+    @Override
+    public <T> T getConfig(HasConfigKey<T> key) {
+        return config().get(key);
+    }
+
+    @Override
+    public <T> T getConfig(ConfigKey<T> key) {
+        return config().get(key);
+    }
+
+    @Override
+    @Deprecated
+    public boolean hasConfig(ConfigKey<?> key, boolean includeInherited) {
+        return config.hasConfig(key, includeInherited);
+    }
+
+    @Override
+    @Deprecated
+    public Map<String,Object> getAllConfig(boolean includeInherited) {
+        // TODO Have no information about what to include/exclude inheritance 
wise.
+        // however few things use getAllConfigBag()
+        ConfigBag bag = (includeInherited ? config().getBag() : 
config().getLocalBag());
+        return bag.getAllConfig();
+    }
+    
+    @Override
+    @Deprecated
+    public ConfigBag getAllConfigBag() {
+        // TODO see comments in EntityConfigMap and on interface methods. 
+        // here ConfigBag is used exclusively so
+        // we have no information about what to include/exclude inheritance 
wise.
+        // however few things use getAllConfigBag()
+        return config().getBag();
+    }
+    
+    @Override
+    public ConfigBag getLocalConfigBag() {
+        return config().getLocalBag();
+    }
+
+    /** 
+     * @deprecated since 0.7; use {@link #getLocalConfigBag()}
+     * @since 0.6
+     */
+    @Deprecated
+    public ConfigBag getRawLocalConfigBag() {
+        return config().getLocalBag();
+    }
+    
+    @Override
+    @Deprecated
+    public <T> T setConfig(ConfigKey<T> key, T value) {
+        return config().set(key, value);
+    }
+
+    /**
+     * @since 0.6.0 (?) - use getDisplayName
+     * @deprecated since 0.7.0; use {@link #getDisplayName()}
+     */
+    @Deprecated
+    public void setName(String newName) {
+        setDisplayName(newName);
+    }
+
+    public void setDisplayName(String newName) {
+        name.set(newName);
+        displayNameAutoGenerated = false;
+        onChanged();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (! (o instanceof Location)) {
+            return false;
+        }
+
+        Location l = (Location) o;
+        return getId().equals(l.getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return getId().hashCode();
+    }
+
+    @Override
+    public boolean containsLocation(Location potentialDescendent) {
+        Location loc = potentialDescendent;
+        while (loc != null) {
+            if (this == loc) return true;
+            loc = loc.getParent();
+        }
+        return false;
+    }
+
+    protected <T extends Location> T addChild(LocationSpec<T> spec) {
+        T child = 
getManagementContext().getLocationManager().createLocation(spec);
+        addChild(child);
+        return child;
+    }
+    
+    @SuppressWarnings("deprecation")
+    public void addChild(Location child) {
+        // Previously, setParent delegated to addChildLocation and we 
sometimes ended up with
+        // duplicate entries here. Instead this now uses a similar scheme to 
+        // AbstractLocation.setParent/addChild (with any weaknesses for 
distribution that such a 
+        // scheme might have...).
+        // 
+        // We continue to use a list to allow identical-looking locations, but 
they must be different 
+        // instances.
+        
+        synchronized (children) {
+            for (Location contender : children) {
+                if (contender == child) {
+                    // don't re-add; no-op
+                    return;
+                }
+            }
+
+            children.add(child);
+        }
+        
+        if (isManaged()) {
+            if (!getManagementContext().getLocationManager().isManaged(child)) 
{
+                Locations.manage(child, getManagementContext());
+            }
+        } else if (getManagementContext() != null) {
+            if 
(((LocalLocationManager)getManagementContext().getLocationManager()).getLocationEvenIfPreManaged(child.getId())
 == null) {
+                
((ManagementContextInternal)getManagementContext()).prePreManage(child);
+            }
+        }
+
+        children.add(child);
+        child.setParent(this);
+        
+        onChanged();
+    }
+    
+    public boolean removeChild(Location child) {
+        boolean removed;
+        synchronized (children) {
+            removed = children.remove(child);
+        }
+        if (removed) {
+            if (child instanceof Closeable) {
+                Streams.closeQuietly((Closeable)child);
+            }
+            child.setParent(null);
+            
+            if (isManaged()) {
+                getManagementContext().getLocationManager().unmanage(child);
+            }
+        }
+        onChanged();
+        return removed;
+    }
+
+    protected void onChanged() {
+        // currently changes simply trigger re-persistence; there is no 
intermediate listener as we do for EntityChangeListener
+        if (isManaged()) {
+            
getManagementContext().getRebindManager().getChangeListener().onChanged(this);
+        }
+    }
+
+    /** Default String representation is simplified name of class, together 
with selected fields. */
+    @Override
+    public String toString() {
+        return string().toString();
+    }
+    
+    @Override
+    public String toVerboseString() {
+        return toString();
+    }
+
+    /** override this, adding to the returned value, to supply additional 
fields to include in the toString */
+    protected ToStringHelper string() {
+        return Objects.toStringHelper(getClass()).add("id", 
getId()).add("name", name);
+    }
+    
+    @Override
+    public HostGeoInfo getHostGeoInfo() { return hostGeoInfo.get(); }
+    
+    public void setHostGeoInfo(HostGeoInfo hostGeoInfo) {
+        if (hostGeoInfo!=null) { 
+            this.hostGeoInfo.set(hostGeoInfo);
+            setConfig(LocationConfigKeys.LATITUDE, hostGeoInfo.latitude); 
+            setConfig(LocationConfigKeys.LONGITUDE, hostGeoInfo.longitude); 
+        } 
+    }
+
+    @Override
+    public RebindSupport<LocationMemento> getRebindSupport() {
+        return new BasicLocationRebindSupport(this);
+    }
+    
+    @Override
+    public boolean hasExtension(Class<?> extensionType) {
+        return extensions.get().containsKey(checkNotNull(extensionType, 
"extensionType"));
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T getExtension(Class<T> extensionType) {
+        Object extension = extensions.get().get(checkNotNull(extensionType, 
"extensionType"));
+        if (extension == null) {
+            throw new IllegalArgumentException("No extension of type 
"+extensionType+" registered for location "+this);
+        }
+        return (T) extension;
+    }
+    
+    @Override
+    public <T> void addExtension(Class<T> extensionType, T extension) {
+        checkNotNull(extensionType, "extensionType");
+        checkNotNull(extension, "extension");
+        checkArgument(extensionType.isInstance(extension), "extension %s does 
not implement %s", extension, extensionType);
+        extensions.get().put(extensionType, extension);
+    }
+
+    @Override
+    public Map<String, String> toMetadataRecord() {
+        ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
+        if (getDisplayName() != null) builder.put("displayName", 
getDisplayName());
+        if (getParent() != null && getParent().getDisplayName() != null) {
+            builder.put("parentDisplayName", getParent().getDisplayName());
+        }
+        return builder.build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/AbstractLocationResolver.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/location/basic/AbstractLocationResolver.java
 
b/core/src/main/java/org/apache/brooklyn/location/basic/AbstractLocationResolver.java
new file mode 100644
index 0000000..59a0771
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/location/basic/AbstractLocationResolver.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.LocationRegistry;
+import org.apache.brooklyn.location.LocationResolver;
+import org.apache.brooklyn.location.LocationSpec;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import 
org.apache.brooklyn.location.basic.AbstractLocationResolver.SpecParser.ParsedSpec;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.text.KeyValueParser;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Examples of valid specs:
+ *   <ul>
+ *     <li>byon(hosts=myhost)
+ *     <li>byon(hosts=myhost,myhost2)
+ *     <li>byon(hosts="myhost, myhost2")
+ *     <li>byon(hosts=myhost,myhost2, name=abc)
+ *     <li>byon(hosts="myhost, myhost2", name="my location name")
+ *   </ul>
+ * 
+ * @author aled
+ */
+@SuppressWarnings({"unchecked","rawtypes"})
+public abstract class AbstractLocationResolver implements LocationResolver {
+
+    public static final Logger log = 
LoggerFactory.getLogger(AbstractLocationResolver.class);
+    
+    protected volatile ManagementContext managementContext;
+
+    protected volatile SpecParser specParser;
+
+    protected abstract Class<? extends Location> getLocationType();
+    
+    protected abstract SpecParser getSpecParser();
+    
+    @Override
+    public void init(ManagementContext managementContext) {
+        this.managementContext = checkNotNull(managementContext, 
"managementContext");
+        this.specParser = getSpecParser();
+    }
+    
+    @Override
+    public boolean accepts(String spec, LocationRegistry registry) {
+        return BasicLocationRegistry.isResolverPrefixForSpec(this, spec, true);
+    }
+
+    @Override
+    public Location newLocationFromString(Map locationFlags, String spec, 
LocationRegistry registry) {
+        ConfigBag config = extractConfig(locationFlags, spec, registry);
+        Map globalProperties = registry.getProperties();
+        String namedLocation = (String) 
locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName());
+        
+        if (registry != null) {
+            
LocationPropertiesFromBrooklynProperties.setLocalTempDir(globalProperties, 
config);
+        }
+
+        return 
managementContext.getLocationManager().createLocation(LocationSpec.create(getLocationType())
+            .configure(config.getAllConfig())
+            .configure(LocationConfigUtils.finalAndOriginalSpecs(spec, 
locationFlags, globalProperties, namedLocation)));
+    }
+
+    protected ConfigBag extractConfig(Map<?,?> locationFlags, String spec, 
LocationRegistry registry) {
+        Map globalProperties = registry.getProperties();
+        ParsedSpec parsedSpec = specParser.parse(spec);
+        String namedLocation = (String) 
locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName());
+        
+        // prefer args map over location flags
+        Map<String, Object> filteredProperties = 
getFilteredLocationProperties(getPrefix(), namedLocation, globalProperties);
+        ConfigBag flags = 
ConfigBag.newInstance(parsedSpec.argsMap).putIfAbsent(locationFlags).putIfAbsent(filteredProperties);
+
+        return flags;
+    }
+    
+    protected Map<String, Object> getFilteredLocationProperties(String 
provider, String namedLocation, Map<String, ?> globalProperties) {
+        return new 
LocationPropertiesFromBrooklynProperties().getLocationProperties(getPrefix(), 
namedLocation, globalProperties);
+    }
+    
+    /**
+     * Parses a spec, by default of the general form 
"prefix:parts1:part2(arg1=val1,arg2=val2)"
+     */
+    protected static class SpecParser {
+        
+        protected static class ParsedSpec {
+            public final String spec;
+            public final List<String> partsList;
+            public final Map<String,String> argsMap;
+            
+            ParsedSpec(String spec, List<String> partsList, Map<String,String> 
argsMap) {
+                this.spec = spec;
+                this.partsList = ImmutableList.copyOf(partsList);
+                this.argsMap = 
Collections.unmodifiableMap(MutableMap.copyOf(argsMap));
+            }
+        }
+        
+        protected final String prefix;
+        protected final Pattern pattern;
+        private String exampleUsage;
+        
+        public SpecParser(String prefix) {
+            this.prefix = prefix;
+            pattern = 
Pattern.compile("("+prefix+"|"+prefix.toLowerCase()+"|"+prefix.toUpperCase()+")"
 + "(:)?" + "(\\((.*)\\))?$");
+        }
+        
+        public SpecParser(String prefix, Pattern pattern) {
+            this.prefix = prefix;
+            this.pattern = pattern;
+        }
+        
+        public SpecParser setExampleUsage(String exampleUsage) {
+            this.exampleUsage = exampleUsage;
+            return this;
+        }
+
+        protected String getUsage(String spec) {
+            if (exampleUsage == null) {
+                return "Spec should be in the form "+pattern;
+            } else {
+                return "for example, "+exampleUsage;
+            }
+        }
+        
+        protected void checkParsedSpec(ParsedSpec parsedSpec) {
+            // If someone tries "byon:(),byon:()" as a single spec, we get 
weird key-values!
+            for (String key : parsedSpec.argsMap.keySet()) {
+                if (key.contains(":") || key.contains("{") || 
key.contains("}") || key.contains("(") || key.contains(")")) {
+                    throw new IllegalArgumentException("Invalid byon spec: 
"+parsedSpec.spec+" (key="+key+")");
+                }
+            }
+            String name = parsedSpec.argsMap.get("name");
+            if (parsedSpec.argsMap.containsKey("name") && (name == null || 
name.isEmpty())) {
+                throw new IllegalArgumentException("Invalid location 
'"+parsedSpec.spec+"'; if name supplied then value must be non-empty");
+            }
+            String displayName = parsedSpec.argsMap.get("displayName");
+            if (parsedSpec.argsMap.containsKey("displayName") && (displayName 
== null || displayName.isEmpty())) {
+                throw new IllegalArgumentException("Invalid location 
'"+parsedSpec.spec+"'; if displayName supplied then value must be non-empty");
+            }
+        }
+        
+        public ParsedSpec parse(String spec) {
+            Matcher matcher = pattern.matcher(spec);
+            if (!matcher.matches()) {
+                throw new IllegalArgumentException("Invalid location 
'"+spec+"'; "+getUsage(spec));
+            }
+            
+            String argsPart = matcher.group(3);
+            if (argsPart != null && argsPart.startsWith("(") && 
argsPart.endsWith(")")) {
+                // TODO Hacky; hosts("1.1.1.1") returns argsPart=("1.1.1.1")
+                argsPart = argsPart.substring(1, argsPart.length()-1);
+            }
+            Map<String, String> argsMap = KeyValueParser.parseMap(argsPart);
+            ParsedSpec result = new ParsedSpec(spec, 
ImmutableList.<String>of(), argsMap);
+            checkParsedSpec(result);
+            return result;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/AggregatingMachineProvisioningLocation.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/location/basic/AggregatingMachineProvisioningLocation.java
 
b/core/src/main/java/org/apache/brooklyn/location/basic/AggregatingMachineProvisioningLocation.java
new file mode 100644
index 0000000..33e8efa
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/location/basic/AggregatingMachineProvisioningLocation.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.Closeable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.location.MachineLocation;
+import org.apache.brooklyn.location.MachineProvisioningLocation;
+import org.apache.brooklyn.location.NoMachinesAvailableException;
+import brooklyn.util.flags.SetFromFlag;
+import brooklyn.util.stream.Streams;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * Takes a list of other provisioners, and round-robins across them when 
obtaining a machine.
+ */
+public class AggregatingMachineProvisioningLocation<T extends MachineLocation> 
extends AbstractLocation 
+        implements MachineProvisioningLocation<T>, Closeable {
+
+    private Object lock;
+    
+    @SetFromFlag
+    protected List<MachineProvisioningLocation<T>> provisioners;
+    
+    @SetFromFlag
+    protected Map<T, MachineProvisioningLocation<T>> inUse;
+
+    protected final AtomicInteger obtainCounter = new AtomicInteger();
+    
+    public AggregatingMachineProvisioningLocation() {
+        this(Maps.newLinkedHashMap());
+    }
+    
+    public AggregatingMachineProvisioningLocation(Map properties) {
+        super(properties);
+
+        if (isLegacyConstruction()) {
+            init();
+        }
+    }
+
+    @Override
+    public void init() {
+        super.init();
+    }
+    
+    @Override
+    public String toVerboseString() {
+        return Objects.toStringHelper(this).omitNullValues()
+                .add("id", getId()).add("name", getDisplayName())
+                .add("provisioners", provisioners)
+                .toString();
+    }
+
+    @Override
+    public AbstractLocation configure(Map<?,?> properties) {
+        if (lock == null) {
+            lock = new Object();
+            provisioners = 
Lists.<MachineProvisioningLocation<T>>newArrayList();
+            inUse = Maps.<T, MachineProvisioningLocation<T>>newLinkedHashMap();
+        }
+        return super.configure(properties);
+    }
+    
+    @Override
+    public AggregatingMachineProvisioningLocation<T> newSubLocation(Map<?,?> 
newFlags) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void close() {
+        for (MachineProvisioningLocation<?> provisioner : provisioners) {
+            if (provisioner instanceof Closeable) {
+                Streams.closeQuietly((Closeable)provisioner);
+            }
+        }
+    }
+    
+    public T obtain() throws NoMachinesAvailableException {
+        return obtain(Maps.<String,Object>newLinkedHashMap());
+    }
+    
+    @Override
+    public T obtain(Map<?,?> flags) throws NoMachinesAvailableException {
+        checkState(provisioners.size() > 0, "no provisioners!");
+        int index = obtainCounter.getAndIncrement();
+        for (int i = 0; i < provisioners.size(); i++) {
+            MachineProvisioningLocation<T> provisioner = 
provisioners.get(index++ % provisioners.size());
+            try {
+                T machine = provisioner.obtain(flags);
+                inUse.put(machine, provisioner);
+                return machine;
+            } catch (NoMachinesAvailableException e) {
+                // move on; try next
+            }
+        }
+        throw new NoMachinesAvailableException("No machines available in 
"+toString());
+    }
+
+    @Override
+    public void release(T machine) {
+        MachineProvisioningLocation<T> provisioner = inUse.remove(machine);
+        if (provisioner != null) {
+            provisioner.release(machine);
+        } else {
+            throw new IllegalStateException("Request to release machine 
"+machine+", but this machine is not currently allocated");
+        }
+    }
+
+    @Override
+    public Map<String,Object> getProvisioningFlags(Collection<String> tags) {
+        return Maps.<String,Object>newLinkedHashMap();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/BasicHardwareDetails.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/location/basic/BasicHardwareDetails.java
 
b/core/src/main/java/org/apache/brooklyn/location/basic/BasicHardwareDetails.java
new file mode 100644
index 0000000..fc90a68
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/location/basic/BasicHardwareDetails.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import javax.annotation.concurrent.Immutable;
+
+import com.google.common.base.Objects;
+
+import org.apache.brooklyn.location.HardwareDetails;
+
+@Immutable
+public class BasicHardwareDetails implements HardwareDetails {
+
+    private final Integer cpuCount;
+    private final Integer ram;
+
+    public BasicHardwareDetails(Integer cpuCount, Integer ram) {
+        this.cpuCount = cpuCount;
+        this.ram = ram;
+    }
+
+    @Override
+    public Integer getCpuCount() {
+        return cpuCount;
+    }
+
+    @Override
+    public Integer getRam() {
+        return ram;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(HardwareDetails.class)
+                .omitNullValues()
+                .add("cpuCount", cpuCount)
+                .add("ram", ram)
+                .toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/BasicLocationDefinition.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/location/basic/BasicLocationDefinition.java
 
b/core/src/main/java/org/apache/brooklyn/location/basic/BasicLocationDefinition.java
new file mode 100644
index 0000000..550ca12
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/location/basic/BasicLocationDefinition.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import java.util.Map;
+
+import org.apache.brooklyn.location.LocationDefinition;
+import brooklyn.util.text.Identifiers;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+
+public class BasicLocationDefinition implements LocationDefinition {
+
+    private final String id;
+    private final String name;
+    private final String spec;
+    private final Map<String,Object> config;
+
+    public BasicLocationDefinition(String name, String spec, Map<String,? 
extends Object> config) {
+        this(Identifiers.makeRandomId(8), name, spec, config);
+    }
+    
+    public BasicLocationDefinition(String id, String name, String spec, 
Map<String,? extends Object> config) {      
+        this.id = Preconditions.checkNotNull(id);
+        this.name = name;
+        this.spec = Preconditions.checkNotNull(spec);
+        this.config = config==null ? ImmutableMap.<String, Object>of() : 
ImmutableMap.<String, Object>copyOf(config);
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+    
+    public String getSpec() {
+        return spec;
+    }
+    
+    @Override
+    public Map<String, Object> getConfig() {
+        return config;
+    }
+    
+    @Override
+    public boolean equals(Object o) {
+        if (this==o) return true;
+        if ((o instanceof LocationDefinition) && 
id.equals(((LocationDefinition)o).getId())) return true;
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "LocationDefinition{" +
+                "id='" + getId() + '\'' +
+                ", name='" + getName() + '\'' +
+                ", spec='" + getSpec() + '\'' +
+                ", config=" + getConfig() +
+                '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/BasicLocationRegistry.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/location/basic/BasicLocationRegistry.java
 
b/core/src/main/java/org/apache/brooklyn/location/basic/BasicLocationRegistry.java
new file mode 100644
index 0000000..1c21e32
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/location/basic/BasicLocationRegistry.java
@@ -0,0 +1,480 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+import org.apache.brooklyn.location.Location;
+import org.apache.brooklyn.location.LocationDefinition;
+import org.apache.brooklyn.location.LocationRegistry;
+import org.apache.brooklyn.location.LocationResolver;
+import org.apache.brooklyn.location.LocationSpec;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.brooklyn.api.catalog.BrooklynCatalog;
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.management.ManagementContext;
+
+import brooklyn.catalog.CatalogPredicates;
+import brooklyn.config.ConfigMap;
+import brooklyn.config.ConfigPredicates;
+import brooklyn.config.ConfigUtils;
+import brooklyn.management.internal.LocalLocationManager;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.guava.Maybe.Absent;
+import brooklyn.util.javalang.JavaClassNames;
+import brooklyn.util.text.Identifiers;
+import brooklyn.util.text.StringEscapes.JavaStringEscapes;
+import brooklyn.util.text.WildcardGlobs;
+import brooklyn.util.text.WildcardGlobs.PhraseTreatment;
+
+import com.google.common.annotations.VisibleForTesting;
+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#addItems(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);
+
+    /**
+     * Splits a comma-separated list of locations (names or specs) into an 
explicit list.
+     * The splitting is very careful to handle commas embedded within specs, 
to split correctly.
+     */
+    public static List<String> expandCommaSeparateLocations(String locations) {
+        return WildcardGlobs.getGlobsAfterBraceExpansion("{"+locations+"}", 
false, PhraseTreatment.INTERIOR_NOT_EXPANDABLE, 
PhraseTreatment.INTERIOR_NOT_EXPANDABLE);
+        // don't do this, it tries to expand commas inside parentheses which 
is not good!
+//        
QuotedStringTokenizer.builder().addDelimiterChars(",").buildList((String)id);
+    }
+
+    private final ManagementContext mgmt;
+    /** map of defined locations by their ID */
+    private final Map<String,LocationDefinition> definedLocations = new 
LinkedHashMap<String, LocationDefinition>();
+
+    protected final Map<String,LocationResolver> resolvers = new 
LinkedHashMap<String, LocationResolver>();
+
+    private final Set<String> specsWarnedOnException = 
Sets.newConcurrentHashSet();
+
+    public BasicLocationRegistry(ManagementContext mgmt) {
+        this.mgmt = checkNotNull(mgmt, "mgmt");
+        findServices();
+        updateDefinedLocations();
+    }
+
+    protected void findServices() {
+        ServiceLoader<LocationResolver> loader = 
ServiceLoader.load(LocationResolver.class, mgmt.getCatalogClassLoader());
+        for (LocationResolver r: loader) {
+            registerResolver(r);
+        }
+        if (log.isDebugEnabled()) log.debug("Location resolvers are: 
"+resolvers);
+        if (resolvers.isEmpty()) log.warn("No location resolvers detected: is 
src/main/resources correctly included?");
+    }
+
+    /** Registers the given resolver, invoking {@link 
LocationResolver#init(ManagementContext)} on the argument
+     * and returning true, unless the argument indicates false for {@link 
LocationResolver.EnableableLocationResolver#isEnabled()} */
+    public boolean registerResolver(LocationResolver r) {
+        r.init(mgmt);
+        if (r instanceof LocationResolver.EnableableLocationResolver) {
+            if (!((LocationResolver.EnableableLocationResolver)r).isEnabled()) 
{
+                return false;
+            }
+        }
+        resolvers.put(r.getPrefix(), r);
+        return true;
+    }
+    
+    @Override
+    public Map<String,LocationDefinition> getDefinedLocations() {
+        synchronized (definedLocations) {
+            return 
ImmutableMap.<String,LocationDefinition>copyOf(definedLocations);
+        }
+    }
+    
+    @Override
+    public LocationDefinition getDefinedLocationById(String id) {
+        return definedLocations.get(id);
+    }
+
+    @Override
+    public LocationDefinition getDefinedLocationByName(String name) {
+        synchronized (definedLocations) {
+            for (LocationDefinition l: definedLocations.values()) {
+                if (l.getName().equals(name)) return l;
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public void updateDefinedLocation(LocationDefinition l) {
+        synchronized (definedLocations) { 
+            definedLocations.put(l.getId(), l); 
+        }
+    }
+
+    /**
+     * 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;
+        synchronized (definedLocations) { 
+            removed = definedLocations.remove(id);
+        }
+        if (removed == null && log.isDebugEnabled()) {
+            log.debug("{} was asked to remove location with id {} but no such 
location was registered", this, id);
+        }
+    }
+    
+    public void updateDefinedLocations() {
+        synchronized (definedLocations) {
+            // first read all properties starting  brooklyn.location.named.xxx
+            // (would be nice to move to a better way, e.g. yaml, then 
deprecate this approach, but first
+            // we need ability/format for persisting named locations, and 
better support for adding+saving via REST/GUI)
+            int count = 0; 
+            String NAMED_LOCATION_PREFIX = "brooklyn.location.named.";
+            ConfigMap namedLocationProps = 
mgmt.getConfig().submap(ConfigPredicates.startingWith(NAMED_LOCATION_PREFIX));
+            for (String k: namedLocationProps.asMapWithStringKeys().keySet()) {
+                String name = k.substring(NAMED_LOCATION_PREFIX.length());
+                // If has a dot, then is a sub-property of a named location 
(e.g. brooklyn.location.named.prod1.user=bob)
+                if (!name.contains(".")) {
+                    // this is a new named location
+                    String spec = (String) 
namedLocationProps.asMapWithStringKeys().get(k);
+                    // make up an ID
+                    String id = Identifiers.makeRandomId(8);
+                    Map<String, Object> config = 
ConfigUtils.filterForPrefixAndStrip(namedLocationProps.asMapWithStringKeys(), 
k+".");
+                    definedLocations.put(id, new BasicLocationDefinition(id, 
name, spec, config));
+                    count++;
+                }
+            }
+            if (log.isDebugEnabled())
+                log.debug("Found "+count+" defined locations from properties 
(*.named.* syntax): "+definedLocations.values());
+            if (getDefinedLocationByName("localhost")==null && 
!BasicOsDetails.Factory.newLocalhostInstance().isWindows()
+                    && LocationConfigUtils.isEnabled(mgmt, 
"brooklyn.location.localhost")) {
+                log.debug("Adding a defined location for localhost");
+                // add 'localhost' *first*
+                ImmutableMap<String, LocationDefinition> oldDefined = 
ImmutableMap.copyOf(definedLocations);
+                definedLocations.clear();
+                String id = Identifiers.makeRandomId(8);
+                definedLocations.put(id, localhost(id));
+                definedLocations.putAll(oldDefined);
+            }
+            
+            for (CatalogItem<Location, LocationSpec<?>> item : 
mgmt.getCatalog().getCatalogItems(CatalogPredicates.IS_LOCATION)) {
+                updateDefinedLocation(item);
+                count++;
+            }
+        }
+    }
+    
+    @VisibleForTesting
+    void disablePersistence() {
+        // persistence isn't enabled yet anyway (have to manually save things,
+        // defining the format and file etc)
+    }
+
+    protected static BasicLocationDefinition localhost(String id) {
+        return new BasicLocationDefinition(id, "localhost", "localhost", null);
+    }
+    
+    /** to catch circular references */
+    protected ThreadLocal<Set<String>> specsSeen = new 
ThreadLocal<Set<String>>();
+    
+    @Override @Deprecated
+    public boolean canMaybeResolve(String spec) {
+        return getSpecResolver(spec) != null;
+    }
+
+    @Override
+    public final Location resolve(String spec) {
+        return resolve(spec, true, null).get();
+    }
+    
+    @Override @Deprecated
+    public final Location resolveIfPossible(String spec) {
+        if (!canMaybeResolve(spec)) return null;
+        return resolve(spec, null, null).orNull();
+    }
+    
+    @Deprecated /** since 0.7.0 not used */
+    public final Maybe<Location> resolve(String spec, boolean manage) {
+        return resolve(spec, manage, null);
+    }
+    
+    public Maybe<Location> resolve(String spec, Boolean manage, Map 
locationFlags) {
+        try {
+            locationFlags = MutableMap.copyOf(locationFlags);
+            if (manage!=null) {
+                locationFlags.put(LocalLocationManager.CREATE_UNMANAGED, 
!manage);
+            }
+            
+            Set<String> seenSoFar = specsSeen.get();
+            if (seenSoFar==null) {
+                seenSoFar = new LinkedHashSet<String>();
+                specsSeen.set(seenSoFar);
+            }
+            if (seenSoFar.contains(spec))
+                return Maybe.absent(Suppliers.ofInstance(new 
IllegalStateException("Circular reference in definition of location '"+spec+"' 
("+seenSoFar+")")));
+            seenSoFar.add(spec);
+            
+            LocationResolver resolver = getSpecResolver(spec);
+
+            if (resolver != null) {
+                try {
+                    return 
Maybe.of(resolver.newLocationFromString(locationFlags, spec, this));
+                } catch (RuntimeException e) {
+                    return Maybe.absent(Suppliers.ofInstance(e));
+                }
+            }
+
+            // problem: but let's ensure that classpath is sane to give better 
errors in common IDE bogus case;
+            // and avoid repeated logging
+            String errmsg;
+            if (spec == null || specsWarnedOnException.add(spec)) {
+                if (resolvers.get("id")==null || resolvers.get("named")==null) 
{
+                    log.error("Standard location resolvers not installed, 
location resolution will fail shortly. "
+                            + "This usually indicates a classpath problem, 
such as when running from an IDE which "
+                            + "has not properly copied META-INF/services from 
src/main/resources. "
+                            + "Known resolvers are: "+resolvers.keySet());
+                    errmsg = "Unresolvable location '"+spec+"': "
+                            + "Problem detected with location resolver 
configuration; "
+                            + resolvers.keySet()+" are the only available 
location resolvers. "
+                            + "More information can be found in the logs.";
+                } else {
+                    log.debug("Location resolution failed for '"+spec+"' (if 
this is being loaded it will fail shortly): known resolvers are: 
"+resolvers.keySet());
+                    errmsg = "Unknown location '"+spec+"': "
+                            + "either this location is not recognised or there 
is a problem with location resolver configuration.";
+                }
+            } else {
+                // For helpful log message construction: assumes classpath 
will not suddenly become wrong; might happen with OSGi though!
+                if (log.isDebugEnabled()) log.debug("Location resolution 
failed again for '"+spec+"' (throwing)");
+                errmsg = "Unknown location '"+spec+"': "
+                        + "either this location is not recognised or there is 
a problem with location resolver configuration.";
+            }
+
+            return Maybe.absent(Suppliers.ofInstance(new 
NoSuchElementException(errmsg)));
+
+        } finally {
+            specsSeen.remove();
+        }
+    }
+
+    @Override
+    public final Location resolve(String spec, Map locationFlags) {
+        return resolve(spec, null, locationFlags).get();
+    }
+
+    protected LocationResolver getSpecResolver(String spec) {
+        int colonIndex = spec.indexOf(':');
+        int bracketIndex = spec.indexOf("(");
+        int dividerIndex = (colonIndex < 0) ? bracketIndex : (bracketIndex < 0 
? colonIndex : Math.min(bracketIndex, colonIndex));
+        String prefix = dividerIndex >= 0 ? spec.substring(0, dividerIndex) : 
spec;
+        LocationResolver resolver = resolvers.get(prefix);
+       
+        if (resolver == null)
+            resolver = getSpecDefaultResolver(spec);
+        
+        return resolver;
+    }
+    
+    protected LocationResolver getSpecDefaultResolver(String spec) {
+        return getSpecFirstResolver(spec, "id", "named", "jclouds");
+    }
+    protected LocationResolver getSpecFirstResolver(String spec, String 
...resolversToCheck) {
+        for (String resolverId: resolversToCheck) {
+            LocationResolver resolver = resolvers.get(resolverId);
+            if (resolver!=null && resolver.accepts(spec, this))
+                return resolver;
+        }
+        return null;
+    }
+
+    /** providers default impl for RegistryLocationResolver.accepts */
+    public static boolean isResolverPrefixForSpec(LocationResolver resolver, 
String spec, boolean argumentRequired) {
+        if (spec==null) return false;
+        if (spec.startsWith(resolver.getPrefix()+":")) return true;
+        if (!argumentRequired && spec.equals(resolver.getPrefix())) return 
true;
+        return false;
+    }
+
+    @Override
+    public List<Location> resolve(Iterable<?> spec) {
+        List<Location> result = new ArrayList<Location>();
+        for (Object id : spec) {
+            if (id instanceof String) {
+                result.add(resolve((String) id));
+            } else if (id instanceof Location) {
+                result.add((Location) id);
+            } else {
+                if (id instanceof Iterable)
+                    throw new IllegalArgumentException("Cannot resolve 
'"+id+"' to a location; collections of collections not allowed"); 
+                throw new IllegalArgumentException("Cannot resolve '"+id+"' to 
a location; unsupported type "+
+                        (id == null ? "null" : id.getClass().getName())); 
+            }
+        }
+        return result;
+    }
+    
+    public List<Location> resolveList(Object l) {
+        if (l==null) l = Collections.emptyList();
+        if (l instanceof String) l = 
JavaStringEscapes.unwrapJsonishListIfPossible((String)l);
+        if (l instanceof Iterable) return resolve((Iterable<?>)l);
+        throw new IllegalArgumentException("Location list must be supplied as 
a collection or a string, not "+
+            JavaClassNames.simpleClassName(l)+"/"+l);
+    }
+    
+    @Override
+    public Location resolve(LocationDefinition ld) {
+        return resolve(ld, null, null).get();
+    }
+
+    @Override @Deprecated
+    public Location resolveForPeeking(LocationDefinition ld) {
+        // TODO should clean up how locations are stored, figuring out whether 
they are shared or not;
+        // or maybe better, the API calls to this might just want to get the 
LocationSpec objects back
+        
+        // for now we use a 'CREATE_UNMANGED' flag to prevent management 
(leaks and logging)
+        return resolve(ld, 
ConfigBag.newInstance().configure(LocalLocationManager.CREATE_UNMANAGED, 
true).getAllConfig());
+    }
+
+    @Override @Deprecated
+    public Location resolve(LocationDefinition ld, Map<?,?> flags) {
+        return resolveLocationDefinition(ld, flags, null);
+    }
+    
+    /** @deprecated since 0.7.0 not used (and optionalName was ignored anyway) 
*/
+    @Deprecated
+    public Location resolveLocationDefinition(LocationDefinition ld, Map 
locationFlags, String optionalName) {
+        return resolve(ld, null, locationFlags).get();
+    }
+    
+    public Maybe<Location> resolve(LocationDefinition ld, Boolean manage, Map 
locationFlags) {
+        ConfigBag newLocationFlags = ConfigBag.newInstance(ld.getConfig())
+            .putAll(locationFlags)
+            .putIfAbsentAndNotNull(LocationInternal.NAMED_SPEC_NAME, 
ld.getName())
+            .putIfAbsentAndNotNull(LocationInternal.ORIGINAL_SPEC, 
ld.getName());
+        Maybe<Location> result = resolve(ld.getSpec(), manage, 
newLocationFlags.getAllConfigRaw());
+        if (result.isPresent()) 
+            return result;
+        throw new IllegalStateException("Cannot instantiate location '"+ld+"' 
pointing at "+ld.getSpec()+": "+
+            Exceptions.collapseText( ((Absent<?>)result).getException() ));
+    }
+
+    @Override
+    public Map getProperties() {
+        return mgmt.getConfig().asMapWithStringKeys();
+    }
+
+    @VisibleForTesting
+    public static void setupLocationRegistryForTesting(ManagementContext mgmt) 
{
+        // ensure localhost is added (even on windows)
+        LocationDefinition l = 
mgmt.getLocationRegistry().getDefinedLocationByName("localhost");
+        if (l==null) mgmt.getLocationRegistry().updateDefinedLocation(
+                BasicLocationRegistry.localhost(Identifiers.makeRandomId(8)) );
+        
+        
((BasicLocationRegistry)mgmt.getLocationRegistry()).disablePersistence();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/BasicMachineDetails.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/location/basic/BasicMachineDetails.java
 
b/core/src/main/java/org/apache/brooklyn/location/basic/BasicMachineDetails.java
new file mode 100644
index 0000000..e4b4575
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/location/basic/BasicMachineDetails.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+
+import org.apache.brooklyn.api.management.Task;
+import org.apache.brooklyn.location.MachineDetails;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.brooklyn.location.HardwareDetails;
+import org.apache.brooklyn.location.OsDetails;
+import brooklyn.util.ResourceUtils;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.task.DynamicTasks;
+import brooklyn.util.task.TaskTags;
+import brooklyn.util.task.ssh.internal.PlainSshExecTaskFactory;
+import brooklyn.util.task.system.ProcessTaskWrapper;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Splitter;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Maps;
+import com.google.common.io.CharStreams;
+
+@Immutable
+public class BasicMachineDetails implements MachineDetails {
+
+    public static final Logger LOG = 
LoggerFactory.getLogger(BasicMachineDetails.class);
+
+    private final HardwareDetails hardwareDetails;
+    private final OsDetails osDetails;
+
+    public BasicMachineDetails(HardwareDetails hardwareDetails, OsDetails 
osDetails) {
+        this.hardwareDetails = checkNotNull(hardwareDetails, 
"hardwareDetails");
+        this.osDetails = checkNotNull(osDetails, "osDetails");
+    }
+
+    @Nonnull
+    @Override
+    public HardwareDetails getHardwareDetails() {
+        return hardwareDetails;
+    }
+
+    @Nonnull
+    @Override
+    public OsDetails getOsDetails() {
+        return osDetails;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(MachineDetails.class)
+                .add("os", osDetails)
+                .add("hardware", hardwareDetails)
+                .toString();
+    }
+
+    /**
+     * Creates a MachineDetails for the given location by SSHing to the 
machine and
+     * running a Bash script to gather data. Should only be called from within 
a
+     * task context. If this might not be the case then use {@link
+     * #taskForSshMachineLocation(SshMachineLocation)} instead.
+     */
+    static BasicMachineDetails forSshMachineLocation(SshMachineLocation 
location) {
+        return 
TaskTags.markInessential(DynamicTasks.queueIfPossible(taskForSshMachineLocation(location))
+                .orSubmitAsync()
+                .asTask())
+                .getUnchecked();
+    }
+
+    /**
+     * @return A task that gathers machine details by SSHing to the machine 
and running
+     *         a Bash script to gather data.
+     */
+    static Task<BasicMachineDetails> 
taskForSshMachineLocation(SshMachineLocation location) {
+        BufferedReader reader = new BufferedReader(Streams.reader(
+                new 
ResourceUtils(BasicMachineDetails.class).getResourceFromUrl(
+                        
"classpath://org/apache/brooklyn/location/basic/os-details.sh")));
+        List<String> script;
+        try {
+            script = CharStreams.readLines(reader);
+        } catch (IOException e) {
+            LOG.error("Error reading os-details script", e);
+            throw Throwables.propagate(e);
+        } finally {
+            try {
+                reader.close();
+            } catch (IOException e) {
+                // Not rethrowing e because it might obscure an exception 
caught by the first catch
+                LOG.error("Error closing os-details script reader", e);
+            }
+        }
+        Task<BasicMachineDetails> task = new 
PlainSshExecTaskFactory<String>(location, script)
+                .summary("Getting machine details for: " + location)
+                .requiringZeroAndReturningStdout()
+                .returning(taskToMachineDetailsFunction(location))
+                .newTask()
+                .asTask();
+
+        return task;
+    }
+
+    private static Function<ProcessTaskWrapper<?>, BasicMachineDetails> 
taskToMachineDetailsFunction(final SshMachineLocation location) {
+        return new Function<ProcessTaskWrapper<?>, BasicMachineDetails>() {
+            @Override
+            public BasicMachineDetails apply(ProcessTaskWrapper<?> input) {
+                if (input.getExitCode() != 0) {
+                    LOG.warn("Non-zero exit code when fetching machine details 
for {}; guessing anonymous linux", location);
+                    return new BasicMachineDetails(new 
BasicHardwareDetails(null, null),
+                            BasicOsDetails.Factory.ANONYMOUS_LINUX);
+                }
+
+                String stdout = input.getStdout();
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Found following details at {}: {}", location, 
stdout);
+                }
+
+                Map<String,String> details = 
Maps.newHashMap(Splitter.on(CharMatcher.anyOf("\r\n"))
+                        .omitEmptyStrings()
+                        .withKeyValueSeparator(":")
+                        .split(stdout));
+
+                String name = details.remove("name");
+                String version = details.remove("version");
+                String architecture = details.remove("architecture");
+                Integer ram = intOrNull(details, "ram");
+                Integer cpuCount = intOrNull(details, "cpus");
+                if (!details.isEmpty()) {
+                    LOG.debug("Unused keys from os-details script: " + 
Joiner.on(", ").join(details.keySet()));
+                }
+
+                OsDetails osDetails = new BasicOsDetails(name, architecture, 
version);
+                HardwareDetails hardwareDetails = new 
BasicHardwareDetails(cpuCount, ram);
+                BasicMachineDetails machineDetails = new 
BasicMachineDetails(hardwareDetails, osDetails);
+
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Machine details for {}: {}", location, 
machineDetails);
+
+                return machineDetails;
+            }
+
+            private Integer intOrNull(Map<String, String> details, String key) 
{
+                try {
+                    return Integer.valueOf(details.remove(key));
+                } catch (NumberFormatException e) {
+                    return null;
+                }
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/BasicMachineMetadata.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/location/basic/BasicMachineMetadata.java
 
b/core/src/main/java/org/apache/brooklyn/location/basic/BasicMachineMetadata.java
new file mode 100644
index 0000000..ff1c0af
--- /dev/null
+++ 
b/core/src/main/java/org/apache/brooklyn/location/basic/BasicMachineMetadata.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import com.google.common.base.Objects;
+import org.apache.brooklyn.location.MachineManagementMixins;
+
+public class BasicMachineMetadata implements 
MachineManagementMixins.MachineMetadata {
+
+    final String id, name, primaryIp;
+    final Boolean isRunning;
+    final Object originalMetadata;
+    
+    public BasicMachineMetadata(String id, String name, String primaryIp, 
Boolean isRunning, Object originalMetadata) {
+        super();
+        this.id = id;
+        this.name = name;
+        this.primaryIp = primaryIp;
+        this.isRunning = isRunning;
+        this.originalMetadata = originalMetadata;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getPrimaryIp() {
+        return primaryIp;
+    }
+
+    public Boolean isRunning() {
+        return isRunning;
+    }
+
+    public Object getOriginalMetadata() {
+        return originalMetadata;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(id, isRunning, name, originalMetadata, 
primaryIp);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        BasicMachineMetadata other = (BasicMachineMetadata) obj;
+        if (!Objects.equal(id, other.id)) return false;
+        if (!Objects.equal(name, other.name)) return false;
+        if (!Objects.equal(primaryIp, other.primaryIp)) return false;
+        if (!Objects.equal(isRunning, other.isRunning)) return false;
+        if (!Objects.equal(originalMetadata, other.originalMetadata)) return 
false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this).add("id", id).add("name", 
name).add("originalMetadata", originalMetadata).toString();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e2c57058/core/src/main/java/org/apache/brooklyn/location/basic/BasicOsDetails.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/location/basic/BasicOsDetails.java 
b/core/src/main/java/org/apache/brooklyn/location/basic/BasicOsDetails.java
new file mode 100644
index 0000000..364e5f3
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/location/basic/BasicOsDetails.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.location.basic;
+
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+import com.google.common.base.Objects;
+
+import org.apache.brooklyn.location.OsDetails;
+
+@Immutable
+public class BasicOsDetails implements OsDetails {
+
+    final String name, arch, version;
+    final boolean is64bit;
+    // (?i) forces matches to be case insensitive
+    public static final String UNIX_OS_NAME_PATTERNS = 
"(?i).*linux.*|centos|debian|fedora|gentoo|rhel|slackware|solaris|suse|ubuntu|coreos";
+
+    /** Sets is64Bit according to value of arch parameter. */
+    public BasicOsDetails(String name, String arch, String version) {
+       this(name, arch, version, arch != null && arch.contains("64"));
+    }
+
+    public BasicOsDetails(String name, String arch, String version, boolean 
is64Bit) {
+        this.name = name; this.arch = arch; this.version = version; 
this.is64bit = is64Bit;
+    }
+    
+    // TODO: Should be replaced with an enum like Jclouds' OsFamily and isX 
methods should
+    // switch against known cases
+    @Nullable
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Nullable
+    @Override
+    public String getArch() {
+        return arch;
+    }
+
+    @Nullable
+    @Override
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public boolean isWindows() {
+        //TODO confirm
+        return getName()!=null && 
getName().toLowerCase().contains("microsoft");
+    }
+
+    @Override
+    public boolean isLinux() {
+        return getName() != null && Pattern.matches(UNIX_OS_NAME_PATTERNS, 
getName());
+    }
+
+    @Override
+    public boolean isMac() {
+        return getName()!=null && getName().equals(OsNames.MAC_OS_X);
+    }
+
+    @Override
+    public boolean is64bit() {
+        return is64bit;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(OsDetails.class)
+                .omitNullValues()
+                .add("name", name)
+                .add("version", version)
+                .add("arch", arch)
+                .toString();
+    }
+
+    public static class OsNames {
+        public static final String MAC_OS_X = "Mac OS X";
+    }
+    
+    public static class OsArchs {
+        public static final String X_86_64 = "x86_64";
+//        public static final String X_86 = "x86";
+//        // is this standard?  or do we ever need the above?
+        public static final String I386 = "i386";
+    }
+
+    public static class OsVersions {
+        public static final String MAC_10_8 = "10.8";
+        public static final String MAC_10_9 = "10.9";
+    }
+    
+    public static class Factory {
+        public static OsDetails newLocalhostInstance() {
+            return new BasicOsDetails(System.getProperty("os.name"), 
System.getProperty("os.arch"), System.getProperty("os.version"));
+        }
+        
+        public static final OsDetails ANONYMOUS_LINUX = new 
BasicOsDetails("linux", OsArchs.I386, "unknown");
+        public static final OsDetails ANONYMOUS_LINUX_64 = new 
BasicOsDetails("linux", OsArchs.X_86_64, "unknown");
+    }
+    
+}

Reply via email to