You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by sj...@apache.org on 2015/10/15 16:02:27 UTC

[06/16] incubator-brooklyn git commit: Split BrooklynComponentTemplateResolver into independent ServiceSpecResolver

Split BrooklynComponentTemplateResolver into independent ServiceSpecResolver

ServiceSpecResolver is not specific to CAMP, it deals with converting a type (i.e. a catalog item, a java class, chef declaration) to a spec. BrooklynComponentTemplateResolver then takes the spec and applies the CAMP config on it.


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

Branch: refs/heads/master
Commit: 4485fe5c212ee5fddf4e6452c9fb17b77429b5b5
Parents: c71d722
Author: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Authored: Wed Oct 14 15:02:26 2015 +0300
Committer: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Committed: Wed Oct 14 17:11:07 2015 +0300

----------------------------------------------------------------------
 .../internal/JavaCatalogToSpecTransformer.java  |  13 +-
 .../core/mgmt/EntityManagementUtils.java        |   4 +-
 .../brooklyn/core/plan/PlanToSpecFactory.java   |   2 +-
 .../BrooklynAssemblyTemplateInstantiator.java   |  12 +-
 .../BrooklynComponentTemplateResolver.java      | 264 ++++---------------
 .../spi/creation/CampToSpecTransformer.java     |  17 +-
 .../service/AbstractServiceSpecResolver.java    |  65 +++++
 .../service/BrooklynServiceTypeResolver.java    |  80 ------
 .../service/CatalogServiceSpecResolver.java     | 110 ++++++++
 .../service/CatalogServiceTypeResolver.java     |  77 ------
 .../service/ChefServiceSpecResolver.java        |  41 +++
 .../service/ChefServiceTypeResolver.java        |  61 -----
 .../service/DefaultServiceTypeResolver.java     |  23 --
 .../service/DelegatingServiceSpecResolver.java  | 127 +++++++++
 .../HardcodedCatalogServiceSpecResolver.java    |  95 +++++++
 .../service/JavaServiceSpecResolver.java        |  91 +++++++
 .../service/JavaServiceTypeResolver.java        |  38 ---
 .../creation/service/ServiceSpecResolver.java   |  56 ++++
 .../creation/service/ServiceTypeResolver.java   |   3 +
 .../service/ServiceTypeResolverAdaptor.java     |  62 +++++
 .../service/UrlServiceSpecResolver.java         |  70 +++++
 ...lyn.spi.creation.service.ServiceSpecResolver |  23 ++
 ...lyn.spi.creation.service.ServiceTypeResolver |  22 --
 .../brooklyn/catalog/CatalogXmlOsgiTest.java    |  16 +-
 .../brooklyn/catalog/CatalogYamlEntityTest.java |  49 +++-
 .../rest/resources/ApplicationResource.java     |   4 +-
 26 files changed, 882 insertions(+), 543 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java
