You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/15 15:33:32 UTC

[30/33] incubator-brooklyn git commit: [BROOKLYN-162] Refactor package in ./core/catalog

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6caee589/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
new file mode 100644
index 0000000..0e8ec80
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/BasicBrooklynCatalog.java
@@ -0,0 +1,1315 @@
+/*
+ * 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.core.catalog.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import io.brooklyn.camp.CampPlatform;
+import io.brooklyn.camp.spi.AssemblyTemplate;
+import io.brooklyn.camp.spi.instantiate.AssemblyTemplateInstantiator;
+import io.brooklyn.camp.spi.pdp.DeploymentPlan;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.yaml.snakeyaml.Yaml;
+
+import brooklyn.basic.BrooklynObjectInternal.ConfigurationSupportInternal;
+import brooklyn.camp.brooklyn.api.AssemblyTemplateSpecInstantiator;
+
+import org.apache.brooklyn.api.basic.AbstractBrooklynObjectSpec;
+import org.apache.brooklyn.api.catalog.BrooklynCatalog;
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle;
+import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.management.ManagementContext;
+import org.apache.brooklyn.api.management.classloading.BrooklynClassLoadingContext;
+import org.apache.brooklyn.api.policy.Policy;
+import org.apache.brooklyn.api.policy.PolicySpec;
+import org.apache.brooklyn.core.catalog.CatalogPredicates;
+import org.apache.brooklyn.core.catalog.internal.CatalogClasspathDo.CatalogScanningModes;
+import org.apache.brooklyn.core.management.internal.ManagementContextInternal;
+
+import brooklyn.config.BrooklynServerConfig;
+
+import org.apache.brooklyn.location.basic.BasicLocationRegistry;
+
+import brooklyn.util.collections.MutableList;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.javalang.AggregateClassLoader;
+import brooklyn.util.javalang.LoadedClassLoader;
+import brooklyn.util.javalang.Reflections;
+import brooklyn.util.stream.Streams;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+import brooklyn.util.yaml.Yamls;
+import brooklyn.util.yaml.Yamls.YamlExtract;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Iterables;
+
+/* TODO the complex tree-structured catalogs are only useful when we are relying on those separate catalog classloaders
+ * to isolate classpaths. with osgi everything is just put into the "manual additions" catalog. */
+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);
+
+    public static class BrooklynLoaderTracker {
+        public static final ThreadLocal<BrooklynClassLoadingContext> loader = new ThreadLocal<BrooklynClassLoadingContext>();
+        
+        public static void setLoader(BrooklynClassLoadingContext val) {
+            loader.set(val);
+        }
+        
+        // TODO Stack, for recursive calls?
+        public static void unsetLoader(BrooklynClassLoadingContext val) {
+            loader.set(null);
+        }
+        
+        public static BrooklynClassLoadingContext getLoader() {
+            return loader.get();
+        }
+    }
+
+    private final ManagementContext mgmt;
+    private CatalogDo catalog;
+    private volatile CatalogDo manualAdditionsCatalog;
+    private volatile LoadedClassLoader manualAdditionsClasses;
+    private final AggregateClassLoader rootClassLoader = AggregateClassLoader.newInstanceWithNoLoaders();
+
+    public BasicBrooklynCatalog(ManagementContext mgmt) {
+        this(mgmt, CatalogDto.newNamedInstance("empty catalog", "empty catalog", "empty catalog, expected to be reset later"));
+    }
+
+    public BasicBrooklynCatalog(ManagementContext mgmt, CatalogDto dto) {
+        this.mgmt = checkNotNull(mgmt, "managementContext");
+        this.catalog = new CatalogDo(mgmt, dto);
+    }
+
+    public boolean blockIfNotLoaded(Duration timeout) {
+        try {
+            return getCatalog().blockIfNotLoaded(timeout);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+    
+    public void reset(CatalogDto dto) {
+        reset(dto, true);
+    }
+
+    public void reset(CatalogDto dto, boolean failOnLoadError) {
+        // Unregister all existing persisted items.
+        for (CatalogItem<?, ?> toRemove : getCatalogItems()) {
+            if (log.isTraceEnabled()) {
+                log.trace("Scheduling item for persistence removal: {}", toRemove.getId());
+            }
+            mgmt.getRebindManager().getChangeListener().onUnmanaged(toRemove);
+        }
+        CatalogDo catalog = new CatalogDo(mgmt, dto);
+        CatalogUtils.logDebugOrTraceIfRebinding(log, "Resetting "+this+" catalog to "+dto);
+        catalog.load(mgmt, null, failOnLoadError);
+        CatalogUtils.logDebugOrTraceIfRebinding(log, "Reloaded catalog for "+this+", now switching");
+        this.catalog = catalog;
+        resetRootClassLoader();
+        this.manualAdditionsCatalog = null;
+
+        // Inject management context into and persist all the new entries.
+        for (CatalogItem<?, ?> entry : getCatalogItems()) {
+            boolean setManagementContext = false;
+            if (entry instanceof CatalogItemDo) {
+                CatalogItemDo<?, ?> cid = CatalogItemDo.class.cast(entry);
+                if (cid.getDto() instanceof CatalogItemDtoAbstract) {
+                    CatalogItemDtoAbstract<?, ?> cdto = CatalogItemDtoAbstract.class.cast(cid.getDto());
+                    if (cdto.getManagementContext() == null) {
+                        cdto.setManagementContext((ManagementContextInternal) mgmt);
+                    }
+                    setManagementContext = true;
+                }
+            }
+            if (!setManagementContext) {
+                log.warn("Can't set management context on entry with unexpected type in catalog. type={}, " +
+                        "expected={}", entry, CatalogItemDo.class);
+            }
+            if (log.isTraceEnabled()) {
+                log.trace("Scheduling item for persistence addition: {}", entry.getId());
+            }
+            mgmt.getRebindManager().getChangeListener().onManaged(entry);
+        }
+
+    }
+
+    /**
+     * Resets the catalog to the given entries
+     */
+    @Override
+    public void reset(Collection<CatalogItem<?, ?>> entries) {
+        CatalogDto newDto = CatalogDto.newDtoFromCatalogItems(entries, "explicit-catalog-reset");
+        reset(newDto);
+    }
+    
+    public CatalogDo getCatalog() {
+        return catalog;
+    }
+
+    protected CatalogItemDo<?,?> getCatalogItemDo(String symbolicName, String version) {
+        String fixedVersionId = getFixedVersionId(symbolicName, version);
+        if (fixedVersionId == null) {
+            //no items with symbolicName exist
+            return null;
+        }
+
+        return catalog.getIdCache().get( CatalogUtils.getVersionedId(symbolicName, fixedVersionId) );
+    }
+    
+    private String getFixedVersionId(String symbolicName, String version) {
+        if (!DEFAULT_VERSION.equals(version)) {
+            return version;
+        } else {
+            return getDefaultVersion(symbolicName);
+        }
+    }
+
+    private String getDefaultVersion(String symbolicName) {
+        Iterable<CatalogItem<Object, Object>> versions = getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName)));
+        Collection<CatalogItem<Object, Object>> orderedVersions = sortVersionsDesc(versions);
+        if (!orderedVersions.isEmpty()) {
+            return orderedVersions.iterator().next().getVersion();
+        } else {
+            return null;
+        }
+    }
+
+    private <T,SpecT> Collection<CatalogItem<T,SpecT>> sortVersionsDesc(Iterable<CatalogItem<T,SpecT>> versions) {
+        return ImmutableSortedSet.orderedBy(CatalogItemComparator.<T,SpecT>getInstance()).addAll(versions).build();
+    }
+
+    @Override
+    @Deprecated
+    public CatalogItem<?,?> getCatalogItem(String symbolicName) {
+        return getCatalogItem(symbolicName, DEFAULT_VERSION);
+    }
+    
+    @Override
+    public CatalogItem<?,?> getCatalogItem(String symbolicName, String version) {
+        if (symbolicName == null) return null;
+        checkNotNull(version, "version");
+        CatalogItemDo<?, ?> itemDo = getCatalogItemDo(symbolicName, version);
+        if (itemDo == null) return null;
+        return itemDo.getDto();
+    }
+    
+    @Override
+    @Deprecated
+    public void deleteCatalogItem(String id) {
+        //Delete only if installed through the
+        //deprecated methods. Don't support DEFAULT_VERSION for delete.
+        deleteCatalogItem(id, NO_VERSION);
+    }
+
+    @Override
+    public void deleteCatalogItem(String symbolicName, String version) {
+        log.debug("Deleting manual catalog item from "+mgmt+": "+symbolicName + ":" + version);
+        checkNotNull(symbolicName, "id");
+        checkNotNull(version, "version");
+        if (DEFAULT_VERSION.equals(version)) {
+            throw new IllegalStateException("Deleting items with unspecified version (argument DEFAULT_VERSION) not supported.");
+        }
+        CatalogItem<?, ?> item = getCatalogItem(symbolicName, version);
+        CatalogItemDtoAbstract<?,?> itemDto = getAbstractCatalogItem(item);
+        if (itemDto == null) {
+            throw new NoSuchElementException("No catalog item found with id "+symbolicName);
+        }
+        if (manualAdditionsCatalog==null) loadManualAdditionsCatalog();
+        manualAdditionsCatalog.deleteEntry(itemDto);
+        
+        // Ensure the cache is de-populated
+        getCatalog().deleteEntry(itemDto);
+
+        // And indicate to the management context that it should be removed.
+        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);
+
+    }
+
+    @Override
+    @Deprecated
+    public <T,SpecT> CatalogItem<T,SpecT> getCatalogItem(Class<T> type, String id) {
+        return getCatalogItem(type, id, DEFAULT_VERSION);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T,SpecT> CatalogItem<T,SpecT> getCatalogItem(Class<T> type, String id, String version) {
+        if (id==null || version==null) return null;
+        CatalogItem<?,?> result = getCatalogItem(id, version);
+        if (result==null) return null;
+        if (type==null || type.isAssignableFrom(result.getCatalogItemJavaType())) 
+            return (CatalogItem<T,SpecT>)result;
+        return null;
+    }
+
+    @Override
+    public void persist(CatalogItem<?, ?> catalogItem) {
+        checkArgument(getCatalogItem(catalogItem.getSymbolicName(), catalogItem.getVersion()) != null, "Unknown catalog item %s", catalogItem);
+        mgmt.getRebindManager().getChangeListener().onChanged(catalogItem);
+    }
+    
+    @Override
+    public ClassLoader getRootClassLoader() {
+        if (rootClassLoader.isEmpty() && catalog!=null) {
+            resetRootClassLoader();
+        }
+        return rootClassLoader;
+    }
+
+    private void resetRootClassLoader() {
+        rootClassLoader.reset(ImmutableList.of(catalog.getRootClassLoader()));
+    }
+
+    /**
+     * Loads this catalog. No effect if already loaded.
+     */
+    public void load() {
+        log.debug("Loading catalog for " + mgmt);
+        getCatalog().load(mgmt, null);
+        if (log.isDebugEnabled()) {
+            log.debug("Loaded catalog for " + mgmt + ": " + catalog + "; search classpath is " + catalog.getRootClassLoader());
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T, SpecT> SpecT createSpec(CatalogItem<T, SpecT> item) {
+        if (item == null) return null;
+        CatalogItemDo<T,SpecT> loadedItem = (CatalogItemDo<T, SpecT>) getCatalogItemDo(item.getSymbolicName(), item.getVersion());
+        if (loadedItem == null) throw new RuntimeException(item+" not in catalog; cannot create spec");
+        Class<SpecT> specType = loadedItem.getSpecType();
+        if (specType==null) return null;
+
+        String yaml = loadedItem.getPlanYaml();
+
+        if (yaml!=null) {
+            // preferred way is to parse the yaml, to resolve references late;
+            // the parsing on load is to populate some fields, but it is optional.
+            // TODO messy for location and policy that we need brooklyn.{locations,policies} root of the yaml, but it works;
+            // see related comment when the yaml is set, in addAbstractCatalogItems
+            // (not sure if anywhere else relies on that syntax; if not, it should be easy to fix!)
+            DeploymentPlan plan = makePlanFromYaml(yaml);
+            BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, item);
+            SpecT spec;
+            switch (item.getCatalogItemType()) {
+                case TEMPLATE:
+                case ENTITY:
+                    spec = createEntitySpec(loadedItem.getSymbolicName(), plan, loader);
+                    break;
+                case POLICY:
+                    spec = createPolicySpec(loadedItem.getSymbolicName(), plan, loader);
+                    break;
+                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());
+            
+            if (Strings.isBlank( ((AbstractBrooklynObjectSpec<?, ?>)spec).getDisplayName() ))
+                ((AbstractBrooklynObjectSpec<?, ?>)spec).displayName(item.getDisplayName());
+            
+            return spec;
+        }
+
+        // revert to legacy mechanism
+        SpecT spec = null;
+        Method method;
+        try {
+            method = Reflections.findMethod(specType, "create", Class.class);
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            throw new IllegalStateException("Unsupported creation of spec type "+specType+"; it must have a public static create(Class) method", e);            
+        }
+        try {
+            if (loadedItem.getJavaType()!=null) {
+                SpecT specT = (SpecT) method.invoke(null, loadedItem.loadJavaClass(mgmt));
+                spec = specT;
+            }
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            throw new IllegalStateException("Error creating "+specType+" "+loadedItem.getJavaType()+": "+e, e);
+        }
+
+        if (spec==null) 
+            throw new IllegalStateException("Unknown how to create instance of "+this);
+
+        return spec;
+    }
+
+    private <T, SpecT> SpecT createSpec(String optionalId, CatalogItemType ciType, DeploymentPlan plan, BrooklynClassLoadingContext loader) {
+        Preconditions.checkNotNull(ciType, "catalog item type for "+plan); 
+        switch (ciType) {
+        case TEMPLATE:
+        case ENTITY: 
+            return createEntitySpec(optionalId, plan, loader);
+        case LOCATION: return createLocationSpec(plan, loader);
+        case POLICY: return createPolicySpec(optionalId, plan, loader);
+        }
+        throw new IllegalStateException("Unknown CI Type "+ciType+" for "+plan);
+    }
+    
+    @SuppressWarnings("unchecked")
+    private <T, SpecT> SpecT createEntitySpec(String symbolicName, DeploymentPlan plan, BrooklynClassLoadingContext loader) {
+        CampPlatform camp = BrooklynServerConfig.getCampPlatform(mgmt).get();
+
+        // TODO should not register new AT each time we instantiate from the same plan; use some kind of cache
+        AssemblyTemplate at;
+        BrooklynLoaderTracker.setLoader(loader);
+        try {
+            at = camp.pdp().registerDeploymentPlan(plan);
+        } finally {
+            BrooklynLoaderTracker.unsetLoader(loader);
+        }
+
+        try {
+            AssemblyTemplateInstantiator instantiator = at.getInstantiator().newInstance();
+            if (instantiator instanceof AssemblyTemplateSpecInstantiator) {
+                return (SpecT) ((AssemblyTemplateSpecInstantiator)instantiator).createNestedSpec(at, camp, loader, 
+                    getInitialEncounteredSymbol(symbolicName));
+            }
+            throw new IllegalStateException("Unable to instantiate YAML; incompatible instantiator "+instantiator+" for "+at);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    private MutableSet<String> getInitialEncounteredSymbol(String symbolicName) {
+        return symbolicName==null ? MutableSet.<String>of() : MutableSet.of(symbolicName);
+    }
+
+    private <T, SpecT> SpecT createPolicySpec(String symbolicName, DeploymentPlan plan, BrooklynClassLoadingContext loader) {
+        return createPolicySpec(plan, loader, getInitialEncounteredSymbol(symbolicName));
+    }
+
+    private <T, SpecT> SpecT createPolicySpec(DeploymentPlan plan, BrooklynClassLoadingContext loader, Set<String> encounteredCatalogTypes) {
+        //Would ideally re-use io.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityDecorationResolver.PolicySpecResolver
+        //but it is CAMP specific and there is no easy way to get hold of it.
+        Object policies = checkNotNull(plan.getCustomAttributes().get(POLICIES_KEY), "policy config");
+        if (!(policies instanceof Iterable<?>)) {
+            throw new IllegalStateException("The value of " + POLICIES_KEY + " must be an Iterable.");
+        }
+
+        Object policy = Iterables.getOnlyElement((Iterable<?>)policies);
+
+        return createPolicySpec(loader, policy, encounteredCatalogTypes);
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T, SpecT> SpecT createPolicySpec(BrooklynClassLoadingContext loader, Object policy, Set<String> encounteredCatalogTypes) {
+        Map<String, Object> itemMap;
+        if (policy instanceof String) {
+            itemMap = ImmutableMap.<String, Object>of("type", policy);
+        } else if (policy instanceof Map) {
+            itemMap = (Map<String, Object>) policy;
+        } else {
+            throw new IllegalStateException("Policy expected to be string or map. Unsupported object type " + policy.getClass().getName() + " (" + policy.toString() + ")");
+        }
+
+        String versionedId = (String) checkNotNull(Yamls.getMultinameAttribute(itemMap, "policy_type", "policyType", "type"), "policy type");
+        PolicySpec<? extends Policy> spec;
+        CatalogItem<?, ?> policyItem = CatalogUtils.getCatalogItemOptionalVersion(mgmt, versionedId);
+        if (policyItem != null && !encounteredCatalogTypes.contains(policyItem.getSymbolicName())) {
+            if (policyItem.getCatalogItemType() != CatalogItemType.POLICY) {
+                throw new IllegalStateException("Non-policy catalog item in policy context: " + policyItem);
+            }
+            //TODO re-use createSpec
+            BrooklynClassLoadingContext itemLoader = CatalogUtils.newClassLoadingContext(mgmt, policyItem);
+            if (policyItem.getPlanYaml() != null) {
+                DeploymentPlan plan = makePlanFromYaml(policyItem.getPlanYaml());
+                encounteredCatalogTypes.add(policyItem.getSymbolicName());
+                return createPolicySpec(plan, itemLoader, encounteredCatalogTypes);
+            } else if (policyItem.getJavaType() != null) {
+                spec = PolicySpec.create((Class<Policy>)itemLoader.loadClass(policyItem.getJavaType()));
+            } else {
+                throw new IllegalStateException("Invalid policy item - neither yaml nor javaType: " + policyItem);
+            }
+        } else {
+            spec = PolicySpec.create(loader.loadClass(versionedId, Policy.class));
+        }
+        Map<String, Object> brooklynConfig = (Map<String, Object>) itemMap.get("brooklyn.config");
+        if (brooklynConfig != null) {
+            spec.configure(brooklynConfig);
+        }
+        return (SpecT) spec;
+    }
+    
+    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);
+
+        return createLocationSpec(loader, location); 
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T, SpecT> SpecT createLocationSpec(BrooklynClassLoadingContext loader, Object location) {
+        Map<String, Object> itemMap;
+        if (location instanceof String) {
+            itemMap = ImmutableMap.<String, Object>of("type", location);
+        } else if (location instanceof Map) {
+            itemMap = (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(itemMap, "location_type", "locationType", "type"), "location type");
+        Map<String, Object> brooklynConfig = (Map<String, Object>) itemMap.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
+    /** @deprecated since 0.7.0 use {@link #createSpec(CatalogItem)} */
+    @Deprecated
+    public <T,SpecT> Class<? extends T> loadClass(CatalogItem<T,SpecT> item) {
+        if (log.isDebugEnabled())
+            log.debug("Loading class for catalog item " + item);
+        checkNotNull(item);
+        CatalogItemDo<?,?> loadedItem = getCatalogItemDo(item.getSymbolicName(), item.getVersion());
+        if (loadedItem==null) throw new NoSuchElementException("Unable to load '"+item.getId()+"' to instantiate it");
+        return (Class<? extends T>) loadedItem.getJavaClass();
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    /** @deprecated since 0.7.0 use {@link #createSpec(CatalogItem)} */
+    @Deprecated
+    public <T> Class<? extends T> loadClassByType(String typeName, Class<T> typeClass) {
+        final CatalogItem<?,?> resultI = getCatalogItemForType(typeName);
+
+        if (resultI == null) {
+            throw new NoSuchElementException("Unable to find catalog item for type "+typeName);
+        }
+
+        return (Class<? extends T>) loadClass(resultI);
+    }
+
+    @Deprecated /** @deprecated since 0.7.0 only used by other deprecated items */ 
+    private <T,SpecT> CatalogItemDtoAbstract<T,SpecT> getAbstractCatalogItem(CatalogItem<T,SpecT> item) {
+        while (item instanceof CatalogItemDo) item = ((CatalogItemDo<T,SpecT>)item).itemDto;
+        if (item==null) return null;
+        if (item instanceof CatalogItemDtoAbstract) return (CatalogItemDtoAbstract<T,SpecT>) item;
+        throw new IllegalStateException("Cannot unwrap catalog item '"+item+"' (type "+item.getClass()+") to restore DTO");
+    }
+    
+    @SuppressWarnings("unchecked")
+    private static <T> Maybe<T> getFirstAs(Map<?,?> map, Class<T> type, String firstKey, String ...otherKeys) {
+        if (map==null) return Maybe.absent("No map available");
+        String foundKey = null;
+        Object value = null;
+        if (map.containsKey(firstKey)) foundKey = firstKey;
+        else for (String key: otherKeys) {
+            if (map.containsKey(key)) {
+                foundKey = key;
+                break;
+            }
+        }
+        if (foundKey==null) return Maybe.absent("Missing entry '"+firstKey+"'");
+        value = map.get(foundKey);
+        if (type.equals(String.class) && Number.class.isInstance(value)) value = value.toString();
+        if (!type.isInstance(value)) 
+            throw new IllegalArgumentException("Entry for '"+firstKey+"' should be of type "+type+", not "+value.getClass());
+        return Maybe.of((T)value);
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private Maybe<Map<?,?>> getFirstAsMap(Map<?,?> map, String firstKey, String ...otherKeys) {
+        return (Maybe<Map<?,?>>)(Maybe) getFirstAs(map, Map.class, firstKey, otherKeys);
+    }
+
+    private List<CatalogItemDtoAbstract<?,?>> collectCatalogItems(String yaml) {
+        Map<?,?> itemDef = Yamls.getAs(Yamls.parseAll(yaml), Map.class);
+        Map<?,?> catalogMetadata = getFirstAsMap(itemDef, "brooklyn.catalog").orNull();
+        if (catalogMetadata==null)
+            log.warn("No `brooklyn.catalog` supplied in catalog request; using legacy mode for "+itemDef);
+        catalogMetadata = MutableMap.copyOf(catalogMetadata);
+
+        List<CatalogItemDtoAbstract<?, ?>> result = MutableList.of();
+        
+        collectCatalogItems(Yamls.getTextOfYamlAtPath(yaml, "brooklyn.catalog").getMatchedYamlTextOrWarn(), 
+            catalogMetadata, result, null);
+        
+        itemDef.remove("brooklyn.catalog");
+        catalogMetadata.remove("item");
+        catalogMetadata.remove("items");
+        if (!itemDef.isEmpty()) {
+            log.debug("Reading brooklyn.catalog peer keys as item ('top-level syntax')");
+            Map<String,?> rootItem = MutableMap.of("item", itemDef);
+            String rootItemYaml = yaml;
+            YamlExtract yamlExtract = Yamls.getTextOfYamlAtPath(rootItemYaml, "brooklyn.catalog");
+            String match = yamlExtract.withOriginalIndentation(true).withKeyIncluded(true).getMatchedYamlTextOrWarn();
+            if (match!=null) {
+                if (rootItemYaml.startsWith(match)) rootItemYaml = Strings.removeFromStart(rootItemYaml, match);
+                else rootItemYaml = Strings.replaceAllNonRegex(rootItemYaml, "\n"+match, "");
+            }
+            collectCatalogItems("item:\n"+makeAsIndentedObject(rootItemYaml), rootItem, result, catalogMetadata);
+        }
+        
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    private void collectCatalogItems(String sourceYaml, Map<?,?> itemMetadata, List<CatalogItemDtoAbstract<?, ?>> result, Map<?,?> parentMetadata) {
+
+        if (sourceYaml==null) sourceYaml = new Yaml().dump(itemMetadata);
+
+        Map<Object,Object> catalogMetadata = MutableMap.builder().putAll(parentMetadata).putAll(itemMetadata).build();
+        
+        // brooklyn.libraries we treat specially, to append the list, with the child's list preferred in classloading order
+        // `libraries` is supported in some places as a legacy syntax; it should always be `brooklyn.libraries` for new apps
+        // TODO in 0.8.0 require brooklyn.libraries, don't allow "libraries" on its own
+        List<?> librariesNew = MutableList.copyOf(getFirstAs(itemMetadata, List.class, "brooklyn.libraries", "libraries").orNull());
+        Collection<CatalogBundle> libraryBundlesNew = CatalogItemDtoAbstract.parseLibraries(librariesNew);
+        
+        List<?> librariesCombined = MutableList.copyOf(librariesNew)
+            .appendAll(getFirstAs(parentMetadata, List.class, "brooklyn.libraries", "libraries").orNull());
+        if (!librariesCombined.isEmpty())
+            catalogMetadata.put("brooklyn.libraries", librariesCombined);
+        Collection<CatalogBundle> libraryBundles = CatalogItemDtoAbstract.parseLibraries(librariesCombined);
+
+        // TODO as this may take a while if downloading, the REST call should be async
+        // (this load is required for the scan below and I think also for yaml resolution)
+        CatalogUtils.installLibraries(mgmt, libraryBundlesNew);
+
+        Boolean scanJavaAnnotations = getFirstAs(itemMetadata, Boolean.class, "scanJavaAnnotations", "scan_java_annotations").orNull();
+        if (scanJavaAnnotations==null || !scanJavaAnnotations) {
+            // don't scan
+        } else {
+            // scan for annotations: if libraries here, scan them; if inherited libraries error; else scan classpath
+            if (!libraryBundlesNew.isEmpty()) {
+                result.addAll(scanAnnotationsFromBundles(mgmt, libraryBundlesNew, catalogMetadata));
+            } else if (libraryBundles.isEmpty()) {
+                result.addAll(scanAnnotationsFromLocal(mgmt, catalogMetadata));
+            } else {
+                throw new IllegalStateException("Cannot scan catalog node no local bundles, and with inherited bundles we will not scan the classpath");
+            }
+        }
+        
+        Object items = catalogMetadata.remove("items");
+        Object item = catalogMetadata.remove("item");
+
+        if (items!=null) {
+            int count = 0;
+            for (Map<?,?> i: ((List<Map<?,?>>)items)) {
+                collectCatalogItems(Yamls.getTextOfYamlAtPath(sourceYaml, "items", count).getMatchedYamlTextOrWarn(), 
+                    i, result, catalogMetadata);
+                count++;
+            }
+        }
+        
+        if (item==null) return;
+
+        // now look at the actual item, first correcting the sourceYaml and interpreting the catalog metadata
+        String itemYaml = Yamls.getTextOfYamlAtPath(sourceYaml, "item").getMatchedYamlTextOrWarn();
+        if (itemYaml!=null) sourceYaml = itemYaml;
+        else sourceYaml = new Yaml().dump(item);
+        
+        CatalogItemType itemType = TypeCoercions.coerce(getFirstAs(catalogMetadata, Object.class, "itemType", "item_type").orNull(), CatalogItemType.class);
+        BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, "<load>:0", libraryBundles);
+
+        String id = getFirstAs(catalogMetadata, String.class, "id").orNull();
+        String version = getFirstAs(catalogMetadata, String.class, "version").orNull();
+        String symbolicName = getFirstAs(catalogMetadata, String.class, "symbolicName").orNull();
+        String displayName = getFirstAs(catalogMetadata, String.class, "displayName").orNull();
+        String name = getFirstAs(catalogMetadata, String.class, "name").orNull();
+
+        if ((Strings.isNonBlank(id) || Strings.isNonBlank(symbolicName)) && 
+                Strings.isNonBlank(displayName) &&
+                Strings.isNonBlank(name) && !name.equals(displayName)) {
+            log.warn("Name property will be ignored due to the existence of displayName and at least one of id, symbolicName");
+        }
+
+        PlanInterpreterGuessingType planInterpreter = new PlanInterpreterGuessingType(null, item, sourceYaml, itemType, loader, result).reconstruct();
+        if (!planInterpreter.isResolved()) {
+            throw Exceptions.create("Could not resolve item "
+                + (Strings.isNonBlank(id) ? id : Strings.isNonBlank(symbolicName) ? symbolicName : Strings.isNonBlank(name) ? name : "<no-name>")
+                + ":\n"+sourceYaml, planInterpreter.getErrors());
+        }
+        itemType = planInterpreter.getCatalogItemType();
+        Map<?, ?> itemAsMap = planInterpreter.getItem();
+        // the "plan yaml" includes the services: ... or brooklyn.policies: ... outer key,
+        // as opposed to the rawer { type: xxx } map without that outer key which is valid as item input
+        // TODO this plan yaml is needed for subsequent reconstruction; would be nicer if it weren't! 
+
+        // if symname not set, infer from: id, then name, then item id, then item name
+        if (Strings.isBlank(symbolicName)) {
+            if (Strings.isNonBlank(id)) {
+                if (CatalogUtils.looksLikeVersionedId(id)) {
+                    symbolicName = CatalogUtils.getIdFromVersionedId(id);
+                } else {
+                    symbolicName = id;
+                }
+            } else if (Strings.isNonBlank(name)) {
+                if (CatalogUtils.looksLikeVersionedId(name)) {
+                    symbolicName = CatalogUtils.getIdFromVersionedId(name);
+                } else {
+                    symbolicName = name;
+                }
+            } else {
+                symbolicName = setFromItemIfUnset(symbolicName, itemAsMap, "id");
+                symbolicName = setFromItemIfUnset(symbolicName, itemAsMap, "name");
+                if (Strings.isBlank(symbolicName)) {
+                    log.error("Can't infer catalog item symbolicName from the following plan:\n" + sourceYaml);
+                    throw new IllegalStateException("Can't infer catalog item symbolicName from catalog item metadata");
+                }
+            }
+        }
+
+        // if version not set, infer from: id, then from name, then item version
+        if (CatalogUtils.looksLikeVersionedId(id)) {
+            String versionFromId = CatalogUtils.getVersionFromVersionedId(id);
+            if (versionFromId != null && Strings.isNonBlank(version) && !versionFromId.equals(version)) {
+                throw new IllegalArgumentException("Discrepency between version set in id " + versionFromId + " and version property " + version);
+            }
+            version = versionFromId;
+        }
+        if (Strings.isBlank(version)) {
+            if (CatalogUtils.looksLikeVersionedId(name)) {
+                version = CatalogUtils.getVersionFromVersionedId(name);
+            } else if (Strings.isBlank(version)) {
+                version = setFromItemIfUnset(version, itemAsMap, "version");
+                if (version==null) {
+                    log.warn("No version specified for catalog item " + symbolicName + ". Using default value.");
+                    version = null;
+                }
+            }
+        }
+        
+        // if not set, ID can come from symname:version, failing that, from the plan.id, failing that from the sym name
+        if (Strings.isBlank(id)) {
+            // let ID be inferred, especially from name, to support style where only "name" is specified, with inline version
+            if (Strings.isNonBlank(symbolicName) && Strings.isNonBlank(version)) {
+                id = symbolicName + ":" + version;
+            }
+            id = setFromItemIfUnset(id, itemAsMap, "id");
+            if (Strings.isBlank(id)) {
+                if (Strings.isNonBlank(symbolicName)) {
+                    id = symbolicName;
+                } else {
+                    log.error("Can't infer catalog item id from the following plan:\n" + sourceYaml);
+                    throw new IllegalStateException("Can't infer catalog item id from catalog item metadata");
+                }
+            }
+        }
+
+        if (Strings.isBlank(displayName)) {
+            if (Strings.isNonBlank(name)) displayName = name;
+            displayName = setFromItemIfUnset(displayName, itemAsMap, "name");
+        }
+
+        String description = getFirstAs(catalogMetadata, String.class, "description").orNull();
+        description = setFromItemIfUnset(description, itemAsMap, "description");
+
+        // icon.url is discouraged, but kept for legacy compatibility; should deprecate this
+        final String catalogIconUrl = getFirstAs(catalogMetadata, String.class, "iconUrl", "icon_url", "icon.url").orNull();
+
+        final String deprecated = getFirstAs(catalogMetadata, String.class, "deprecated").orNull();
+        final Boolean catalogDeprecated = Boolean.valueOf(deprecated);
+
+        // run again now that we know the ID
+        planInterpreter = new PlanInterpreterGuessingType(id, item, sourceYaml, itemType, loader, result).reconstruct();
+        if (!planInterpreter.isResolved()) {
+            throw new IllegalStateException("Could not resolve plan once id and itemType are known (recursive reference?): "+sourceYaml);
+        }
+        String sourcePlanYaml = planInterpreter.getPlanYaml();
+        
+        CatalogItemDtoAbstract<?, ?> dto = createItemBuilder(itemType, symbolicName, version)
+            .libraries(libraryBundles)
+            .displayName(displayName)
+            .description(description)
+            .deprecated(catalogDeprecated)
+            .iconUrl(catalogIconUrl)
+            .plan(sourcePlanYaml)
+            .build();
+
+        dto.setManagementContext((ManagementContextInternal) mgmt);
+        result.add(dto);
+    }
+
+    private String setFromItemIfUnset(String oldValue, Map<?,?> item, String fieldAttr) {
+        if (Strings.isNonBlank(oldValue)) return oldValue;
+        if (item!=null) {
+            Object newValue = item.get(fieldAttr);
+            if (newValue instanceof String && Strings.isNonBlank((String)newValue)) 
+                return (String)newValue;
+        }
+        return oldValue;
+    }
+
+    private Collection<CatalogItemDtoAbstract<?, ?>> scanAnnotationsFromLocal(ManagementContext mgmt, Map<Object, Object> catalogMetadata) {
+        CatalogDto dto = CatalogDto.newNamedInstance("Local Scanned Catalog", "All annotated Brooklyn entities detected in the classpath", "scanning-local-classpath");
+        return scanAnnotationsInternal(mgmt, new CatalogDo(dto), catalogMetadata);
+    }
+    
+    private Collection<CatalogItemDtoAbstract<?, ?>> scanAnnotationsFromBundles(ManagementContext mgmt, Collection<CatalogBundle> libraries, Map<Object, Object> catalogMetadata) {
+        CatalogDto dto = CatalogDto.newNamedInstance("Bundles Scanned Catalog", "All annotated Brooklyn entities detected in bundles", "scanning-bundles-classpath-"+libraries.hashCode());
+        List<String> urls = MutableList.of();
+        for (CatalogBundle b: libraries) {
+            // TODO currently does not support pre-installed bundles identified by name:version 
+            // (ie where URL not supplied)
+            if (Strings.isNonBlank(b.getUrl())) {
+                urls.add(b.getUrl());
+            }
+        }
+        
+        if (urls.isEmpty()) {
+            log.warn("No bundles to scan: scanJavaAnnotations currently only applies to OSGi bundles provided by URL"); 
+            return MutableList.of();
+        }
+        
+        CatalogDo subCatalog = new CatalogDo(dto);
+        subCatalog.addToClasspath(urls.toArray(new String[0]));
+        return scanAnnotationsInternal(mgmt, subCatalog, catalogMetadata);
+    }
+    
+    private Collection<CatalogItemDtoAbstract<?, ?>> scanAnnotationsInternal(ManagementContext mgmt, CatalogDo subCatalog, Map<Object, Object> catalogMetadata) {
+        // TODO this does java-scanning only;
+        // the call when scanning bundles should use the CatalogItem instead and use OSGi when loading for scanning
+        // (or another scanning mechanism).  see comments on CatalogClasspathDo.load
+        subCatalog.mgmt = mgmt;
+        subCatalog.setClasspathScanForEntities(CatalogScanningModes.ANNOTATIONS);
+        subCatalog.load();
+        // TODO apply metadata?  (extract YAML from the items returned)
+        // also see doc .../catalog/index.md which says we might not apply metadata
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        Collection<CatalogItemDtoAbstract<?, ?>> result = (Collection<CatalogItemDtoAbstract<?, ?>>)(Collection)Collections2.transform(
+                (Collection<CatalogItemDo<Object,Object>>)(Collection)subCatalog.getIdCache().values(), 
+                itemDoToDtoAddingSelectedMetadataDuringScan(catalogMetadata));
+        return result;
+    }
+
+    private class PlanInterpreterGuessingType {
+
+        final String id;
+        final Map<?,?> item;
+        final String itemYaml;
+        final BrooklynClassLoadingContext loader;
+        final List<CatalogItemDtoAbstract<?, ?>> itemsDefinedSoFar;
+        
+        CatalogItemType catalogItemType;
+        String planYaml;
+        @SuppressWarnings("unused")
+        DeploymentPlan plan;
+        AbstractBrooklynObjectSpec<?,?> spec;
+        boolean resolved = false;
+        List<Exception> errors = MutableList.of();
+        
+        public PlanInterpreterGuessingType(@Nullable String id, Object item, String itemYaml, @Nullable CatalogItemType optionalCiType, 
+                BrooklynClassLoadingContext loader, List<CatalogItemDtoAbstract<?,?>> itemsDefinedSoFar) {
+            // ID is useful to prevent recursive references (currently for entities only)
+            this.id = id;
+            
+            if (item instanceof String) {
+                // if just a string supplied, wrap as map
+                this.item = MutableMap.of("type", item);
+                this.itemYaml = "type:\n"+makeAsIndentedObject(itemYaml);                
+            } else {
+                this.item = (Map<?,?>)item;
+                this.itemYaml = itemYaml;
+            }
+            this.catalogItemType = optionalCiType;
+            this.loader = loader;
+            this.itemsDefinedSoFar = itemsDefinedSoFar;
+        }
+
+        public PlanInterpreterGuessingType reconstruct() {
+            if (catalogItemType==CatalogItemType.TEMPLATE) {
+                // template *must* be explicitly defined, and if so, none of the other calls apply
+                attemptType(null, CatalogItemType.TEMPLATE);
+                
+            } else {
+                attemptType(null, CatalogItemType.ENTITY);
+
+                attemptType("services", CatalogItemType.ENTITY);
+                attemptType(POLICIES_KEY, CatalogItemType.POLICY);
+                attemptType(LOCATIONS_KEY, CatalogItemType.LOCATION);
+            }
+            
+            if (!resolved && catalogItemType==CatalogItemType.TEMPLATE) {
+                // anything goes, for an explicit template, because we can't easily recurse into the types
+                planYaml = itemYaml;
+                resolved = true;
+            }
+            
+            return this;
+        }
+        
+        public boolean isResolved() { return resolved; }
+        
+        /** Returns potentially useful errors encountered while guessing types. 
+         * May only be available where the type is known. */
+        public List<Exception> getErrors() {
+            return errors;
+        }
+        
+        public CatalogItemType getCatalogItemType() {
+            return catalogItemType; 
+        }
+        
+        public String getPlanYaml() {
+            return planYaml;
+        }
+        
+        private boolean attemptType(String key, CatalogItemType candidateCiType) {
+            if (resolved) return false;
+            if (catalogItemType!=null && catalogItemType!=candidateCiType) return false;
+            
+            final String candidateYaml;
+            if (key==null) candidateYaml = itemYaml;
+            else {
+                if (item.containsKey(key))
+                    candidateYaml = itemYaml;
+                else
+                    candidateYaml = key + ":\n" + makeAsIndentedList(itemYaml);
+            }
+            // first look in collected items, if a key is given
+            String type = (String) item.get("type");
+            String version = null;
+            if (CatalogUtils.looksLikeVersionedId(type)) {
+                version = CatalogUtils.getVersionFromVersionedId(type);
+                type = CatalogUtils.getIdFromVersionedId(type);
+            }
+            if (type!=null && key!=null) {
+                for (CatalogItemDtoAbstract<?,?> candidate: itemsDefinedSoFar) {
+                    if (candidateCiType == candidate.getCatalogItemType() &&
+                            (type.equals(candidate.getSymbolicName()) || type.equals(candidate.getId()))) {
+                        if (version==null || version.equals(candidate.getVersion())) {
+                            // matched - exit
+                            catalogItemType = candidateCiType;
+                            planYaml = candidateYaml;
+                            resolved = true;
+                            return true;
+                        }
+                    }
+                }
+            }
+            
+            // then try parsing plan - this will use loader
+            try {
+                DeploymentPlan candidatePlan = makePlanFromYaml(candidateYaml);
+                spec = createSpec(id, candidateCiType, candidatePlan, loader);
+                if (spec!=null) {
+                    catalogItemType = candidateCiType;
+                    plan = candidatePlan;
+                    planYaml = candidateYaml;
+                    resolved = true;
+                }
+                return true;
+            } catch (Exception e) {
+                Exceptions.propagateIfFatal(e);
+                // record the error if we have reason to expect this guess to succeed
+                if (item.containsKey("services") && (candidateCiType==CatalogItemType.ENTITY || candidateCiType==CatalogItemType.TEMPLATE)) {
+                    // explicit services supplied, so plan should have been parseable for an entity or a a service
+                    errors.add(e);
+                } else if (catalogItemType!=null && key!=null) {
+                    // explicit itemType supplied, so plan should be parseable in the cases where we're given a key
+                    // (when we're not given a key, the previous block should apply)
+                    errors.add(e);
+                } else {
+                    // all other cases, the error is probably due to us not getting the type right, ignore it
+                    if (log.isTraceEnabled())
+                        log.trace("Guessing type of plan, it looks like it isn't "+candidateCiType+"/"+key+": "+e);
+                }
+            }
+            
+            // finally try parsing a cut-down plan, in case there is a nested reference to a newly defined catalog item
+            if (type!=null && key!=null) {
+                try {
+                    String cutDownYaml = key + ":\n" + makeAsIndentedList("type: "+type);
+                    DeploymentPlan candidatePlan = makePlanFromYaml(cutDownYaml);
+                    Object cutdownSpec = createSpec(id, candidateCiType, candidatePlan, loader);
+                    if (cutdownSpec!=null) {
+                        catalogItemType = candidateCiType;
+                        planYaml = candidateYaml;
+                        resolved = true;
+                    }
+                    return true;
+                } catch (Exception e) {
+                    Exceptions.propagateIfFatal(e);
+                }
+            }
+            
+            return false;
+        }
+        public Map<?,?> getItem() {
+            return item;
+        }
+    }
+    
+    private String makeAsIndentedList(String yaml) {
+        String[] lines = yaml.split("\n");
+        lines[0] = "- "+lines[0];
+        for (int i=1; i<lines.length; i++)
+            lines[i] = "  " + lines[i];
+        return Strings.join(lines, "\n");
+    }
+
+    private String makeAsIndentedObject(String yaml) {
+        String[] lines = yaml.split("\n");
+        for (int i=0; i<lines.length; i++)
+            lines[i] = "  " + lines[i];
+        return Strings.join(lines, "\n");
+    }
+
+    private CatalogItemBuilder<?> createItemBuilder(CatalogItemType itemType, String itemId, String version) {
+        Preconditions.checkNotNull(itemType, "itemType required");
+        switch (itemType) {
+        case ENTITY: return CatalogItemBuilder.newEntity(itemId, version);
+        case TEMPLATE: return CatalogItemBuilder.newTemplate(itemId, version);
+        case POLICY: return CatalogItemBuilder.newPolicy(itemId, version);
+        case LOCATION: return CatalogItemBuilder.newLocation(itemId, version);
+        }
+        throw new IllegalStateException("Unexpected itemType: "+itemType);
+    }
+
+    // these kept as their logic may prove useful; Apr 2015
+//    private boolean isApplicationSpec(EntitySpec<?> spec) {
+//        return !Boolean.TRUE.equals(spec.getConfig().get(EntityManagementUtils.WRAPPER_APP_MARKER));
+//    }
+//
+//    private boolean isEntityPlan(DeploymentPlan plan) {
+//        return plan!=null && !plan.getServices().isEmpty() || !plan.getArtifacts().isEmpty();
+//    }
+//    
+//    private boolean isPolicyPlan(DeploymentPlan plan) {
+//        return !isEntityPlan(plan) && plan.getCustomAttributes().containsKey(POLICIES_KEY);
+//    }
+//
+//    private boolean isLocationPlan(DeploymentPlan plan) {
+//        return !isEntityPlan(plan) && plan.getCustomAttributes().containsKey(LOCATIONS_KEY);
+//    }
+
+    private DeploymentPlan makePlanFromYaml(String yaml) {
+        CampPlatform camp = BrooklynServerConfig.getCampPlatform(mgmt).get();
+        return camp.pdp().parseDeploymentPlan(Streams.newReaderWithContents(yaml));
+    }
+
+    //------------------------
+    
+    @Override
+    public CatalogItem<?,?> addItem(String yaml) {
+        return addItem(yaml, false);
+    }
+
+    @Override
+    public List<? extends CatalogItem<?,?>> addItems(String yaml) {
+        return addItems(yaml, false);
+    }
+
+    @Override
+    public CatalogItem<?,?> addItem(String yaml, boolean forceUpdate) {
+        return Iterables.getOnlyElement(addItems(yaml, forceUpdate));
+    }
+    
+    @Override
+    public List<? extends CatalogItem<?,?>> addItems(String yaml, boolean forceUpdate) {
+        log.debug("Adding manual catalog item to "+mgmt+": "+yaml);
+        checkNotNull(yaml, "yaml");
+        List<CatalogItemDtoAbstract<?, ?>> result = collectCatalogItems(yaml);
+        // do this at the end for atomic updates; if there are intra-yaml references, we handle them specially
+        for (CatalogItemDtoAbstract<?, ?> item: result) {
+            addItemDto(item, forceUpdate);
+        }
+        return result;
+    }
+    
+    private CatalogItem<?,?> addItemDto(CatalogItemDtoAbstract<?, ?> itemDto, boolean forceUpdate) {
+        CatalogItem<?, ?> existingDto = checkItemAllowedAndIfSoReturnAnyDuplicate(itemDto, true, forceUpdate);
+        if (existingDto!=null) {
+            // it's a duplicate, and not forced, just return it
+            log.trace("Using existing duplicate for catalog item {}", itemDto.getId());
+            return existingDto;
+        }
+
+        if (manualAdditionsCatalog==null) loadManualAdditionsCatalog();
+        manualAdditionsCatalog.addEntry(itemDto);
+
+        // Ensure the cache is populated and it is persisted by the management context
+        getCatalog().addEntry(itemDto);
+
+        // Request that the management context persist the item.
+        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;
+    }
+
+    /** returns item DTO if item is an allowed duplicate, or null if it should be added (there is no duplicate), 
+     * throwing if item cannot be added */
+    private CatalogItem<?, ?> checkItemAllowedAndIfSoReturnAnyDuplicate(CatalogItem<?,?> itemDto, boolean allowDuplicates, boolean forceUpdate) {
+        if (forceUpdate) return null;
+        CatalogItemDo<?, ?> existingItem = getCatalogItemDo(itemDto.getSymbolicName(), itemDto.getVersion());
+        if (existingItem == null) return null;
+        // check if they are equal
+        CatalogItem<?, ?> existingDto = existingItem.getDto();
+        if (existingDto.equals(itemDto)) {
+            if (allowDuplicates) return existingItem;
+            throw new IllegalStateException("Updating existing catalog entries, even with the same content, is forbidden: " +
+                    itemDto.getSymbolicName() + ":" + itemDto.getVersion() + ". Use forceUpdate argument to override.");
+        } else {
+            throw new IllegalStateException("Updating existing catalog entries is forbidden: " +
+                    itemDto.getSymbolicName() + ":" + itemDto.getVersion() + ". Use forceUpdate argument to override.");
+        }
+    }
+
+    @Override @Deprecated /** @deprecated see super */
+    public void addItem(CatalogItem<?,?> item) {
+        //assume forceUpdate for backwards compatibility
+        log.debug("Adding manual catalog item to "+mgmt+": "+item);
+        checkNotNull(item, "item");
+        CatalogUtils.installLibraries(mgmt, item.getLibraries());
+        if (manualAdditionsCatalog==null) loadManualAdditionsCatalog();
+        manualAdditionsCatalog.addEntry(getAbstractCatalogItem(item));
+    }
+
+    @Override @Deprecated /** @deprecated see super */
+    public CatalogItem<?,?> addItem(Class<?> type) {
+        //assume forceUpdate for backwards compatibility
+        log.debug("Adding manual catalog item to "+mgmt+": "+type);
+        checkNotNull(type, "type");
+        if (manualAdditionsCatalog==null) loadManualAdditionsCatalog();
+        manualAdditionsClasses.addClass(type);
+        return manualAdditionsCatalog.classpath.addCatalogEntry(type);
+    }
+
+    private synchronized void loadManualAdditionsCatalog() {
+        if (manualAdditionsCatalog!=null) return;
+        CatalogDto manualAdditionsCatalogDto = CatalogDto.newNamedInstance(
+                "Manual Catalog Additions", "User-additions to the catalog while Brooklyn is running, " +
+                "created "+Time.makeDateString(),
+                "manual-additions");
+        CatalogDo manualAdditionsCatalog = catalog.addCatalog(manualAdditionsCatalogDto);
+        if (manualAdditionsCatalog==null) {
+            // not hard to support, but slightly messy -- probably have to use ID's to retrieve the loaded instance
+            // for now block once, then retry
+            log.warn("Blocking until catalog is loaded before changing it");
+            boolean loaded = blockIfNotLoaded(Duration.TEN_SECONDS);
+            if (!loaded)
+                log.warn("Catalog still not loaded after delay; subsequent operations may fail");
+            manualAdditionsCatalog = catalog.addCatalog(manualAdditionsCatalogDto);
+            if (manualAdditionsCatalog==null) {
+                throw new UnsupportedOperationException("Catalogs cannot be added until the base catalog is loaded, and catalog is taking a while to load!");
+            }
+        }
+        
+        log.debug("Creating manual additions catalog for "+mgmt+": "+manualAdditionsCatalog);
+        manualAdditionsClasses = new LoadedClassLoader();
+        ((AggregateClassLoader)manualAdditionsCatalog.classpath.getLocalClassLoader()).addFirst(manualAdditionsClasses);
+        
+        // expose when we're all done
+        this.manualAdditionsCatalog = manualAdditionsCatalog;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public <T,SpecT> Iterable<CatalogItem<T,SpecT>> getCatalogItems() {
+        if (!getCatalog().isLoaded()) {
+            // some callers use this to force the catalog to load (maybe when starting as hot_backup without a catalog ?)
+            log.debug("Forcing catalog load on access of catalog items");
+            load();
+        }
+        return ImmutableList.copyOf((Iterable)catalog.getIdCache().values());
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public <T,SpecT> Iterable<CatalogItem<T,SpecT>> getCatalogItems(Predicate<? super CatalogItem<T,SpecT>> filter) {
+        Iterable<CatalogItemDo<T,SpecT>> filtered = Iterables.filter((Iterable)catalog.getIdCache().values(), (Predicate<CatalogItem<T,SpecT>>)(Predicate) filter);
+        return Iterables.transform(filtered, BasicBrooklynCatalog.<T,SpecT>itemDoToDto());
+    }
+
+    private static <T,SpecT> Function<CatalogItemDo<T,SpecT>, CatalogItem<T,SpecT>> itemDoToDto() {
+        return new Function<CatalogItemDo<T,SpecT>, CatalogItem<T,SpecT>>() {
+            @Override
+            public CatalogItem<T,SpecT> apply(@Nullable CatalogItemDo<T,SpecT> item) {
+                if (item==null) return null;
+                return item.getDto();
+            }
+        };
+    }
+    
+    private static <T,SpecT> Function<CatalogItemDo<T, SpecT>, CatalogItem<T,SpecT>> itemDoToDtoAddingSelectedMetadataDuringScan(final Map<Object, Object> catalogMetadata) {
+        return new Function<CatalogItemDo<T,SpecT>, CatalogItem<T,SpecT>>() {
+            @Override
+            public CatalogItem<T,SpecT> apply(@Nullable CatalogItemDo<T,SpecT> item) {
+                if (item==null) return null;
+                CatalogItemDtoAbstract<T, SpecT> dto = (CatalogItemDtoAbstract<T, SpecT>) item.getDto();
+
+                // when scanning we only allow version and libraries to be overwritten
+                
+                String version = getFirstAs(catalogMetadata, String.class, "version").orNull();
+                if (Strings.isNonBlank(version)) dto.setVersion(version);
+                
+                Object librariesCombined = catalogMetadata.get("brooklyn.libraries");
+                if (librariesCombined instanceof Collection) {
+                    // will be set by scan -- slightly longwinded way to retrieve, but scanning for osgi needs an overhaul in any case
+                    Collection<CatalogBundle> libraryBundles = CatalogItemDtoAbstract.parseLibraries((Collection<?>) librariesCombined);
+                    dto.setLibraries(libraryBundles);
+                }
+                // replace java type with plan yaml -- needed for libraries / catalog item to be picked up,
+                // but probably useful to transition away from javaType altogether
+                dto.setSymbolicName(dto.getJavaType());
+                switch (dto.getCatalogItemType()) {
+                    case TEMPLATE:
+                    case ENTITY:
+                        dto.setPlanYaml("services: [{ type: "+dto.getJavaType()+" }]");
+                        break;
+                    case POLICY:
+                        dto.setPlanYaml(POLICIES_KEY + ": [{ type: "+dto.getJavaType()+" }]");
+                        break;
+                    case LOCATION:
+                        dto.setPlanYaml(LOCATIONS_KEY + ": [{ type: "+dto.getJavaType()+" }]");
+                        break;
+                }
+                dto.setJavaType(null);
+
+                return dto;
+            }
+        };
+    }
+
+    transient CatalogXmlSerializer serializer;
+    
+    public String toXmlString() {
+        if (serializer==null) loadSerializer();
+        return serializer.toString(catalog.dto);
+    }
+    
+    private synchronized void loadSerializer() {
+        if (serializer==null) 
+            serializer = new CatalogXmlSerializer();
+    }
+
+    @Deprecated
+    public CatalogItem<?,?> getCatalogItemForType(String typeName) {
+        final CatalogItem<?,?> resultI;
+        final BrooklynCatalog catalog = mgmt.getCatalog();
+        if (CatalogUtils.looksLikeVersionedId(typeName)) {
+            //All catalog identifiers of the form xxxx:yyyy are composed of symbolicName+version.
+            //No javaType is allowed as part of the identifier.
+            resultI = CatalogUtils.getCatalogItemOptionalVersion(mgmt, typeName);
+        } else {
+            //Usually for catalog items with javaType (that is items from catalog.xml)
+            //the symbolicName and javaType match because symbolicName (was ID)
+            //is not specified explicitly. But could be the case that there is an item
+            //whose symbolicName is explicitly set to be different from the javaType.
+            //Note that in the XML the attribute is called registeredTypeName.
+            Iterable<CatalogItem<Object,Object>> resultL = catalog.getCatalogItems(CatalogPredicates.javaType(Predicates.equalTo(typeName)));
+            if (!Iterables.isEmpty(resultL)) {
+                //Push newer versions in front of the list (not that there should
+                //be more than one considering the items are coming from catalog.xml).
+                resultI = sortVersionsDesc(resultL).iterator().next();
+                if (log.isDebugEnabled() && Iterables.size(resultL)>1) {
+                    log.debug("Found "+Iterables.size(resultL)+" matches in catalog for type "+typeName+"; returning the result with preferred version, "+resultI);
+                }
+            } else {
+                //As a last resort try searching for items with the same symbolicName supposedly
+                //different from the javaType.
+                resultI = catalog.getCatalogItem(typeName, BrooklynCatalog.DEFAULT_VERSION);
+                if (resultI != null) {
+                    if (resultI.getJavaType() == null) {
+                        //Catalog items scanned from the classpath (using reflection and annotations) now
+                        //get yaml spec rather than a java type. Can't use those when creating apps from
+                        //the legacy app spec format.
+                        log.warn("Unable to find catalog item for type "+typeName +
+                                ". There is an existing catalog item with ID " + resultI.getId() +
+                                " but it doesn't define a class type.");
+                        return null;
+                    }
+                }
+            }
+        }
+        return resultI;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6caee589/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleConverter.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleConverter.java
new file mode 100644
index 0000000..79b39c3
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleConverter.java
@@ -0,0 +1,63 @@
+/*
+ * 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.core.catalog.internal;
+
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
+import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+
+/**
+ *  Convert old-style catalog.xml file formats to the latest version.
+ *  The code is needed only during transition to the new version, can be removed after a while.
+ */
+@Deprecated
+public class CatalogBundleConverter implements Converter {
+
+    private ReflectionConverter delegateConverter;
+
+    public CatalogBundleConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
+        this.delegateConverter = new ReflectionConverter(mapper, reflectionProvider);
+    }
+
+    @Override
+    public boolean canConvert(@SuppressWarnings("rawtypes") Class type) {
+        return type == CatalogBundleDto.class;
+    }
+
+    @Override
+    public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+        context.convertAnother(source, delegateConverter);
+    }
+
+    @Override
+    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+        if (reader.hasMoreChildren()) {
+            return context.convertAnother(context.currentObject(), CatalogBundleDto.class, delegateConverter);
+        } else {
+            return new CatalogBundleDto(null, null, reader.getValue());
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6caee589/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java
new file mode 100644
index 0000000..60472ae
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/CatalogBundleDto.java
@@ -0,0 +1,93 @@
+/*
+ * 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.core.catalog.internal;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+
+import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle;
+
+public class CatalogBundleDto implements CatalogBundle {
+    private String symbolicName;
+    private String version;
+    private String url;
+
+    public CatalogBundleDto() {}
+
+    public CatalogBundleDto(String name, String version, String url) {
+        if (name == null && version == null) {
+            Preconditions.checkNotNull(url, "url to an OSGi bundle is required");
+        } else {
+            Preconditions.checkNotNull(name, "both name and version are required");
+            Preconditions.checkNotNull(version, "both name and version are required");
+        }
+
+        this.symbolicName = name;
+        this.version = version;
+        this.url = url;
+    }
+
+    @Override
+    public boolean isNamed() {
+        return symbolicName != null && version != null;
+    }
+
+    @Override
+    public String getSymbolicName() {
+        return symbolicName;
+    }
+
+    @Override
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public String getUrl() {
+        return url;
+    }
+
+    @Override
+    public String toString() {
+        return Objects.toStringHelper(this)
+                .add("symbolicName", symbolicName)
+                .add("version", version)
+                .add("url", url)
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(symbolicName, version, url);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        CatalogBundleDto other = (CatalogBundleDto) obj;
+        if (!Objects.equal(symbolicName, other.symbolicName)) return false;
+        if (!Objects.equal(version, other.version)) return false;
+        if (!Objects.equal(url, other.url)) return false;
+        return true;
+    }
+    
+    
+}