index 9b4a83c..a6a8e3a 100644
--- a/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java
+++ b/core/src/main/java/org/apache/brooklyn/core/catalog/internal/JavaCatalogToSpecTransformer.java
@@ -34,6 +34,11 @@ import org.apache.brooklyn.core.plan.PlanToSpecTransformer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * Old-style catalog items (can be defined in catalog.xml only) don't have structure, only a single type, so
+ * they are loaded as a simple java type, only taking the class name from the catalog item instead of the
+ * type value in the YAML. Classpath entries in the item are also used (through the catalog root classloader).
+ */
 public class JavaCatalogToSpecTransformer implements PlanToSpecTransformer {
     private static final Logger log = LoggerFactory.getLogger(JavaCatalogToSpecTransformer.class);
 
@@ -64,8 +69,12 @@ public class JavaCatalogToSpecTransformer implements PlanToSpecTransformer {
             CatalogItem<T, SpecT> item, Set<String> encounteredTypes) throws PlanNotRecognizedException {
         if (item.getJavaType() != null) {
             log.warn("Deprecated functionality (since 0.9.0). Using old-style xml catalog items with java type attribute for " + item);
-            BrooklynClassLoadingContext loader = CatalogUtils.newClassLoadingContext(mgmt, item);
-            Class<?> type = loader.loadClass(item.getJavaType());
+            Class<?> type;
+            try {
+                type = mgmt.getCatalogClassLoader().loadClass(item.getJavaType());
+            } catch (ClassNotFoundException e) {
+                throw new IllegalStateException("Unable to load old-style java catalog item type " + item.getJavaType() + " for item " + item, e);
+            }
             AbstractBrooklynObjectSpec<?,?> spec;
             if (Entity.class.isAssignableFrom(type)) {
                 @SuppressWarnings("unchecked")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
index 0dca83c..cda3f5b 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/EntityManagementUtils.java
@@ -44,6 +44,7 @@ import org.apache.brooklyn.core.plan.PlanToSpecTransformer;
 import org.apache.brooklyn.entity.stock.BasicApplication;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.core.task.TaskBuilder;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.text.Strings;
@@ -56,7 +57,6 @@ import com.google.common.base.Function;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 
@@ -99,7 +99,7 @@ public class EntityManagementUtils {
     }
 
     public static <T,SpecT extends AbstractBrooklynObjectSpec<? extends T, SpecT>> SpecT createCatalogSpec(ManagementContext mgmt, CatalogItem<T, SpecT> item) {
-        return createCatalogSpec(mgmt, item, ImmutableSet.<String>of());
+        return createCatalogSpec(mgmt, item, MutableSet.<String>of());
     }
 
     public static <T,SpecT extends AbstractBrooklynObjectSpec<? extends T, SpecT>> SpecT createCatalogSpec(ManagementContext mgmt, final CatalogItem<T, SpecT> item, final Set<String> encounteredTypes) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java b/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java
index 3328545..1b49170 100644
--- a/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java
+++ b/core/src/main/java/org/apache/brooklyn/core/plan/PlanToSpecFactory.java
@@ -117,7 +117,7 @@ public class PlanToSpecFactory {
             // at least one thought he could do it
             log.debug("Plan could not be transformed; failure will be propagated (other transformers tried = "+transformersWhoDontSupport+"): "+otherProblemsFromTransformers);
             result = otherProblemsFromTransformers.size()==1 ? Exceptions.create(null, otherProblemsFromTransformers) :
-                Exceptions.create("Plan transformers all failed", otherProblemsFromTransformers);
+                Exceptions.create("All plan transformers failed", otherProblemsFromTransformers);
         } else {
             result = new PlanNotRecognizedException("Invalid plan; format could not be recognized, trying with: "+transformersWhoDontSupport);
         }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
index e54e2ef..19aa100 100644
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynAssemblyTemplateInstantiator.java
@@ -39,6 +39,7 @@ import org.apache.brooklyn.core.mgmt.HasBrooklynManagementContext;
 import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
 import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext;
 import org.apache.brooklyn.entity.stock.BasicApplicationImpl;
+import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -71,10 +72,11 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
     }
 
     @Override
-    public List<EntitySpec<?>> createServiceSpecs(AssemblyTemplate template,
+    public List<EntitySpec<?>> createServiceSpecs(
+            AssemblyTemplate template,
             CampPlatform platform, BrooklynClassLoadingContext itemLoader,
             Set<String> encounteredCatalogTypes) {
-        return buildTemplateServicesAsSpecs(itemLoader, template, platform, encounteredCatalogTypes, true);
+        return buildTemplateServicesAsSpecs(itemLoader, template, platform, encounteredCatalogTypes);
     }
 
     @Override
@@ -88,7 +90,7 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
 
         BrooklynComponentTemplateResolver resolver = BrooklynComponentTemplateResolver.Factory.newInstance(
             loader, buildWrapperAppTemplate(template));
-        EntitySpec<? extends Application> app = resolver.resolveSpec(null, false);
+        EntitySpec<? extends Application> app = resolver.resolveSpec(MutableSet.<String>of());
         app.configure(EntityManagementUtils.WRAPPER_APP_MARKER, Boolean.TRUE);
 
         // first build the children into an empty shell app
@@ -124,13 +126,13 @@ public class BrooklynAssemblyTemplateInstantiator implements AssemblyTemplateSpe
         return EntityManagementUtils.canPromoteWrappedApplication(app);
     }
 
-    private List<EntitySpec<?>> buildTemplateServicesAsSpecs(BrooklynClassLoadingContext loader, AssemblyTemplate template, CampPlatform platform, Set<String> encounteredCatalogTypes, boolean canUseOtherTransformers) {
+    private List<EntitySpec<?>> buildTemplateServicesAsSpecs(BrooklynClassLoadingContext loader, AssemblyTemplate template, CampPlatform platform, Set<String> encounteredCatalogTypes) {
         List<EntitySpec<?>> result = Lists.newArrayList();
 
         for (ResolvableLink<PlatformComponentTemplate> ctl: template.getPlatformComponentTemplates().links()) {
             PlatformComponentTemplate appChildComponentTemplate = ctl.resolve();
             BrooklynComponentTemplateResolver entityResolver = BrooklynComponentTemplateResolver.Factory.newInstance(loader, appChildComponentTemplate);
-            EntitySpec<?> spec = entityResolver.resolveSpec(encounteredCatalogTypes, canUseOtherTransformers);
+            EntitySpec<?> spec = entityResolver.resolveSpec(encounteredCatalogTypes);
             result.add(spec);
         }
         return result;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
index f42145d..05252f5 100644
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
@@ -18,6 +18,7 @@
  */
 package org.apache.brooklyn.camp.brooklyn.spi.creation;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -28,17 +29,16 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.annotation.Nullable;
 
-import org.apache.brooklyn.api.catalog.CatalogItem;
-import org.apache.brooklyn.api.entity.Application;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
 import org.apache.brooklyn.api.location.Location;
 import org.apache.brooklyn.api.mgmt.ManagementContext;
 import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants;
 import org.apache.brooklyn.camp.brooklyn.BrooklynCampReservedKeys;
-import org.apache.brooklyn.camp.brooklyn.spi.creation.service.DefaultServiceTypeResolver;
+import org.apache.brooklyn.camp.brooklyn.spi.creation.service.DelegatingServiceSpecResolver;
+import org.apache.brooklyn.camp.brooklyn.spi.creation.service.ServiceSpecResolver;
 import org.apache.brooklyn.camp.brooklyn.spi.creation.service.ServiceTypeResolver;
+import org.apache.brooklyn.camp.brooklyn.spi.creation.service.ServiceTypeResolverAdaptor;
 import org.apache.brooklyn.camp.spi.AbstractResource;
 import org.apache.brooklyn.camp.spi.ApplicationComponentTemplate;
 import org.apache.brooklyn.camp.spi.AssemblyTemplate;
@@ -46,21 +46,17 @@ import org.apache.brooklyn.camp.spi.PlatformComponentTemplate;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
 import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.core.mgmt.BrooklynTags;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
-import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
 import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
 import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
 import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.core.ResourceUtils;
 import org.apache.brooklyn.util.core.config.ConfigBag;
 import org.apache.brooklyn.util.core.flags.FlagUtils;
 import org.apache.brooklyn.util.core.flags.FlagUtils.FlagConfigKeyAndValueRecord;
 import org.apache.brooklyn.util.core.task.Tasks;
 import org.apache.brooklyn.util.guava.Maybe;
-import org.apache.brooklyn.util.javalang.Reflections;
 import org.apache.brooklyn.util.net.Urls;
 import org.apache.brooklyn.util.text.Strings;
 import org.slf4j.Logger;
@@ -75,6 +71,7 @@ import com.google.common.collect.Maps;
  * This generates instances of a template resolver that use a {@link ServiceTypeResolver}
  * to parse the {@code serviceType} line in the template.
  */
+@SuppressWarnings("deprecation")  // Because of ServiceTypeResolver
 public class BrooklynComponentTemplateResolver {
 
     private static final Logger log = LoggerFactory.getLogger(BrooklynComponentTemplateResolver.class);
@@ -85,47 +82,29 @@ public class BrooklynComponentTemplateResolver {
     private final Maybe<AbstractResource> template;
     private final BrooklynYamlTypeInstantiator.Factory yamlLoader;
     private final String type;
-    private final ServiceTypeResolver typeResolver;
     private final AtomicBoolean alreadyBuilt = new AtomicBoolean(false);
+    private final ServiceSpecResolver serviceSpecResolver;
 
-    private BrooklynComponentTemplateResolver(BrooklynClassLoadingContext loader, ConfigBag attrs, AbstractResource optionalTemplate, String type, ServiceTypeResolver typeResolver) {
+    private BrooklynComponentTemplateResolver(BrooklynClassLoadingContext loader, ConfigBag attrs, AbstractResource optionalTemplate, String type) {
         this.loader = loader;
         this.mgmt = loader.getManagementContext();
         this.attrs = ConfigBag.newInstanceCopying(attrs);
         this.template = Maybe.fromNullable(optionalTemplate);
         this.yamlLoader = new BrooklynYamlTypeInstantiator.Factory(loader, this);
         this.type = type;
-        this.typeResolver = typeResolver;
+        this.serviceSpecResolver = new DelegatingServiceSpecResolver(mgmt, getServiceTypeResolverOverrides());
     }
 
-    public ManagementContext getManagementContext() { return mgmt; }
-    public ConfigBag getAttrs() { return attrs; }
-    public BrooklynYamlTypeInstantiator.Factory getYamlLoader() { return yamlLoader; }
-    public String getDeclaredType() { return type; }
+    // Deprecated because want to keep as much of the state private as possible
+    // Can't remove them because used by ServiceTypeResolver implementations
+    /** @deprecated since 0.9.0 */
+    @Deprecated public ManagementContext getManagementContext() { return mgmt; }
+    @Deprecated public ConfigBag getAttrs() { return attrs; }
+    @Deprecated public BrooklynYamlTypeInstantiator.Factory getYamlLoader() { return yamlLoader; }
+    @Deprecated public String getDeclaredType() { return type; }
 
     public static class Factory {
 
-        /** returns resolver type based on the service type, inspecting the arguments in order to determine the service type */
-        private static ServiceTypeResolver computeResolverType(BrooklynClassLoadingContext context, String knownServiceType, AbstractResource optionalTemplate, ConfigBag attrs) {
-            String type = getDeclaredType(knownServiceType, optionalTemplate, attrs);
-            return findService(context, type);
-        }
-
-        // TODO This could be extended to support multiple prefixes per resolver and a 'best-match' algorithm
-        private static ServiceTypeResolver findService(BrooklynClassLoadingContext context, String type) {
-            if (type.indexOf(':') != -1) {
-                String prefix = Splitter.on(":").splitToList(type).get(0);
-                ServiceLoader<ServiceTypeResolver> loader = ServiceLoader.load(ServiceTypeResolver.class,
-                        context.getManagementContext().getCatalogClassLoader());
-                for (ServiceTypeResolver resolver : loader) {
-                   if (prefix.equals(resolver.getTypePrefix())) {
-                       return resolver;
-                   }
-                }
-            }
-            return null;
-        }
-
         public static BrooklynComponentTemplateResolver newInstance(BrooklynClassLoadingContext context, Map<String, ?> childAttrs) {
             return newInstance(context, ConfigBag.newInstance(childAttrs), null);
         }
@@ -139,11 +118,8 @@ public class BrooklynComponentTemplateResolver {
         }
 
         private static BrooklynComponentTemplateResolver newInstance(BrooklynClassLoadingContext context, ConfigBag attrs, AbstractResource optionalTemplate) {
-            ServiceTypeResolver typeResolver = computeResolverType(context, null, optionalTemplate, attrs);
             String type = getDeclaredType(null, optionalTemplate, attrs);
-            if (typeResolver == null) // use default
-                typeResolver = new DefaultServiceTypeResolver();
-            return new BrooklynComponentTemplateResolver(context, attrs, optionalTemplate, type, typeResolver);
+            return new BrooklynComponentTemplateResolver(context, attrs, optionalTemplate, type);
         }
 
         private static String getDeclaredType(String knownServiceType, AbstractResource optionalTemplate, @Nullable ConfigBag attrs) {
@@ -164,185 +140,49 @@ public class BrooklynComponentTemplateResolver {
     }
 
     public boolean canResolve() {
-        if (!(typeResolver instanceof DefaultServiceTypeResolver)) {
-            return true;
-        }
-
-        CatalogItem<Entity, EntitySpec<?>> item = typeResolver.getCatalogItem(this, type);
-        if (item != null) {
-            if (item.isDisabled()) {
-                log.warn("Disallowed attempt to use disabled catalog item "+item.getId());
-                return false;
-            } else if (item.isDeprecated()) {
-                log.warn("Use of deprecated catalog item "+item.getId());
-            }
-            return true;
-        }
-
-        if (tryLoadEntityClass().isPresent()) {
-            return true;
-        }
-
-        String protocol = Urls.getProtocol(type);
-        if (protocol != null) {
-            if (BrooklynCampConstants.YAML_URL_PROTOCOL_WHITELIST.contains(protocol)) {
-                return true;
-            } else {
-                log.debug("The reference '" + type + "' looks like a URL (running the CAMP Brooklyn entity-matcher) but the protocol '" + 
-                        protocol + "' isn't white listed " + BrooklynCampConstants.YAML_URL_PROTOCOL_WHITELIST + ". " +
-                        "Not recognized as catalog item or java item as well!");
-            }
-        }
-
-        return true;
-    }
-
-    /** returns the entity class, if needed in contexts which scan its statics for example */
-    private Class<? extends Entity> loadEntityClass() {
-        Maybe<Class<? extends Entity>> result = tryLoadEntityClass();
-        if (result.isAbsent())
-            throw new IllegalStateException("Could not find "+typeResolver.getBrooklynType(type), ((Maybe.Absent<?>)result).getException());
-        return result.get();
-    }
-
-    /** tries to load the Java entity class */
-    private Maybe<Class<? extends Entity>> tryLoadEntityClass() {
-        return loader.tryLoadClass(getJavaType(), Entity.class);
-    }
-
-    // TODO Generalise to have other prefixes (e.g. explicit "catalog:" etc)?
-    private boolean isJavaTypePrefix() {
-        return type != null && (type.toLowerCase().startsWith("java:") || type.toLowerCase().startsWith("brooklyn:java:"));
-    }
-
-    private String getJavaType() {
-        return typeResolver.getBrooklynType(type);
+        return serviceSpecResolver.accepts(type, loader);
     }
 
-    public <T extends Entity> EntitySpec<T> resolveSpec(Set<String> encounteredCatalogTypes, boolean canUseOtherTransformers) {
+    public <T extends Entity> EntitySpec<T> resolveSpec(Set<String> encounteredCatalogTypes) {
         if (alreadyBuilt.getAndSet(true))
             throw new IllegalStateException("Spec can only be used once: "+this);
 
-        String brooklynType = typeResolver.getBrooklynType(type);
-        CatalogItem<Entity, EntitySpec<?>> item = typeResolver.getCatalogItem(this, type);
-
-        if (log.isTraceEnabled()) log.trace("Building CAMP template services: type="+brooklynType+"; item="+item+"; loader="+loader+"; encounteredCatalogTypes="+encounteredCatalogTypes);
-
-        // TODO implement as service type
-        EntitySpec<T> spec = null;
-        String protocol = Urls.getProtocol(brooklynType);
-        if (protocol != null) {
-            if (BrooklynCampConstants.YAML_URL_PROTOCOL_WHITELIST.contains(protocol)) {
-                spec = tryResolveYamlUrlReferenceSpec(brooklynType, encounteredCatalogTypes);
-                if (spec != null) {
-                    populateSpec(spec);
-                }
-            } else {
-                // TODO this will probably be logged if we refer to  chef:cookbook  or other service types which BCTR accepts;
-                // better would be to have BCTR supporting the calls above
-                log.debug("The reference " + brooklynType + " looks like a URL (running the CAMP Brooklyn assembly-template instantiator) but the protocol " +
-                        protocol + " isn't white listed (" + BrooklynCampConstants.YAML_URL_PROTOCOL_WHITELIST + "). " +
-                        "Will try to load it as catalog item or java type.");
-            }
-        }
+        EntitySpec<?> spec = serviceSpecResolver.resolve(type, loader, encounteredCatalogTypes);
 
         if (spec == null) {
-            // load from java or yaml
-            spec = resolveLocalSpec(encounteredCatalogTypes, canUseOtherTransformers);
+            String proto = Urls.getProtocol(type);
+            if (proto != null) {
+                log.debug("The reference " + type + " looks like a URL (running the CAMP Brooklyn assembly-template instantiator) but the protocol " +
+                        proto + " isn't white listed (" + BrooklynCampConstants.YAML_URL_PROTOCOL_WHITELIST + "). " +
+                        "Not a catalog item or java type as well.");
+            }
+            throw new IllegalStateException("Unable to create spec for type " + type + ". No resolver knew how to handle it.");
         }
 
-        return spec;
-    }
+        populateSpec(spec, encounteredCatalogTypes);
 
-    @SuppressWarnings("unchecked")
-    private <T extends Entity> EntitySpec<T> tryResolveYamlUrlReferenceSpec(String brooklynType, Set<String> encounteredCatalogTypes) {
-        String yaml;
-        try {
-            yaml = ResourceUtils.create(this).getResourceAsString(brooklynType);
-        } catch (Exception e) {
-            log.warn("AssemblyTemplate type " + brooklynType + " which looks like a URL can't be fetched.", e);
-            return null;
-        }
-        // Referenced specs are expected to be CAMP format as well.
-        List<EntitySpec<?>> serviceSpecs = CampUtils.createServiceSpecs(yaml, loader, encounteredCatalogTypes);
-        if (serviceSpecs.size() > 1) {
-            throw new UnsupportedOperationException("Only supporting single service in remotely referenced plans: got "+serviceSpecs);
-        }
-        return (EntitySpec<T>) serviceSpecs.get(0);
+        @SuppressWarnings("unchecked")
+        EntitySpec<T> typedSpec = (EntitySpec<T>) spec;
+        return typedSpec;
     }
 
-    private <T extends Entity> EntitySpec<T> resolveLocalSpec(Set<String> encounteredCatalogTypes, boolean canUseOtherTransformers) {
-        CatalogItem<Entity, EntitySpec<?>> item = typeResolver.getCatalogItem(this, type);
-        EntitySpec<T> spec = createSpec(item, encounteredCatalogTypes);
-        populateSpec(spec);
-        return spec;
-    }
-
-    @SuppressWarnings({ "unchecked" })
-    private <T extends Entity,SpecT extends AbstractBrooklynObjectSpec<? extends T, SpecT>> EntitySpec<T> createSpec(CatalogItem<Entity, EntitySpec<?>> item, Set<String> encounteredCatalogTypes) {
-        if (item == null) {
-            // ignore; presumably a java type or some such?
-        } else if (item.isDisabled()) {
-            throw new IllegalStateException("Illegal use of disabled catalog item "+item.getSymbolicName()+":"+item.getVersion());
-        } else if (item.isDeprecated()) {
-            log.warn("Use of deprecated catalog item "+item.getSymbolicName()+":"+item.getVersion());
-        }
-        
-        if (encounteredCatalogTypes==null) encounteredCatalogTypes = MutableSet.of();
-        
-        //Take the symbolicName part of the catalog item only for recursion detection to prevent
-        //cross referencing of different versions. Not interested in non-catalog item types.
-        //Prevent catalog items self-referencing even if explicitly different version.
-        boolean firstOccurrence = (item == null || encounteredCatalogTypes.add(item.getSymbolicName()));
-        boolean recursiveButTryJava = !firstOccurrence;
-
-        // Load a java class from current loader if explicit java prefix, or if no item, or if item is legacy / 
-        // old-style catalog item (item != null && item.getJavaType() != null).
-        // Old-style catalog items (can be defined in catalog.xml only) don't have structure, only a single type, so
-        // they are loaded as a simple java type, only taking the class name from the catalog item instead of the
-        // type value in the YAML. Classpath entries in the item are also used (through the catalog root classloader).
-        if (isJavaTypePrefix() || item == null) {
-            return createSpecFromJavaType();
-
-        // Same as above case, but this time force java type loading (either as plain class or through an old-style
-        // catalog item, since we have already loaded a class item with the same name as the type value.
-        } else if (recursiveButTryJava) {
-            if (tryLoadEntityClass().isAbsent()) {
-                throw new IllegalStateException("Recursive reference to " + item + " (and cannot be resolved as a Java type)");
+    private List<ServiceSpecResolver> getServiceTypeResolverOverrides() {
+        List<ServiceSpecResolver> overrides = new ArrayList<>();
+        if (type.indexOf(':') != -1) {
+            String prefix = Splitter.on(":").splitToList(type).get(0);
+            ServiceLoader<ServiceTypeResolver> loader = ServiceLoader.load(ServiceTypeResolver.class,
+                    mgmt.getCatalogClassLoader());
+            for (ServiceTypeResolver resolver : loader) {
+               if (prefix.equals(resolver.getTypePrefix())) {
+                   overrides.add(new ServiceTypeResolverAdaptor(this, resolver));
+               }
             }
-            return createSpecFromJavaType();
-
-        // Only case that's left is a catalog item with CAMP YAML content - try to parse it recursively
-        // including its OSGi bundles in the loader classpath.
-        } else {
-            return (EntitySpec<T>) EntityManagementUtils.createCatalogSpec(mgmt, (CatalogItem<T,SpecT>)item, encounteredCatalogTypes);
         }
-    }
-    
-    @SuppressWarnings("unchecked")
-    private <T extends Entity> EntitySpec<T> createSpecFromJavaType() {
-        Class<T> type = (Class<T>) loadEntityClass();
-        
-        EntitySpec<T> spec;
-        if (type.isInterface()) {
-            spec = EntitySpec.create(type);
-        } else {
-            // If this is a concrete class, particularly for an Application class, we want the proxy
-            // to expose all interfaces it implements.
-            @SuppressWarnings("rawtypes")
-            Class interfaceclazz = (Application.class.isAssignableFrom(type)) ? Application.class : Entity.class;
-            List<Class<?>> additionalInterfaceClazzes = Reflections.getAllInterfaces(type);
-            spec = EntitySpec.create(interfaceclazz).impl(type).additionalInterfaces(additionalInterfaceClazzes);
-        }
-        spec.catalogItemId(CatalogUtils.getCatalogItemIdFromLoader(loader));
-        if (template.isPresent() && template.get().getSourceCode()!=null)
-            spec.tag(BrooklynTags.newYamlSpecTag(template.get().getSourceCode()));
-
-        return spec;
+        return overrides;
     }
 
     @SuppressWarnings("unchecked")
-    private <T extends Entity> void populateSpec(EntitySpec<T> spec) {
+    private <T extends Entity> void populateSpec(EntitySpec<T> spec, Set<String> encounteredCatalogTypes) {
         String name, templateId=null, planId=null;
         if (template.isPresent()) {
             name = template.get().getName();
@@ -356,15 +196,12 @@ public class BrooklynComponentTemplateResolver {
 
         Object childrenObj = attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_CHILDREN);
         if (childrenObj != null) {
-            // Creating a new set of encounteredCatalogTypes means that this won't check things recursively;
-            // but we are looking at children so we probably *should* be resetting the recursive list we've looked at;
-            // (but see also, a previous comment here which suggested otherwise? - Apr 2015)
-            Set<String> encounteredCatalogTypes = MutableSet.of();
-
             Iterable<Map<String,?>> children = (Iterable<Map<String,?>>)childrenObj;
             for (Map<String,?> childAttrs : children) {
                 BrooklynComponentTemplateResolver entityResolver = BrooklynComponentTemplateResolver.Factory.newInstance(loader, childAttrs);
-                EntitySpec<? extends Entity> childSpec = entityResolver.resolveSpec(encounteredCatalogTypes, true);
+                // encounteredCatalogTypes must contain the items currently being loaded (the dependency chain),
+                // but not parent items in this catalog item already resolved.
+                EntitySpec<? extends Entity> childSpec = entityResolver.resolveSpec(encounteredCatalogTypes);
                 spec.child(childSpec);
             }
         }
@@ -379,7 +216,14 @@ public class BrooklynComponentTemplateResolver {
         if (childLocations != null)
             spec.locations(childLocations);
 
-        typeResolver.decorateSpec(this, spec);
+        decoreateSpec(spec);
+    }
+
+    private <T extends Entity> void decoreateSpec(EntitySpec<T> spec) {
+        new BrooklynEntityDecorationResolver.PolicySpecResolver(yamlLoader).decorate(spec, attrs);
+        new BrooklynEntityDecorationResolver.EnricherSpecResolver(yamlLoader).decorate(spec, attrs);
+        new BrooklynEntityDecorationResolver.InitializerResolver(yamlLoader).decorate(spec, attrs);
+
         configureEntityConfig(spec);
     }
 
@@ -508,7 +352,7 @@ public class BrooklynComponentTemplateResolver {
                 @SuppressWarnings("unchecked")
                 Map<String, Object> resolvedConfig = (Map<String, Object>)transformSpecialFlags(specConfig.getSpecConfiguration());
                 specConfig.setSpecConfiguration(resolvedConfig);
-                return Factory.newInstance(getLoader(), specConfig.getSpecConfiguration()).resolveLocalSpec(null, false);
+                return Factory.newInstance(getLoader(), specConfig.getSpecConfiguration()).resolveSpec(MutableSet.<String>of());
             }
             if (flag instanceof ManagementContextInjectable) {
                 log.debug("Injecting Brooklyn management context info object: {}", flag);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java
index dd0a6f4..9104f8f 100644
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/CampToSpecTransformer.java
@@ -76,19 +76,15 @@ public class CampToSpecTransformer implements PlanToSpecTransformer {
                 if (at.getPlatformComponentTemplates()==null || at.getPlatformComponentTemplates().isEmpty()) {
                     if (at.getCustomAttributes().containsKey("brooklyn.catalog"))
                         throw new IllegalArgumentException("Unrecognized application blueprint format: expected an application, not a brooklyn.catalog");
-                    throw new IllegalArgumentException("Unrecognized application blueprint format: no services defined");
+                    throw new PlanNotRecognizedException("Unrecognized application blueprint format: no services defined");
                 }
                 // map this (expected) error to a nicer message
-                throw new IllegalArgumentException("Unrecognized application blueprint format");
+                throw new PlanNotRecognizedException("Unrecognized application blueprint format");
             }
         } catch (Exception e) {
-            if (e instanceof PlanNotRecognizedException) {
-                if (log.isTraceEnabled())
-                    log.debug("Failed to create entity from CAMP spec:\n" + plan, e);
-            } else {
-                if (log.isDebugEnabled())
-                    log.debug("Failed to create entity from CAMP spec:\n" + plan, e);
-            }
+            // TODO how do we figure out that the plan is not supported vs. invalid to wrap in a PlanNotRecognizedException?
+            if (log.isDebugEnabled())
+                log.debug("Failed to create entity from CAMP spec:\n" + plan, e);
             throw Exceptions.propagate(e);
         }
     }
@@ -96,6 +92,9 @@ public class CampToSpecTransformer implements PlanToSpecTransformer {
     @SuppressWarnings({ "unchecked", "rawtypes" })
     @Override
     public <T, SpecT extends AbstractBrooklynObjectSpec<? extends T, SpecT>> SpecT createCatalogSpec(CatalogItem<T, SpecT> item, Set<String> encounteredTypes) {
+        // Ignore old-style java type catalog items
+        if (item.getPlanYaml() == null) return null;
+
         // Not really clear what should happen to the top-level attributes, ignored until a good use case appears.
         return (SpecT) CampCatalogUtils.createSpec(mgmt, (CatalogItem)item, encounteredTypes);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/AbstractServiceSpecResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/AbstractServiceSpecResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/AbstractServiceSpecResolver.java
new file mode 100644
index 0000000..3fb3c89
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/AbstractServiceSpecResolver.java
@@ -0,0 +1,65 @@
+/*
+ * 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.camp.brooklyn.spi.creation.service;
+
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
+import org.apache.brooklyn.util.text.Strings;
+
+public abstract class AbstractServiceSpecResolver implements ServiceSpecResolver {
+    private static final String PREFIX_DELIMITER = ":";
+    protected final String name;
+    protected final String prefix;
+    protected ManagementContext mgmt;
+
+    public AbstractServiceSpecResolver(String name) {
+        this.name = name;
+        this.prefix = name + PREFIX_DELIMITER;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean accepts(String type, BrooklynClassLoadingContext loader) {
+        return type.startsWith(prefix) && canResolve(type, loader);
+    }
+
+    protected boolean canResolve(String type, BrooklynClassLoadingContext loader) {
+        return true;
+    }
+
+    protected String getLocalType(String type) {
+        return Strings.removeFromStart(type, prefix);
+    }
+
+    @Override
+    public void injectManagementContext(ManagementContext mgmt) {
+        this.mgmt = mgmt;
+    }
+
+    @Override
+    public abstract EntitySpec<?> resolve(String type, BrooklynClassLoadingContext loader, Set<String> encounteredTypes);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/BrooklynServiceTypeResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/BrooklynServiceTypeResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/BrooklynServiceTypeResolver.java
deleted file mode 100644
index e0d2e9e..0000000
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/BrooklynServiceTypeResolver.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.camp.brooklyn.spi.creation.service;
-
-import javax.annotation.Nullable;
-
-import org.apache.brooklyn.api.catalog.CatalogItem;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.api.mgmt.ManagementContext;
-import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynComponentTemplateResolver;
-import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynEntityDecorationResolver;
-import org.apache.brooklyn.camp.spi.PlatformComponentTemplate;
-import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
-import org.apache.brooklyn.core.mgmt.persist.DeserializingClassRenamesProvider;
-import org.apache.brooklyn.util.text.Strings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This converts {@link PlatformComponentTemplate} instances whose type is prefixed {@code brooklyn:}
- * to Brooklyn {@link EntitySpec} instances.
- */
-public class BrooklynServiceTypeResolver implements ServiceTypeResolver {
-
-    @SuppressWarnings("unused")
-    private static final Logger LOG = LoggerFactory.getLogger(ServiceTypeResolver.class);
-    
-    public BrooklynServiceTypeResolver() {
-    }
-    
-    @Override
-    public String getTypePrefix() { return DEFAULT_TYPE_PREFIX; }
-
-    @Override
-    public String getBrooklynType(String serviceType) {
-        String type = Strings.removeFromStart(serviceType, getTypePrefix() + ":").trim();
-        if (type == null) return null;
-        return type;
-    }
-
-    @Nullable
-    @Override
-    public CatalogItem<Entity,EntitySpec<?>> getCatalogItem(BrooklynComponentTemplateResolver resolver, String serviceType) {
-        String type = getBrooklynType(serviceType);
-        if (type != null) {
-            return getCatalogItemImpl(resolver.getManagementContext(),  type);
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public <T extends Entity> void decorateSpec(BrooklynComponentTemplateResolver resolver, EntitySpec<T> spec) {
-        new BrooklynEntityDecorationResolver.PolicySpecResolver(resolver.getYamlLoader()).decorate(spec, resolver.getAttrs());
-        new BrooklynEntityDecorationResolver.EnricherSpecResolver(resolver.getYamlLoader()).decorate(spec, resolver.getAttrs());
-        new BrooklynEntityDecorationResolver.InitializerResolver(resolver.getYamlLoader()).decorate(spec, resolver.getAttrs());
-    }
-
-    protected CatalogItem<Entity,EntitySpec<?>> getCatalogItemImpl(ManagementContext mgmt, String brooklynType) {
-        brooklynType = DeserializingClassRenamesProvider.findMappedName(brooklynType);
-        return CatalogUtils.getCatalogItemOptionalVersion(mgmt, Entity.class,  brooklynType);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceSpecResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceSpecResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceSpecResolver.java
new file mode 100644
index 0000000..19a76b3
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceSpecResolver.java
@@ -0,0 +1,110 @@
+/*
+ * 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.camp.brooklyn.spi.creation.service;
+
+import java.util.Set;
+
+import org.apache.brooklyn.api.catalog.CatalogItem;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
+import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
+import org.apache.brooklyn.core.mgmt.persist.DeserializingClassRenamesProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CatalogServiceSpecResolver extends AbstractServiceSpecResolver {
+    private static final Logger log = LoggerFactory.getLogger(CatalogServiceSpecResolver.class);
+
+    private static final String RESOLVER_NAME = "catalog";
+    private final ServiceSpecResolver hardcodedResolver;
+    
+    public CatalogServiceSpecResolver() {
+        super(RESOLVER_NAME);
+        hardcodedResolver = new HardcodedCatalogServiceSpecResolver();
+    }
+
+    @Override
+    protected boolean canResolve(String type, BrooklynClassLoadingContext loader) {
+        String localType = getLocalType(type);
+        CatalogItem<Entity, EntitySpec<?>> item = getCatalogItem(mgmt, localType);
+        if (item != null) {
+            try {
+                //Keeps behaviour of previous functionality, but probably should throw instead when using disabled items.
+                checkUsable(item);
+                return true;
+            } catch (IllegalStateException e) {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public EntitySpec<?> resolve(String type, BrooklynClassLoadingContext loader, Set<String> encounteredTypes) {
+        String localType = getLocalType(type);
+        CatalogItem<Entity, EntitySpec<?>> item = getCatalogItem(mgmt, localType);
+        if (item != null) {
+            checkUsable(item);
+
+            //Take the symbolicName part of the catalog item only for recursion detection to prevent
+            //cross referencing of different versions. Not interested in non-catalog item types.
+            //Prevent catalog items self-referencing even if explicitly different version.
+            boolean firstOccurrence = encounteredTypes.add(item.getSymbolicName());
+            boolean nonRecursiveCall = firstOccurrence;
+            if (nonRecursiveCall) {
+                // CatalogItem generics are just getting in the way, better get rid of them, we
+                // are casting anyway.
+                @SuppressWarnings({ "rawtypes" })
+                CatalogItem rawItem = item;
+                @SuppressWarnings({ "rawtypes", "unchecked" })
+                AbstractBrooklynObjectSpec rawSpec = EntityManagementUtils.createCatalogSpec(mgmt, rawItem, encounteredTypes);
+                return (EntitySpec<?>) rawSpec;
+            } else {
+                return null;
+            }
+        } else {
+            return hardcodedResolver.resolve(type, loader, encounteredTypes);
+        }
+    }
+
+    private void checkUsable(CatalogItem<Entity, EntitySpec<?>> item) {
+        if (item.isDisabled()) {
+            throw new IllegalStateException("Illegal use of disabled catalog item "+item.getSymbolicName()+":"+item.getVersion());
+        } else if (item.isDeprecated()) {
+            log.warn("Use of deprecated catalog item "+item.getSymbolicName()+":"+item.getVersion());
+        }
+    }
+
+    protected CatalogItem<Entity,EntitySpec<?>> getCatalogItem(ManagementContext mgmt, String brooklynType) {
+        brooklynType = DeserializingClassRenamesProvider.findMappedName(brooklynType);
+        return CatalogUtils.getCatalogItemOptionalVersion(mgmt, Entity.class,  brooklynType);
+    }
+
+    @Override
+    public void injectManagementContext(ManagementContext mgmt) {
+        super.injectManagementContext(mgmt);
+        hardcodedResolver.injectManagementContext(mgmt);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceTypeResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceTypeResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceTypeResolver.java
deleted file mode 100644
index 94aa8fc..0000000
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/CatalogServiceTypeResolver.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.camp.brooklyn.spi.creation.service;
-
-import java.util.Map;
-
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.camp.spi.PlatformComponentTemplate;
-import org.apache.brooklyn.entity.brooklynnode.BrooklynNode;
-import org.apache.brooklyn.entity.group.DynamicCluster;
-import org.apache.brooklyn.entity.group.DynamicRegionsFabric;
-import org.apache.brooklyn.entity.java.VanillaJavaApp;
-import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.CaseFormat;
-import com.google.common.base.Converter;
-import com.google.common.collect.ImmutableMap;
-
-/**
- * This converts {@link PlatformComponentTemplate} instances whose type is prefixed {@code catalog:}
- * to Brooklyn {@link EntitySpec} instances.
- */
-public class CatalogServiceTypeResolver extends BrooklynServiceTypeResolver {
-
-    @SuppressWarnings("unused")
-    private static final Logger LOG = LoggerFactory.getLogger(ServiceTypeResolver.class);
-
-    // TODO currently a hardcoded list of aliases; would like that to come from mgmt somehow
-    private static final Map<String, String> CATALOG_TYPES = ImmutableMap.<String, String>builder()
-            .put("cluster", DynamicCluster.class.getName())
-            .put("fabric", DynamicRegionsFabric.class.getName())
-            .put("vanilla", VanillaSoftwareProcess.class.getName())
-            .put("software-process", VanillaSoftwareProcess.class.getName())
-            .put("java-app", VanillaJavaApp.class.getName())
-            .put("brooklyn-node", BrooklynNode.class.getName())
-            .put("web-app-cluster","org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster")
-            .build();
-
-    // Allow catalog-type or CatalogType as service type string
-    private static final Converter<String, String> FMT = CaseFormat.LOWER_HYPHEN.converterTo(CaseFormat.UPPER_CAMEL);
-
-    @Override
-    public String getTypePrefix() { return "catalog"; }
-
-    @Override
-    public String getBrooklynType(String serviceType) {
-        String type = super.getBrooklynType(serviceType);
-        if (type == null) return null;
-
-        for (String check : CATALOG_TYPES.keySet()) {
-            if (type.equals(check) || type.equals(FMT.convert(check))) {
-                return CATALOG_TYPES.get(check);
-            }
-        }
-
-        return type;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceSpecResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceSpecResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceSpecResolver.java
new file mode 100644
index 0000000..1910971
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceSpecResolver.java
@@ -0,0 +1,41 @@
+/*
+ * 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.camp.brooklyn.spi.creation.service;
+
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
+import org.apache.brooklyn.entity.chef.ChefConfig;
+import org.apache.brooklyn.entity.chef.ChefEntity;
+
+public class ChefServiceSpecResolver extends AbstractServiceSpecResolver {
+    private static final String RESOLVER_NAME = "chef";
+
+    public ChefServiceSpecResolver() {
+        super(RESOLVER_NAME);
+    }
+
+    @Override
+    public EntitySpec<?> resolve(String type, BrooklynClassLoadingContext loader, Set<String> encounteredTypes) {
+        return EntitySpec.create(ChefEntity.class)
+                .configure(ChefConfig.CHEF_COOKBOOK_PRIMARY_NAME, getLocalType(type));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceTypeResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceTypeResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceTypeResolver.java
deleted file mode 100644
index b44deb7..0000000
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ChefServiceTypeResolver.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.camp.brooklyn.spi.creation.service;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.api.catalog.CatalogItem;
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynComponentTemplateResolver;
-import org.apache.brooklyn.camp.spi.PlatformComponentTemplate;
-import org.apache.brooklyn.entity.chef.ChefConfig;
-import org.apache.brooklyn.entity.chef.ChefEntity;
-import org.apache.brooklyn.util.text.Strings;
-
-/**
- * This converts {@link PlatformComponentTemplate} instances whose type is prefixed {@code chef:}
- * to Brooklyn {@link EntitySpec} instances.
- */
-public class ChefServiceTypeResolver extends BrooklynServiceTypeResolver {
-
-    @SuppressWarnings("unused")
-    private static final Logger LOG = LoggerFactory.getLogger(ServiceTypeResolver.class);
-
-    @Override
-    public String getTypePrefix() { return "chef"; }
-
-    @Override
-    public String getBrooklynType(String serviceType) {
-        return ChefEntity.class.getName();
-    }
-
-    /** Chef items are not in the catalog. */
-    @Override
-    public CatalogItem<Entity, EntitySpec<?>> getCatalogItem(BrooklynComponentTemplateResolver resolver, String serviceType) {
-        return null;
-    }
-
-    @Override
-    public <T extends Entity> void decorateSpec(BrooklynComponentTemplateResolver resolver, EntitySpec<T> spec) {
-        spec.configure(ChefConfig.CHEF_COOKBOOK_PRIMARY_NAME, Strings.removeFromStart(resolver.getDeclaredType(), "chef:"));
-        super.decorateSpec(resolver, spec);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/DefaultServiceTypeResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/DefaultServiceTypeResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/DefaultServiceTypeResolver.java
deleted file mode 100644
index fdd57d5..0000000
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/DefaultServiceTypeResolver.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.camp.brooklyn.spi.creation.service;
-
-public class DefaultServiceTypeResolver extends BrooklynServiceTypeResolver {
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/DelegatingServiceSpecResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/DelegatingServiceSpecResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/DelegatingServiceSpecResolver.java
new file mode 100644
index 0000000..e1a2f19
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/DelegatingServiceSpecResolver.java
@@ -0,0 +1,127 @@
+/*
+ * 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.camp.brooklyn.spi.creation.service;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
+
+public class DelegatingServiceSpecResolver extends AbstractServiceSpecResolver {
+    private static final String RESOLVER_PREFIX_CATALOG = "catalog:";
+
+    private static final String RESOLVER_PREFIX_JAVA = "java:";
+
+    private static final Logger log = LoggerFactory.getLogger(DelegatingServiceSpecResolver.class);
+
+    private static final String RESOLVER_NAME = "brooklyn";
+
+    private Collection<ServiceSpecResolver> resolvers;
+
+    public DelegatingServiceSpecResolver(@Nonnull ManagementContext mgmt, @Nonnull List<ServiceSpecResolver> overridingResolvers) {
+        super(RESOLVER_NAME);
+        this.resolvers = ImmutableList.<ServiceSpecResolver>builder()
+                .addAll(overridingResolvers)
+                .addAll(ServiceLoader.load(ServiceSpecResolver.class))
+                .build();
+        for (ServiceSpecResolver resolver : resolvers) {
+            resolver.injectManagementContext(mgmt);
+        }
+
+        injectManagementContext(mgmt);
+    }
+
+    @Override
+    public boolean accepts(String type, BrooklynClassLoadingContext loader) {
+        return accepts("", type, loader) ||
+                accepts(RESOLVER_PREFIX_CATALOG, type, loader) ||
+                accepts(RESOLVER_PREFIX_JAVA, type, loader);
+    }
+
+    private boolean accepts(String prefix, String type, BrooklynClassLoadingContext loader) {
+        for (ServiceSpecResolver resolver : resolvers) {
+            String localType = getLocalType(type);
+            if (resolver.accepts(prefix + localType, loader)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public EntitySpec<?> resolve(String type, BrooklynClassLoadingContext loader, Set<String> encounteredTypes) {
+        String localType = getLocalType(type);
+
+        EntitySpec<?> spec = resolve(resolvers, localType, loader, encounteredTypes);
+        if (spec != null) {
+            return spec;
+        }
+        spec = resolve(resolvers, RESOLVER_PREFIX_CATALOG + localType, loader, encounteredTypes);
+        if (spec != null) {
+            return spec;
+        }
+        return resolve(resolvers, RESOLVER_PREFIX_JAVA + localType, loader, encounteredTypes);
+    }
+
+    private EntitySpec<?> resolve(
+            Collection<ServiceSpecResolver> resolvers,
+            String localType,
+            BrooklynClassLoadingContext loader,
+            Set<String> encounteredTypes) {
+        Collection<String> resolversWhoDontSupport = new ArrayList<String>();
+        Collection<Exception> otherProblemsFromResolvers = new ArrayList<Exception>();
+
+        for (ServiceSpecResolver resolver : resolvers) {
+            if (resolver.accepts(localType, loader)) {
+                try {
+                    EntitySpec<?> spec = resolver.resolve(localType, loader, encounteredTypes);
+                    if (spec != null) {
+                        return spec;
+                    } else {
+                        resolversWhoDontSupport.add(resolver.getName() + " (returned null)");
+                    }
+                } catch (Exception e) {
+                    otherProblemsFromResolvers.add(new PropagatedRuntimeException("Transformer for "+resolver.getName()+" gave an error creating this plan: "+
+                            Exceptions.collapseText(e), e));
+                }
+            }
+        }
+        if (!otherProblemsFromResolvers.isEmpty()) {
+            // at least one thought he could do it
+            log.debug("Type " + localType + " could not be resolved; failure will be propagated (other transformers tried = "+resolversWhoDontSupport+"): "+otherProblemsFromResolvers);
+            throw otherProblemsFromResolvers.size()==1 ? Exceptions.create(null, otherProblemsFromResolvers) :
+                Exceptions.create("ServiceSpecResolvers all failed", otherProblemsFromResolvers);
+        }
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/HardcodedCatalogServiceSpecResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/HardcodedCatalogServiceSpecResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/HardcodedCatalogServiceSpecResolver.java
new file mode 100644
index 0000000..2a3d7cd
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/HardcodedCatalogServiceSpecResolver.java
@@ -0,0 +1,95 @@
+/*
+ * 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.camp.brooklyn.spi.creation.service;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
+import org.apache.brooklyn.entity.brooklynnode.BrooklynNode;
+import org.apache.brooklyn.entity.group.DynamicCluster;
+import org.apache.brooklyn.entity.group.DynamicRegionsFabric;
+import org.apache.brooklyn.entity.java.VanillaJavaApp;
+import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Converter;
+import com.google.common.collect.ImmutableMap;
+
+public class HardcodedCatalogServiceSpecResolver extends AbstractServiceSpecResolver {
+    private static final String RESOLVER_NAME = "catalog";
+
+    private static final Map<String, String> CATALOG_TYPES = ImmutableMap.<String, String>builder()
+            .put("cluster", DynamicCluster.class.getName())
+            .put("fabric", DynamicRegionsFabric.class.getName())
+            .put("vanilla", VanillaSoftwareProcess.class.getName())
+            .put("software-process", VanillaSoftwareProcess.class.getName())
+            .put("java-app", VanillaJavaApp.class.getName())
+            .put("brooklyn-node", BrooklynNode.class.getName())
+            .put("web-app-cluster","org.apache.brooklyn.entity.webapp.ControlledDynamicWebAppCluster")
+            .build();
+
+    // Allow catalog-type or CatalogType as service type string
+    private static final Converter<String, String> FMT = CaseFormat.UPPER_CAMEL.converterTo(CaseFormat.LOWER_HYPHEN);
+
+    public HardcodedCatalogServiceSpecResolver() {
+        super(RESOLVER_NAME);
+    }
+
+    @Override
+    protected boolean canResolve(String type, BrooklynClassLoadingContext loader) {
+        String localType = getLocalType(type);
+        String specType = getImplementation(localType);
+        return specType != null;
+    }
+
+    @Override
+    public EntitySpec<?> resolve(String type, BrooklynClassLoadingContext loader, Set<String> encounteredTypes) {
+        String localType = getLocalType(type);
+        String specType = getImplementation(localType);
+        if (specType != null) {
+            return buildSpec(specType);
+        } else {
+            return null;
+        }
+    }
+
+    private String getImplementation(String type) {
+        String specType = CATALOG_TYPES.get(type);
+        if (specType != null) {
+            return specType;
+        } else {
+            return CATALOG_TYPES.get(FMT.convert(type));
+        }
+    }
+
+    private EntitySpec<?> buildSpec(String specType) {
+        // TODO is this hardcoded list deprecated? If so log a warning.
+        try {
+            @SuppressWarnings("unchecked")
+            Class<Entity> specClass = (Class<Entity>)mgmt.getCatalogClassLoader().loadClass(specType);
+            return EntitySpec.create(specClass);
+        } catch (ClassNotFoundException e) {
+            throw new IllegalStateException("Unable to load hardcoded catalog type " + specType, e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceSpecResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceSpecResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceSpecResolver.java
new file mode 100644
index 0000000..fa0f9e5
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceSpecResolver.java
@@ -0,0 +1,91 @@
+/*
+ * 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.camp.brooklyn.spi.creation.service;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.Application;
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
+import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.javalang.Reflections;
+
+public class JavaServiceSpecResolver extends AbstractServiceSpecResolver{
+    private static final String RESOLVER_NAME = "java";
+
+    public JavaServiceSpecResolver() {
+        super(RESOLVER_NAME);
+    }
+
+    @Override
+    protected boolean canResolve(String type, BrooklynClassLoadingContext loader) {
+        String localType = getLocalType(type);
+        Maybe<?> javaType = tryLoadJavaType(localType, loader);
+        return javaType.isPresent();
+    }
+
+    @Override
+    public EntitySpec<?> resolve(String type, BrooklynClassLoadingContext loader, Set<String> encounteredTypes) {
+        String localType = getLocalType(type);
+        try {
+            return resolveInternal(localType, loader);
+        } catch (Exception e) {
+            boolean firstOccurrence = encounteredTypes.add(localType);
+            boolean recursiveButTryJava = !firstOccurrence;
+            if (recursiveButTryJava) {
+                throw new IllegalStateException("Recursive reference to " + localType + " (and cannot be resolved as a Java type)", e);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    private EntitySpec<?> resolveInternal(String localType, BrooklynClassLoadingContext loader) {
+        Maybe<Class<? extends Entity>> javaTypeMaybe = tryLoadJavaType(localType, loader);
+        if (javaTypeMaybe.isAbsent())
+            throw new IllegalStateException("Could not find "+localType, ((Maybe.Absent<?>)javaTypeMaybe).getException());
+        Class<? extends Entity> javaType = javaTypeMaybe.get();
+
+        EntitySpec<? extends Entity> spec;
+        if (javaType.isInterface()) {
+            spec = EntitySpec.create(javaType);
+        } else {
+            // If this is a concrete class, particularly for an Application class, we want the proxy
+            // to expose all interfaces it implements.
+            Class<? extends Entity> interfaceclazz = (Application.class.isAssignableFrom(javaType)) ? Application.class : Entity.class;
+            List<Class<?>> additionalInterfaceClazzes = Reflections.getAllInterfaces(javaType);
+            @SuppressWarnings({ "rawtypes", "unchecked" })
+            EntitySpec<?> rawSpec = EntitySpec.create(interfaceclazz)
+                .impl((Class) javaType)
+                .additionalInterfaces(additionalInterfaceClazzes);
+            spec = rawSpec;
+        }
+        spec.catalogItemId(CatalogUtils.getCatalogItemIdFromLoader(loader));
+
+        return spec;
+    }
+
+    private Maybe<Class<? extends Entity>> tryLoadJavaType(String localType, BrooklynClassLoadingContext loader) {
+        return loader.tryLoadClass(localType, Entity.class);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceTypeResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceTypeResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceTypeResolver.java
deleted file mode 100644
index d6c52f4..0000000
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/JavaServiceTypeResolver.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.camp.brooklyn.spi.creation.service;
-
-import org.apache.brooklyn.api.entity.EntitySpec;
-import org.apache.brooklyn.camp.spi.PlatformComponentTemplate;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This converts {@link PlatformComponentTemplate} instances whose type is prefixed {@code java:}
- * to Brooklyn {@link EntitySpec} instances.
- */
-public class JavaServiceTypeResolver extends BrooklynServiceTypeResolver {
-
-    @SuppressWarnings("unused")
-    private static final Logger LOG = LoggerFactory.getLogger(ServiceTypeResolver.class);
-
-    @Override
-    public String getTypePrefix() { return "java"; }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceSpecResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceSpecResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceSpecResolver.java
new file mode 100644
index 0000000..00d6dd5
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceSpecResolver.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.camp.brooklyn.spi.creation.service;
+
+import java.util.ServiceLoader;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.core.mgmt.ManagementContextInjectable;
+import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
+
+/**
+ * Resolves and decorates {@link EntitySpec entity specifications} based on the {@code serviceType} in a template.
+ * <p>
+ * The resolver implementation will use the rest of the local part of the service type information
+ * to create and decorate an appropriate {@link EntitySpec entity}.
+ * <p>
+ * The resolvers are loaded using the {@link ServiceLoader} mechanism, allowing external libraries
+ * to add extra service type implementations that will be picked up at runtime.
+ */
+// TODO Not CAMP specific, move to core, to be reused by other parsers
+public interface ServiceSpecResolver extends ManagementContextInjectable {
+    /**
+     * Uniquely identifies the resolver, can be used to address the same resolver at a later point in time.
+     * For implementations: this usually matches the service type prefix, but not required.
+     */
+    String getName();
+
+    /**
+     * @return if the resolver can create a spec for the service type
+     */
+    boolean accepts(String type, BrooklynClassLoadingContext loader);
+
+    /**
+     * Create a spec for the service type
+     */
+    @Nullable EntitySpec<?> resolve(String type, BrooklynClassLoadingContext loader, Set<String> encounteredTypes);
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolver.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolver.java
index dcbd971..c1399ff 100644
--- a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolver.java
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolver.java
@@ -37,7 +37,10 @@ import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynComponentTemplateR
  *
  * @see BrooklynServiceTypeResolver
  * @see ChefServiceTypeResolver
+ * 
+ * @deprecated since 0.9.0, {@link ServiceSpecResolver} instead.
  */
+@Deprecated
 public interface ServiceTypeResolver {
 
     String DEFAULT_TYPE_PREFIX = "brooklyn";

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/4485fe5c/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolverAdaptor.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolverAdaptor.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolverAdaptor.java
new file mode 100644
index 0000000..60abd40
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/service/ServiceTypeResolverAdaptor.java
@@ -0,0 +1,62 @@
+/*
+ * 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.camp.brooklyn.spi.creation.service;
+
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.EntitySpec;
+import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynComponentTemplateResolver;
+import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import groovy.xml.Entity;
+
+@SuppressWarnings("deprecation")
+public class ServiceTypeResolverAdaptor extends AbstractServiceSpecResolver {
+    private static final Logger log = LoggerFactory.getLogger(ServiceTypeResolverAdaptor.class);
+    private ServiceTypeResolver serviceTypeResolver;
+    private BrooklynComponentTemplateResolver resolver;
+
+    public ServiceTypeResolverAdaptor(BrooklynComponentTemplateResolver resolver, ServiceTypeResolver serviceTypeResolver) {
+        super(serviceTypeResolver.getTypePrefix());
+        this.serviceTypeResolver = serviceTypeResolver;
+        this.resolver = resolver;
+    }
+
+    @Override
+    public boolean accepts(String type, BrooklynClassLoadingContext loader) {
+        return true;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public EntitySpec<?> resolve(String type, BrooklynClassLoadingContext loader, Set<String> encounteredTypes) {
+        // Assume this is interface! Only known implementation is DockerServiceTypeResolver.
+        String brooklynType = serviceTypeResolver.getBrooklynType(type);
+        Class<? extends Entity> javaType = loader.loadClass(brooklynType, Entity.class);
+        if (!javaType.isInterface()) {
+            log.warn("Using " + ServiceTypeResolver.class.getSimpleName() + " with a non-interface type - this usage is not supported. Use " + ServiceSpecResolver.class.getSimpleName() + " instead.");
+        }
+        EntitySpec<?> spec = EntitySpec.create((Class)javaType);
+        serviceTypeResolver.decorateSpec(resolver, spec);
+        return spec;
+    }
+
+}