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/05 22:56:19 UTC

[15/20] incubator-brooklyn git commit: Package rename to org.apache.brooklyn: usage/camp/

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java
new file mode 100644
index 0000000..e30f7f2
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslInterpreter.java
@@ -0,0 +1,188 @@
+/*
+ * 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.dsl;
+
+import io.brooklyn.camp.spi.resolve.PlanInterpreter;
+import io.brooklyn.camp.spi.resolve.PlanInterpreter.PlanInterpreterAdapter;
+import io.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode;
+import io.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode.Role;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.BrooklynDslCommon;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.parse.DslParser;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.parse.FunctionWithArgs;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.parse.QuotedString;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.javalang.Reflections;
+import brooklyn.util.text.Strings;
+
+import com.google.common.base.Optional;
+
+/**
+ * {@link PlanInterpreter} which understands the $brooklyn DSL
+ */
+public class BrooklynDslInterpreter extends PlanInterpreterAdapter {
+
+    private static final Logger log = LoggerFactory.getLogger(BrooklynDslInterpreter.class);
+
+    @Override
+    public boolean isInterestedIn(PlanInterpretationNode node) {
+        return node.matchesPrefix("$brooklyn:") || node.getNewValue() instanceof FunctionWithArgs;
+    }
+
+    private static ThreadLocal<PlanInterpretationNode> currentNode = new ThreadLocal<PlanInterpretationNode>();
+    /** returns the current node, stored in a thread-local, to populate the dsl field of {@link BrooklynDslDeferredSupplier} instances */
+    public static PlanInterpretationNode currentNode() {
+        return currentNode.get();
+    }
+    /** sets the current node */
+    public static void currentNode(PlanInterpretationNode node) {
+        currentNode.set(node);
+    }
+    public static void currentNodeClear() {
+        currentNode.set(null);
+    }
+    
+    @Override
+    public void applyYamlPrimitive(PlanInterpretationNode node) {
+        String expression = node.getNewValue().toString();
+
+        try {
+            currentNode.set(node);
+            Object parsedNode = new DslParser(expression).parse();
+            if ((parsedNode instanceof FunctionWithArgs) && ((FunctionWithArgs)parsedNode).getArgs()==null) {
+                if (node.getRoleInParent() == Role.MAP_KEY) {
+                    node.setNewValue(parsedNode);
+                    // will be handled later
+                } else {
+                    throw new IllegalStateException("Invalid function-only expression '"+((FunctionWithArgs)parsedNode).getFunction()+"'");
+                }
+            } else {
+                node.setNewValue( evaluate(parsedNode, true) );
+            }
+        } catch (Exception e) {
+            log.warn("Error evaluating node (rethrowing) '"+expression+"': "+e);
+            Exceptions.propagateIfFatal(e);
+            throw new IllegalArgumentException("Error evaluating node '"+expression+"'", e);
+        } finally {
+            currentNodeClear();
+        }
+    }
+    
+    @Override
+    public boolean applyMapEntry(PlanInterpretationNode node, Map<Object, Object> mapIn, Map<Object, Object> mapOut,
+            PlanInterpretationNode key, PlanInterpretationNode value) {
+        if (key.getNewValue() instanceof FunctionWithArgs) {
+            try {
+                currentNode.set(node);
+
+                FunctionWithArgs f = (FunctionWithArgs) key.getNewValue();
+                if (f.getArgs()!=null)
+                    throw new IllegalStateException("Invalid map key function "+f.getFunction()+"; should not have arguments if taking arguments from map");
+
+                // means evaluation acts on values
+                List<Object> args = new ArrayList<Object>();
+                if (value.getNewValue() instanceof Iterable<?>) {
+                    for (Object vi: (Iterable<?>)value.getNewValue())
+                        args.add(vi);
+                } else {
+                    args.add(value.getNewValue());
+                }
+
+                try {
+                    // TODO in future we should support functions of the form 'Maps.clear', 'Maps.reset', 'Maps.remove', etc;
+                    // default approach only supported if mapIn has single item and mapOut is empty
+                    if (mapIn.size()!=1) 
+                        throw new IllegalStateException("Map-entry DSL syntax only supported with single item in map, not "+mapIn);
+                    if (mapOut.size()!=0) 
+                        throw new IllegalStateException("Map-entry DSL syntax only supported with empty output map-so-far, not "+mapOut);
+
+                    node.setNewValue( evaluate(new FunctionWithArgs(f.getFunction(), args), false) );
+                    return false;
+                } catch (Exception e) {
+                    log.warn("Error evaluating map-entry (rethrowing) '"+f.getFunction()+args+"': "+e);
+                    Exceptions.propagateIfFatal(e);
+                    throw new IllegalArgumentException("Error evaluating map-entry '"+f.getFunction()+args+"'", e);
+                }
+
+            } finally {
+                currentNodeClear();
+            }
+        }
+        return super.applyMapEntry(node, mapIn, mapOut, key, value);
+    }
+
+    public Object evaluate(Object f, boolean deepEvaluation) {
+        if (f instanceof FunctionWithArgs) {
+            return evaluateOn(BrooklynDslCommon.class, (FunctionWithArgs) f, deepEvaluation);
+        }
+        
+        if (f instanceof List) {
+            Object o = BrooklynDslCommon.class;
+            for (Object i: (List<?>)f) {
+                o = evaluateOn( o, (FunctionWithArgs)i, deepEvaluation );
+            }
+            return o;
+        }
+
+        if (f instanceof QuotedString) {
+            return ((QuotedString)f).unwrapped();
+        }
+
+        throw new IllegalArgumentException("Unexpected element in parse tree: '"+f+"' (type "+(f!=null ? f.getClass() : null)+")");
+    }
+    
+    public Object evaluateOn(Object o, FunctionWithArgs f, boolean deepEvaluation) {
+        if (f.getArgs()==null)
+            throw new IllegalStateException("Invalid function-only expression '"+f.getFunction()+"'");
+
+        Class<?> clazz;
+        if (o instanceof Class) {
+            clazz = (Class<?>)o;
+        } else {
+            clazz = o.getClass();
+        }
+        if (!(clazz.getPackage().getName().startsWith(BrooklynDslCommon.class.getPackage().getName())))
+            throw new IllegalArgumentException("Not permitted to invoke function on '"+clazz+"' (outside allowed package scope)");
+        
+        String fn = f.getFunction();
+        fn = Strings.removeFromStart(fn, "$brooklyn:");
+        try {
+            List<Object> args = new ArrayList<Object>();
+            for (Object arg: f.getArgs()) {
+                args.add( deepEvaluation ? evaluate(arg, true) : arg );
+            }
+            Optional<Object> v = Reflections.invokeMethodWithArgs(o, fn, args);
+            if (v.isPresent()) return v.get();
+        } catch (Exception e) {
+            Exceptions.propagateIfFatal(e);
+            throw Exceptions.propagate(new InvocationTargetException(e, "Error invoking '"+fn+"' on '"+o+"'"));
+        }
+        
+        throw new IllegalArgumentException("No such function '"+fn+"' on "+o);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslUtils.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslUtils.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslUtils.java
new file mode 100644
index 0000000..e5194bf
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslUtils.java
@@ -0,0 +1,44 @@
+/*
+ * 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.dsl;
+
+import brooklyn.util.task.DeferredSupplier;
+
+import com.google.common.collect.Iterables;
+
+public class DslUtils {
+
+    /** true iff none of the args are deferred / tasks */
+    public static boolean resolved(Iterable<Object> args) {
+        return resolved(Iterables.toArray(args, Object.class));
+    }
+
+    /** true iff none of the args are deferred / tasks */
+    public static boolean resolved(final Object... args) {
+        boolean allResolved = true;
+        for (Object arg: args) {
+            if (arg instanceof DeferredSupplier<?>) {
+                allResolved = false;
+                break;
+            }
+        }
+        return allResolved;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
new file mode 100644
index 0000000..37bc043
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
@@ -0,0 +1,301 @@
+/*
+ * 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.dsl.methods;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampReservedKeys;
+import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynYamlTypeInstantiator;
+import org.apache.brooklyn.camp.brooklyn.spi.creation.EntitySpecConfiguration;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.DslUtils;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslComponent.Scope;
+import org.apache.commons.beanutils.BeanUtils;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.EntityDynamicType;
+import brooklyn.entity.trait.Configurable;
+import brooklyn.event.Sensor;
+import brooklyn.event.basic.DependentConfiguration;
+import brooklyn.management.Task;
+import brooklyn.management.TaskAdaptable;
+import brooklyn.management.TaskFactory;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.config.ConfigBag;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.ClassCoercionException;
+import brooklyn.util.flags.FlagUtils;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.javalang.Reflections;
+import brooklyn.util.task.DeferredSupplier;
+import brooklyn.util.text.StringEscapes.JavaStringEscapes;
+import brooklyn.util.text.Strings;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+/** static import functions which can be used in `$brooklyn:xxx` contexts */
+public class BrooklynDslCommon {
+
+    // Access specific entities
+
+    public static DslComponent entity(String id) {
+        return new DslComponent(Scope.GLOBAL, id);
+    }
+    public static DslComponent parent() {
+        return new DslComponent(Scope.PARENT, null);
+    }
+    public static DslComponent child(String id) {
+        return new DslComponent(Scope.CHILD, id);
+    }
+    public static DslComponent sibling(String id) {
+        return new DslComponent(Scope.SIBLING, id);
+    }
+    public static DslComponent descendant(String id) {
+        return new DslComponent(Scope.DESCENDANT, id);
+    }
+    public static DslComponent ancestor(String id) {
+        return new DslComponent(Scope.ANCESTOR, id);
+    }
+    // prefer the syntax above to the below now, but not deprecating the below
+    public static DslComponent component(String id) {
+        return component("global", id);
+    }
+    public static DslComponent component(String scope, String id) {
+        if (!DslComponent.Scope.isValid(scope)) {
+            throw new IllegalArgumentException(scope + " is not a valid scope");
+        }
+        return new DslComponent(DslComponent.Scope.fromString(scope), id);
+    }
+
+    // Access things on entities
+
+    public static BrooklynDslDeferredSupplier<?> config(String keyName) {
+        return new DslComponent(Scope.THIS, "").config(keyName);
+    }
+
+    public static BrooklynDslDeferredSupplier<?> attributeWhenReady(String sensorName) {
+        return new DslComponent(Scope.THIS, "").attributeWhenReady(sensorName);
+    }
+
+    /** Returns a {@link Sensor}, looking up the sensor on the context if available and using that,
+     * or else defining an untyped (Object) sensor */
+    public static BrooklynDslDeferredSupplier<Sensor<?>> sensor(String sensorName) {
+        return new DslComponent(Scope.THIS, "").sensor(sensorName);
+    }
+    
+    /** Returns a {@link Sensor} declared on the type (e.g. entity class) declared in the first argument. */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static Sensor<?> sensor(String clazzName, String sensorName) {
+        try {
+            // TODO Should use catalog's classloader, rather than Class.forName; how to get that? Should we return a future?!
+            Class<?> clazz = Class.forName(clazzName);
+            Sensor<?> sensor;
+            if (Entity.class.isAssignableFrom(clazz)) {
+                sensor = new EntityDynamicType((Class<? extends Entity>) clazz).getSensor(sensorName);
+            } else {
+                // Some non-entity classes (e.g. ServiceRestarter policy) declare sensors that other
+                // entities/policies/enrichers may wish to reference.
+                Map<String,Sensor<?>> sensors = EntityDynamicType.findSensors((Class)clazz, null);
+                sensor = sensors.get(sensorName);
+            }
+            if (sensor == null) {
+                // TODO could extend API to return a sensor of the given type; useful but makes API ambiguous in theory (unlikely in practise, but still...)
+                throw new IllegalArgumentException("Sensor " + sensorName + " not found on class " + clazzName);
+            }
+            return sensor;
+        } catch (ClassNotFoundException e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    // Build complex things
+
+    public static EntitySpecConfiguration entitySpec(Map<String, Object> arguments) {
+        return new EntitySpecConfiguration(arguments);
+    }
+
+    /**
+     * Return an instance of the specified class with its fields set according
+     * to the {@link Map} or a {@link BrooklynDslDeferredSupplier} if the arguments are not
+     * yet fully resolved.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object object(Map<String, Object> arguments) {
+        ConfigBag config = ConfigBag.newInstance(arguments);
+        String typeName = BrooklynYamlTypeInstantiator.InstantiatorFromKey.extractTypeName("object", config).orNull();
+        Map<String,Object> objectFields = (Map<String, Object>) config.getStringKeyMaybe("object.fields").or(MutableMap.of());
+        Map<String,Object> brooklynConfig = (Map<String, Object>) config.getStringKeyMaybe(BrooklynCampReservedKeys.BROOKLYN_CONFIG).or(MutableMap.of());
+        try {
+            // TODO Should use catalog's classloader, rather than Class.forName; how to get that? Should we return a future?!
+            Class<?> type = Class.forName(typeName);
+            if (!Reflections.hasNoArgConstructor(type)) {
+                throw new IllegalStateException(String.format("Cannot construct %s bean: No public no-arg constructor available", type));
+            }
+            if ((objectFields.isEmpty() || DslUtils.resolved(objectFields.values())) &&
+                    (brooklynConfig.isEmpty() || DslUtils.resolved(brooklynConfig.values()))) {
+                return DslObject.create(type, objectFields, brooklynConfig);
+            } else {
+                return new DslObject(type, objectFields, brooklynConfig);
+            }
+        } catch (ClassNotFoundException e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    // String manipulation
+
+    /** Return the expression as a literal string without any further parsing. */
+    public static Object literal(Object expression) {
+        return expression;
+    }
+
+    /**
+     * Returns a formatted string or a {@link BrooklynDslDeferredSupplier} if the arguments
+     * are not yet fully resolved.
+     */
+    public static Object formatString(final String pattern, final Object...args) {
+        if (DslUtils.resolved(args)) {
+            // if all args are resolved, apply the format string now
+            return String.format(pattern, args);
+        } else {
+            return new DslFormatString(pattern, args);
+        }
+    }
+
+    /**
+     * Deferred execution of String formatting.
+     *
+     * @see DependentConfiguration#formatString(String, Object...)
+     */
+    protected static class DslFormatString extends BrooklynDslDeferredSupplier<String> {
+
+        private static final long serialVersionUID = -4849297712650560863L;
+
+        private String pattern;
+        private Object[] args;
+
+        public DslFormatString(String pattern, Object ...args) {
+            this.pattern = pattern;
+            this.args = args;
+        }
+
+        @Override
+        public Task<String> newTask() {
+            return DependentConfiguration.formatString(pattern, args);
+        }
+
+        @Override
+        public String toString() {
+            return "$brooklyn:formatString("+
+                JavaStringEscapes.wrapJavaString(pattern)+
+                (args==null || args.length==0 ? "" : ","+Strings.join(args, ","))+")";
+        }
+    }
+
+    /** @deprecated since 0.7.0; use {@link DslFormatString} */
+    @SuppressWarnings("serial")
+    @Deprecated
+    protected static class FormatString extends DslFormatString {
+        public FormatString(String pattern, Object[] args) {
+            super(pattern, args);
+        }
+    }
+
+    /** Deferred execution of Object creation. */
+    protected static class DslObject extends BrooklynDslDeferredSupplier<Object> {
+
+        private static final long serialVersionUID = 8878388748085419L;
+
+        private Class<?> type;
+        private Map<String,Object> fields, config;
+
+        public DslObject(Class<?> type, Map<String,Object> fields,  Map<String,Object> config) {
+            this.type = type;
+            this.fields = MutableMap.copyOf(fields);
+            this.config = MutableMap.copyOf(config);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public Task<Object> newTask() {
+            List<TaskAdaptable<Object>> tasks = Lists.newLinkedList();
+            for (Object value : Iterables.concat(fields.values(), config.values())) {
+                if (value instanceof TaskAdaptable) {
+                    tasks.add((TaskAdaptable<Object>) value);
+                } else if (value instanceof TaskFactory) {
+                    tasks.add(((TaskFactory<TaskAdaptable<Object>>) value).newTask());
+                }
+            }
+            Map<String,?> flags = MutableMap.<String,String>of("displayName", "building '"+type+"' with "+tasks.size()+" task"+(tasks.size()!=1?"s":""));
+            return DependentConfiguration.transformMultiple(flags, new Function<List<Object>, Object>() {
+                        @Override
+                        public Object apply(List<Object> input) {
+                            Iterator<Object> values = input.iterator();
+                            for (String name : fields.keySet()) {
+                                Object value = fields.get(name);
+                                if (value instanceof TaskAdaptable || value instanceof TaskFactory) {
+                                    fields.put(name, values.next());
+                                } else if (value instanceof DeferredSupplier) {
+                                    fields.put(name, ((DeferredSupplier<?>) value).get());
+                                }
+                            }
+                            for (String name : config.keySet()) {
+                                Object value = config.get(name);
+                                if (value instanceof TaskAdaptable || value instanceof TaskFactory) {
+                                    config.put(name, values.next());
+                                } else if (value instanceof DeferredSupplier) {
+                                    config.put(name, ((DeferredSupplier<?>) value).get());
+                                }
+                            }
+                            return create(type, fields, config);
+                        }
+                    }, tasks);
+        }
+
+        public static <T> T create(Class<T> type, Map<String,?> fields, Map<String,?> config) {
+            try {
+                T bean;
+                try {
+                    bean = (T) TypeCoercions.coerce(fields, type);
+                } catch (ClassCoercionException ex) {
+                    bean = Reflections.invokeConstructorWithArgs(type).get();
+                    BeanUtils.populate(bean, fields);
+                }
+                if (bean instanceof Configurable && config.size() > 0) {
+                    ConfigBag brooklyn = ConfigBag.newInstance(config);
+                    FlagUtils.setFieldsFromFlags(bean, brooklyn);
+                    FlagUtils.setAllConfigKeys((Configurable) bean, brooklyn, true);
+                }
+                return bean;
+            } catch (Exception e) {
+                throw Exceptions.propagate(e);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "$brooklyn:object(\""+type.getName()+"\")";
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
new file mode 100644
index 0000000..4801a41
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
@@ -0,0 +1,320 @@
+/*
+ * 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.dsl.methods;
+
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants;
+import org.apache.brooklyn.camp.brooklyn.spi.dsl.BrooklynDslDeferredSupplier;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.basic.EntityPredicates;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.Sensor;
+import brooklyn.event.basic.DependentConfiguration;
+import brooklyn.event.basic.Sensors;
+import brooklyn.management.Task;
+import brooklyn.management.internal.EntityManagerInternal;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.task.TaskBuilder;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.text.StringEscapes.JavaStringEscapes;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class DslComponent extends BrooklynDslDeferredSupplier<Entity> {
+
+    private static final long serialVersionUID = -7715984495268724954L;
+    
+    private final String componentId;
+    private final DslComponent scopeComponent;
+    private final Scope scope;
+
+    public DslComponent(String componentId) {
+        this(Scope.GLOBAL, componentId);
+    }
+    
+    public DslComponent(Scope scope, String componentId) {
+        this(null, scope, componentId);
+    }
+    
+    public DslComponent(DslComponent scopeComponent, Scope scope, String componentId) {
+        Preconditions.checkNotNull(scope, "scope");
+        this.scopeComponent = scopeComponent;
+        this.componentId = componentId;
+        this.scope = scope;
+    }
+
+    // ---------------------------
+    
+    @Override
+    public Task<Entity> newTask() {
+        return TaskBuilder.<Entity>builder().name(toString()).tag(BrooklynTaskTags.TRANSIENT_TASK_TAG)
+            .body(new EntityInScopeFinder(scopeComponent, scope, componentId)).build();
+    }
+    
+    protected static class EntityInScopeFinder implements Callable<Entity> {
+        protected final DslComponent scopeComponent;
+        protected final Scope scope;
+        protected final String componentId;
+
+        public EntityInScopeFinder(DslComponent scopeComponent, Scope scope, String componentId) {
+            this.scopeComponent = scopeComponent;
+            this.scope = scope;
+            this.componentId = componentId;
+        }
+
+        protected EntityInternal getEntity() {
+            if (scopeComponent!=null) {
+                return (EntityInternal)scopeComponent.get();
+            } else {
+                return entity();
+            }
+        }
+        
+        @Override
+        public Entity call() throws Exception {
+            Iterable<Entity> entitiesToSearch = null;
+            switch (scope) {
+                case THIS:
+                    return getEntity();
+                case PARENT:
+                    return getEntity().getParent();
+                case GLOBAL:
+                    entitiesToSearch = ((EntityManagerInternal)getEntity().getManagementContext().getEntityManager())
+                        .getAllEntitiesInApplication( entity().getApplication() );
+                    break;
+                case DESCENDANT:
+                    entitiesToSearch = Entities.descendants(getEntity());
+                    break;
+                case ANCESTOR:
+                    entitiesToSearch = Entities.ancestors(getEntity());
+                    break;
+                case SIBLING:
+                    entitiesToSearch = getEntity().getParent().getChildren();
+                    break;
+                case CHILD:
+                    entitiesToSearch = getEntity().getChildren();
+                    break;
+                default:
+                    throw new IllegalStateException("Unexpected scope "+scope);
+            }
+            
+            Optional<Entity> result = Iterables.tryFind(entitiesToSearch, EntityPredicates.configEqualTo(BrooklynCampConstants.PLAN_ID, componentId));
+            
+            if (result.isPresent())
+                return result.get();
+            
+            // TODO may want to block and repeat on new entities joining?
+            throw new NoSuchElementException("No entity matching id " + componentId+
+                (scope==Scope.GLOBAL ? "" : ", in scope "+scope+" wrt "+getEntity()+
+                (scopeComponent!=null ? " ("+scopeComponent+" from "+entity()+")" : "")));
+        }        
+    }
+    
+    // -------------------------------
+
+    // DSL words which move to a new component
+    
+    public DslComponent entity(String scopeOrId) {
+        return new DslComponent(this, Scope.GLOBAL, scopeOrId);
+    }
+    public DslComponent child(String scopeOrId) {
+        return new DslComponent(this, Scope.CHILD, scopeOrId);
+    }
+    public DslComponent sibling(String scopeOrId) {
+        return new DslComponent(this, Scope.SIBLING, scopeOrId);
+    }
+    public DslComponent descendant(String scopeOrId) {
+        return new DslComponent(this, Scope.DESCENDANT, scopeOrId);
+    }
+    public DslComponent ancestor(String scopeOrId) {
+        return new DslComponent(this, Scope.ANCESTOR, scopeOrId);
+    }
+    
+    @Deprecated /** @deprecated since 0.7.0 */
+    public DslComponent component(String scopeOrId) {
+        return new DslComponent(this, Scope.GLOBAL, scopeOrId);
+    }
+    
+    public DslComponent parent() {
+        return new DslComponent(this, Scope.PARENT, "");
+    }
+    
+    public DslComponent component(String scope, String id) {
+        if (!DslComponent.Scope.isValid(scope)) {
+            throw new IllegalArgumentException(scope + " is not a vlaid scope");
+        }
+        return new DslComponent(this, DslComponent.Scope.fromString(scope), id);
+    }
+
+    // DSL words which return things
+    
+    public BrooklynDslDeferredSupplier<?> attributeWhenReady(final String sensorName) {
+        return new AttributeWhenReady(this, sensorName);
+    }
+    // class simply makes the memento XML files nicer
+    protected static class AttributeWhenReady extends BrooklynDslDeferredSupplier<Object> {
+        private static final long serialVersionUID = 1740899524088902383L;
+        private final DslComponent component;
+        private final String sensorName;
+        public AttributeWhenReady(DslComponent component, String sensorName) {
+            this.component = Preconditions.checkNotNull(component);
+            this.sensorName = sensorName;
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public Task<Object> newTask() {
+            Entity targetEntity = component.get();
+            Sensor<?> targetSensor = targetEntity.getEntityType().getSensor(sensorName);
+            if (!(targetSensor instanceof AttributeSensor<?>)) {
+                targetSensor = Sensors.newSensor(Object.class, sensorName);
+            }
+            return (Task<Object>) DependentConfiguration.attributeWhenReady(targetEntity, (AttributeSensor<?>)targetSensor);
+        }
+        @Override
+        public String toString() {
+            return (component.scope==Scope.THIS ? "" : component.toString()+".") +
+                "attributeWhenReady("+JavaStringEscapes.wrapJavaString(sensorName)+")";
+        }
+    }
+
+    public BrooklynDslDeferredSupplier<?> config(final String keyName) {
+        return new DslConfigSupplier(this, keyName);
+    }
+    protected final static class DslConfigSupplier extends BrooklynDslDeferredSupplier<Object> {
+        private final DslComponent component;
+        private final String keyName;
+        private static final long serialVersionUID = -4735177561947722511L;
+
+        public DslConfigSupplier(DslComponent component, String keyName) {
+            this.component = Preconditions.checkNotNull(component);
+            this.keyName = keyName;
+        }
+
+        @Override
+        public Task<Object> newTask() {
+            return Tasks.builder().name("retrieving config for "+keyName).tag(BrooklynTaskTags.TRANSIENT_TASK_TAG).dynamic(false).body(new Callable<Object>() {
+                @Override
+                public Object call() throws Exception {
+                    Entity targetEntity = component.get();
+                    return targetEntity.getConfig(ConfigKeys.newConfigKey(Object.class, keyName));
+                }
+            }).build();
+        }
+
+        @Override
+        public String toString() {
+            return (component.scope==Scope.THIS ? "" : component.toString()+".") + 
+                "config("+JavaStringEscapes.wrapJavaString(keyName)+")";
+        }
+    }
+    
+    public BrooklynDslDeferredSupplier<Sensor<?>> sensor(final String sensorName) {
+        return new DslSensorSupplier(this, sensorName);
+    }
+    protected final static class DslSensorSupplier extends BrooklynDslDeferredSupplier<Sensor<?>> {
+        private final DslComponent component;
+        private final String sensorName;
+        private static final long serialVersionUID = -4735177561947722511L;
+
+        public DslSensorSupplier(DslComponent component, String sensorName) {
+            this.component = Preconditions.checkNotNull(component);
+            this.sensorName = sensorName;
+        }
+
+        @Override
+        public Task<Sensor<?>> newTask() {
+            return Tasks.<Sensor<?>>builder().name("looking up sensor for "+sensorName).dynamic(false).body(new Callable<Sensor<?>>() {
+                @Override
+                public Sensor<?> call() throws Exception {
+                    Entity targetEntity = component.get();
+                    Sensor<?> result = null;
+                    if (targetEntity!=null) {
+                        result = targetEntity.getEntityType().getSensor(sensorName);
+                    }
+                    if (result!=null) return result;
+                    return Sensors.newSensor(Object.class, sensorName);
+                }
+            }).build();
+        }
+
+        @Override
+        public String toString() {
+            return (component.scope==Scope.THIS ? "" : component.toString()+".") + 
+                "sensor("+JavaStringEscapes.wrapJavaString(sensorName)+")";
+        }
+    }
+
+    public static enum Scope {
+        GLOBAL ("global"),
+        CHILD ("child"),
+        PARENT ("parent"),
+        SIBLING ("sibling"),
+        DESCENDANT ("descendant"),
+        ANCESTOR("ancestor"),
+        THIS ("this");
+        
+        public static final Set<Scope> VALUES = ImmutableSet.of(GLOBAL, CHILD, PARENT, SIBLING, DESCENDANT, ANCESTOR, THIS);
+        
+        private final String name;
+        
+        private Scope(String name) {
+            this.name = name;
+        }
+        
+        public static Scope fromString(String name) {
+            return tryFromString(name).get();
+        }
+        
+        public static Maybe<Scope> tryFromString(String name) {
+            for (Scope scope : VALUES)
+                if (scope.name.toLowerCase().equals(name.toLowerCase()))
+                    return Maybe.of(scope);
+            return Maybe.absent(new IllegalArgumentException(name + " is not a valid scope"));
+        }
+        
+        public static boolean isValid(String name) {
+            for (Scope scope : VALUES)
+                if (scope.name.toLowerCase().equals(name.toLowerCase()))
+                    return true;
+            return false;
+        }
+    }
+
+
+    @Override
+    public String toString() {
+        return "$brooklyn:entity("+
+            (scopeComponent==null ? "" : JavaStringEscapes.wrapJavaString(scopeComponent.toString())+", ")+
+            (scope==Scope.GLOBAL ? "" : JavaStringEscapes.wrapJavaString(scope.toString())+", ")+
+            JavaStringEscapes.wrapJavaString(componentId)+
+            ")";
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java
new file mode 100644
index 0000000..345de05
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/DslParser.java
@@ -0,0 +1,144 @@
+/*
+ * 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.dsl.parse;
+
+import java.util.Collection;
+import java.util.List;
+
+import brooklyn.util.collections.MutableList;
+
+public class DslParser {
+    private final String expression;
+    int index = -1;
+    
+    public DslParser(String expression) {
+        this.expression = expression;
+    }
+    
+    public synchronized Object parse() {
+        if (index>=0)
+            throw new IllegalStateException("Parser can only be used once");
+        
+        index++;
+        Object result = next();
+        
+        if (index < expression.length())
+            throw new IllegalStateException("Unexpected character at position "+index+" in "+expression);
+        
+        return result;
+    }
+    
+    @SuppressWarnings("unchecked")
+    public Object next() {
+        int start = index;
+        
+        skipWhitespace();
+        if (index >= expression.length())
+            throw new IllegalStateException("Unexpected end of expression to parse, looking for content since position "+start);
+        
+        if (expression.charAt(index)=='"') {
+            // assume a string
+            int stringStart = index;
+            index++;
+            do {
+                if (index >= expression.length())
+                    throw new IllegalStateException("Unexpected end of expression to parse, looking for close quote since position "+stringStart);
+                char c = expression.charAt(index);
+                if (c=='"') break;
+                if (c=='\\') index++;
+                index++;
+            } while (true);
+            index++;
+            return new QuotedString(expression.substring(stringStart, index));
+        }
+
+        // not a string, must be a function (or chain thereof)
+        List<FunctionWithArgs> result = new MutableList<FunctionWithArgs>();
+
+        int fnStart = index;
+        do {
+            if (index >= expression.length())
+                break;
+            char c = expression.charAt(index);
+            if (Character.isJavaIdentifierPart(c)) ;
+            // these chars also permitted
+            else if (".:".indexOf(c)>=0) ;
+            // other things e.g. whitespace, parentheses, etc, skip
+            else break;
+            index++;
+        } while (true);
+        String fn = expression.substring(fnStart, index);
+        if (fn.length()==0)
+            throw new IllegalStateException("Expected a function name at position "+start);
+        skipWhitespace();
+        
+        if (index < expression.length() && expression.charAt(index)=='(') {
+            // collect arguments
+            int parenStart = index;
+            List<Object> args = new MutableList<Object>();
+            index ++;
+            do {
+                skipWhitespace();
+                if (index >= expression.length())
+                    throw new IllegalStateException("Unexpected end of arguments to function '"+fn+"', no close parenthesis matching character at position "+parenStart);
+                char c = expression.charAt(index);
+                if (c==')') break;
+                if (c==',') {
+                    if (args.isEmpty())
+                        throw new IllegalStateException("Invalid character at position"+index);
+                    index++;
+                } else {
+                    if (!args.isEmpty())
+                        throw new IllegalStateException("Expected , before position"+index);
+                }
+                args.add(next());
+            } while (true);
+            result.add(new FunctionWithArgs(fn, args));
+            index++;
+            skipWhitespace();
+            if (index >= expression.length())
+                return result;
+            char c = expression.charAt(index);
+            if (c=='.') {
+                // chained expression
+                int chainStart = index;
+                index++;
+                Object next = next();
+                if (next instanceof List) {
+                    result.addAll((Collection<? extends FunctionWithArgs>) next);
+                    return result;
+                } else {
+                    throw new IllegalStateException("Expected functions following position"+chainStart);
+                }
+            } else {
+                // following word not something handled at this level; assume parent will handle (or throw) - e.g. a , or extra )
+                return result;
+            }
+        } else {
+            // it is just a word; return it with args as null
+            return new FunctionWithArgs(fn, null);
+        }
+    }
+
+    private void skipWhitespace() {
+        while (index<expression.length() && Character.isWhitespace(expression.charAt(index)))
+            index++;
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/FunctionWithArgs.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/FunctionWithArgs.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/FunctionWithArgs.java
new file mode 100644
index 0000000..41bc837
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/FunctionWithArgs.java
@@ -0,0 +1,57 @@
+/*
+ * 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.dsl.parse;
+
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+
+public class FunctionWithArgs {
+    private final String function;
+    private final List<Object> args;
+    
+    public FunctionWithArgs(String function, List<Object> args) {
+        this.function = function;
+        this.args = args==null ? null : ImmutableList.copyOf(args);
+    }
+    
+    public String getFunction() {
+        return function;
+    }
+    
+    /**
+     * arguments (typically {@link QuotedString} or more {@link FunctionWithArgs}).
+     * 
+     * null means it is a function in a map key which expects map value to be the arguments -- specified without parentheses;
+     * empty means parentheses already applied, with 0 args.
+     */
+    public List<Object> getArgs() {
+        return args;
+    }
+    
+    @Override
+    public String toString() {
+        return function+(args==null ? "" : args);
+    }
+
+    public Object arg(int i) {
+        return args.get(i);
+    }
+    
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/QuotedString.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/QuotedString.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/QuotedString.java
new file mode 100644
index 0000000..8076bfb
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/parse/QuotedString.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dsl.parse;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import brooklyn.util.text.StringEscapes.JavaStringEscapes;
+
+import com.google.common.base.Objects;
+
+public class QuotedString {
+    private final String s;
+    
+    public QuotedString(String s) {
+        this.s = checkNotNull(s, "string");
+    }
+    @Override
+    public String toString() {
+        return s;
+    }
+    public String unwrapped() {
+        return JavaStringEscapes.unwrapJavaString(s);
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        return (obj instanceof QuotedString) && ((QuotedString)obj).toString().equals(toString());
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(s);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AbstractBrooklynResourceLookup.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AbstractBrooklynResourceLookup.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AbstractBrooklynResourceLookup.java
new file mode 100644
index 0000000..6f4670d
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AbstractBrooklynResourceLookup.java
@@ -0,0 +1,36 @@
+/*
+ * 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.lookup;
+
+import io.brooklyn.camp.spi.AbstractResource;
+import io.brooklyn.camp.spi.PlatformRootSummary;
+import io.brooklyn.camp.spi.collection.AbstractResourceLookup;
+import brooklyn.management.ManagementContext;
+
+public abstract class AbstractBrooklynResourceLookup<T extends AbstractResource>  extends AbstractResourceLookup<T> {
+
+    protected final PlatformRootSummary root;
+    protected final ManagementContext bmc;
+
+    public AbstractBrooklynResourceLookup(PlatformRootSummary root, ManagementContext bmc) {
+        this.root = root;
+        this.bmc = bmc;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AbstractTemplateBrooklynLookup.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AbstractTemplateBrooklynLookup.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AbstractTemplateBrooklynLookup.java
new file mode 100644
index 0000000..b7a276c
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AbstractTemplateBrooklynLookup.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.lookup;
+
+import io.brooklyn.camp.spi.AbstractResource;
+import io.brooklyn.camp.spi.PlatformRootSummary;
+import io.brooklyn.camp.spi.collection.ResolvableLink;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.internal.CatalogUtils;
+import brooklyn.entity.Entity;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.management.ManagementContext;
+
+public abstract class AbstractTemplateBrooklynLookup<T extends AbstractResource>  extends AbstractBrooklynResourceLookup<T> {
+
+    private static final Logger log = LoggerFactory.getLogger(AbstractTemplateBrooklynLookup.class);
+    
+    public AbstractTemplateBrooklynLookup(PlatformRootSummary root, ManagementContext bmc) {
+        super(root, bmc);
+    }
+
+    @Override
+    public T get(String id) {
+        CatalogItem<?,?> item = getCatalogItem(id);
+        if (item==null) {
+            log.warn("Could not find item '"+id+"' in Brooklyn catalog; returning null");
+            return null;
+        }
+        return adapt(item);
+    }
+
+    private CatalogItem<?, ?> getCatalogItem(String versionedId) {
+        return CatalogUtils.getCatalogItemOptionalVersion(bmc, versionedId);
+    }
+
+    public abstract T adapt(CatalogItem<?,?> item);
+
+    protected ResolvableLink<T> newLink(CatalogItem<? extends Entity,EntitySpec<?>> li) {
+        return newLink(li.getId(), li.getDisplayName());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AssemblyBrooklynLookup.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AssemblyBrooklynLookup.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AssemblyBrooklynLookup.java
new file mode 100644
index 0000000..e3130e0
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AssemblyBrooklynLookup.java
@@ -0,0 +1,69 @@
+/*
+ * 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.lookup;
+
+import io.brooklyn.camp.spi.Assembly;
+import io.brooklyn.camp.spi.PlatformRootSummary;
+import io.brooklyn.camp.spi.collection.ResolvableLink;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import brooklyn.entity.Application;
+import brooklyn.entity.Entity;
+import brooklyn.management.ManagementContext;
+
+
+public class AssemblyBrooklynLookup extends AbstractBrooklynResourceLookup<Assembly> {
+
+    private PlatformComponentBrooklynLookup pcs;
+
+    public AssemblyBrooklynLookup(PlatformRootSummary root, ManagementContext bmc, PlatformComponentBrooklynLookup pcs) {
+        super(root, bmc);
+        this.pcs = pcs;
+    }
+
+    @Override
+    public Assembly get(String id) {
+        Entity entity = bmc.getEntityManager().getEntity(id);
+        if (!(entity instanceof Application))
+            throw new IllegalArgumentException("Element for "+id+" is not an Application ("+entity+")");
+        Assembly.Builder<? extends Assembly> builder = Assembly.builder()
+                .created(new Date(entity.getCreationTime()))
+                .id(entity.getId())
+                .name(entity.getDisplayName());
+        
+        builder.customAttribute("externalManagementUri", BrooklynUrlLookup.getUrl(bmc, entity));
+        
+        for (Entity child: entity.getChildren())
+            // FIXME this walks the whole damn tree!
+            builder.add( pcs.get(child.getId() ));
+        return builder.build();
+    }
+
+    @Override
+    public List<ResolvableLink<Assembly>> links() {
+        List<ResolvableLink<Assembly>> result = new ArrayList<ResolvableLink<Assembly>>();
+        for (Application app: bmc.getApplications())
+            result.add(newLink(app.getId(), app.getDisplayName()));
+        return result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AssemblyTemplateBrooklynLookup.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AssemblyTemplateBrooklynLookup.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AssemblyTemplateBrooklynLookup.java
new file mode 100644
index 0000000..1e0d6a1
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/AssemblyTemplateBrooklynLookup.java
@@ -0,0 +1,70 @@
+/*
+ * 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.lookup;
+
+import io.brooklyn.camp.spi.AssemblyTemplate;
+import io.brooklyn.camp.spi.PlatformRootSummary;
+import io.brooklyn.camp.spi.collection.ResolvableLink;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.brooklyn.camp.brooklyn.spi.creation.BrooklynAssemblyTemplateInstantiator;
+import org.apache.brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogPredicates;
+import brooklyn.entity.Application;
+import brooklyn.entity.Entity;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.management.ManagementContext;
+
+public class AssemblyTemplateBrooklynLookup extends AbstractTemplateBrooklynLookup<AssemblyTemplate> {
+
+    public AssemblyTemplateBrooklynLookup(PlatformRootSummary root, ManagementContext bmc) {
+        super(root, bmc);
+    }
+
+    @Override
+    public AssemblyTemplate adapt(CatalogItem<?,?> item) {
+        return AssemblyTemplate.builder().
+                name(item.getDisplayName()).
+                id(item.getId()).
+                description(item.getDescription()).
+                created(root.getCreated()).
+                instantiator(BrooklynAssemblyTemplateInstantiator.class).
+                build();
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    // why can I not pass an EntitySpec<? extends Application> to    newLink(EntitySpec<?> spec)  ?
+    // feels to me (alexheneveld) that `? extends Application` should be both covariant and contravariant to `?` ..
+    // but it's not, so we introduce this conversion method
+    protected ResolvableLink<AssemblyTemplate> newApplicationLink(CatalogItem<? extends Entity, EntitySpec<? extends Application>> li) {
+        return super.newLink((CatalogItem)li);
+    }
+    
+    @Override
+    public List<ResolvableLink<AssemblyTemplate>> links() {
+        Iterable<CatalogItem<Application,EntitySpec<? extends Application>>> l = bmc.getCatalog().getCatalogItems(CatalogPredicates.IS_TEMPLATE);
+        List<ResolvableLink<AssemblyTemplate>> result = new ArrayList<ResolvableLink<AssemblyTemplate>>();
+        for (CatalogItem<Application,EntitySpec<? extends Application>> li: l)
+            result.add(newApplicationLink(li));
+        return result;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/BrooklynUrlLookup.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/BrooklynUrlLookup.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/BrooklynUrlLookup.java
new file mode 100644
index 0000000..f9bc390
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/BrooklynUrlLookup.java
@@ -0,0 +1,38 @@
+/*
+ * 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.lookup;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.management.ManagementContext;
+import brooklyn.util.net.Urls;
+
+public class BrooklynUrlLookup {
+
+    public static ConfigKey<String> BROOKLYN_ROOT_URL = ConfigKeys.newStringConfigKey("brooklyn.root.url");
+    
+    public static String getUrl(ManagementContext bmc, Entity entity) {
+        String root = bmc.getConfig().getConfig(BROOKLYN_ROOT_URL);
+        if (root==null) return null;
+        return Urls.mergePaths(root, "#/", 
+                "/v1/applications/"+entity.getApplicationId()+"/entities/"+entity.getId());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/PlatformComponentBrooklynLookup.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/PlatformComponentBrooklynLookup.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/PlatformComponentBrooklynLookup.java
new file mode 100644
index 0000000..6b26849
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/PlatformComponentBrooklynLookup.java
@@ -0,0 +1,61 @@
+/*
+ * 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.lookup;
+
+import io.brooklyn.camp.spi.PlatformComponent;
+import io.brooklyn.camp.spi.PlatformComponent.Builder;
+import io.brooklyn.camp.spi.PlatformRootSummary;
+import io.brooklyn.camp.spi.collection.ResolvableLink;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import brooklyn.entity.Entity;
+import brooklyn.management.ManagementContext;
+
+
+public class PlatformComponentBrooklynLookup extends AbstractBrooklynResourceLookup<PlatformComponent> {
+
+    public PlatformComponentBrooklynLookup(PlatformRootSummary root, ManagementContext bmc) {
+        super(root, bmc);
+    }
+
+    @Override
+    public PlatformComponent get(String id) {
+        Entity entity = bmc.getEntityManager().getEntity(id);
+        Builder<? extends PlatformComponent> builder = PlatformComponent.builder()
+            .created(new Date(entity.getCreationTime()))
+            .id(entity.getId())
+            .name(entity.getDisplayName())
+            .externalManagementUri(BrooklynUrlLookup.getUrl(bmc, entity));
+        
+        for (Entity child: entity.getChildren())
+            // FIXME this walks the whole damn tree!
+            builder.add( get(child.getId() ));
+        return builder.build();
+    }
+
+    // platform components are not listed at the top level -- you have to walk the assemblies
+    @Override
+    public List<ResolvableLink<PlatformComponent>> links() {
+        return Collections.emptyList();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/PlatformComponentTemplateBrooklynLookup.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/PlatformComponentTemplateBrooklynLookup.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/PlatformComponentTemplateBrooklynLookup.java
new file mode 100644
index 0000000..b990464
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/lookup/PlatformComponentTemplateBrooklynLookup.java
@@ -0,0 +1,59 @@
+/*
+ * 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.lookup;
+
+import io.brooklyn.camp.spi.PlatformComponentTemplate;
+import io.brooklyn.camp.spi.PlatformRootSummary;
+import io.brooklyn.camp.spi.collection.ResolvableLink;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogPredicates;
+import brooklyn.entity.Entity;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.management.ManagementContext;
+
+public class PlatformComponentTemplateBrooklynLookup extends AbstractTemplateBrooklynLookup<PlatformComponentTemplate> {
+
+    public PlatformComponentTemplateBrooklynLookup(PlatformRootSummary root, ManagementContext bmc) {
+        super(root, bmc);
+    }
+
+    @Override
+    public PlatformComponentTemplate adapt(CatalogItem<?,?> item) {
+        return PlatformComponentTemplate.builder().
+                name(item.getDisplayName()).
+                id(item.getId()).
+                description(item.getDescription()).
+                created(root.getCreated()).
+                build();
+    }
+
+    @Override
+    public List<ResolvableLink<PlatformComponentTemplate>> links() {
+        Iterable<CatalogItem<Entity,EntitySpec<?>>> l = bmc.getCatalog().getCatalogItems(CatalogPredicates.IS_ENTITY);
+        List<ResolvableLink<PlatformComponentTemplate>> result = new ArrayList<ResolvableLink<PlatformComponentTemplate>>();
+        for (CatalogItem<Entity,EntitySpec<?>> li: l)
+            result.add(newLink(li));
+        return result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/platform/BrooklynImmutableCampPlatform.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/platform/BrooklynImmutableCampPlatform.java b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/platform/BrooklynImmutableCampPlatform.java
new file mode 100644
index 0000000..c04c134
--- /dev/null
+++ b/usage/camp/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/platform/BrooklynImmutableCampPlatform.java
@@ -0,0 +1,109 @@
+/*
+ * 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.platform;
+
+import io.brooklyn.camp.CampPlatform;
+import io.brooklyn.camp.spi.ApplicationComponent;
+import io.brooklyn.camp.spi.ApplicationComponentTemplate;
+import io.brooklyn.camp.spi.Assembly;
+import io.brooklyn.camp.spi.AssemblyTemplate;
+import io.brooklyn.camp.spi.PlatformComponent;
+import io.brooklyn.camp.spi.PlatformComponentTemplate;
+import io.brooklyn.camp.spi.PlatformRootSummary;
+import io.brooklyn.camp.spi.PlatformTransaction;
+import io.brooklyn.camp.spi.collection.BasicResourceLookup;
+import io.brooklyn.camp.spi.collection.ResourceLookup;
+import io.brooklyn.camp.spi.collection.ResourceLookup.EmptyResourceLookup;
+
+import org.apache.brooklyn.camp.brooklyn.spi.lookup.AssemblyBrooklynLookup;
+import org.apache.brooklyn.camp.brooklyn.spi.lookup.AssemblyTemplateBrooklynLookup;
+import org.apache.brooklyn.camp.brooklyn.spi.lookup.PlatformComponentBrooklynLookup;
+import org.apache.brooklyn.camp.brooklyn.spi.lookup.PlatformComponentTemplateBrooklynLookup;
+
+import brooklyn.camp.brooklyn.api.HasBrooklynManagementContext;
+import brooklyn.management.ManagementContext;
+
+/** Immutable CAMP platform which reflects things in the underlying Brooklyn system */
+public class BrooklynImmutableCampPlatform extends CampPlatform implements HasBrooklynManagementContext {
+
+    private final ManagementContext bmc;
+    private final AssemblyTemplateBrooklynLookup ats;
+    private final PlatformComponentTemplateBrooklynLookup pcts;
+    private final BasicResourceLookup<ApplicationComponentTemplate> acts;
+    private final PlatformComponentBrooklynLookup pcs;
+    private final AssemblyBrooklynLookup assemblies;
+
+    public BrooklynImmutableCampPlatform(PlatformRootSummary root, ManagementContext managementContext) {
+        super(root);
+        this.bmc = managementContext;
+        
+        // these come from brooklyn
+        pcts = new PlatformComponentTemplateBrooklynLookup(root(), getBrooklynManagementContext());
+        ats = new AssemblyTemplateBrooklynLookup(root(), getBrooklynManagementContext());
+        pcs = new PlatformComponentBrooklynLookup(root(), getBrooklynManagementContext());
+        assemblies = new AssemblyBrooklynLookup(root(), getBrooklynManagementContext(), pcs);
+        
+        // ACT's are not known in brooklyn (everything comes in as config) -- to be extended to support!
+        acts = new BasicResourceLookup<ApplicationComponentTemplate>();
+    }
+
+    // --- brooklyn setup
+    
+    public ManagementContext getBrooklynManagementContext() {
+        return bmc;
+    }
+    
+    // --- camp comatibility setup
+    
+    @Override
+    public ResourceLookup<PlatformComponentTemplate> platformComponentTemplates() {
+        return pcts;
+    }
+
+    @Override
+    public ResourceLookup<ApplicationComponentTemplate> applicationComponentTemplates() {
+        return acts;
+    }
+
+    @Override
+    public ResourceLookup<AssemblyTemplate> assemblyTemplates() {
+        return ats;
+    }
+    
+    @Override
+    public ResourceLookup<PlatformComponent> platformComponents() {
+        return pcs;
+    }
+
+    @Override
+    public ResourceLookup<ApplicationComponent> applicationComponents() {
+        return new EmptyResourceLookup<ApplicationComponent>();
+    }
+
+    @Override
+    public ResourceLookup<Assembly> assemblies() {
+        return assemblies;
+    }
+    
+    @Override
+    public PlatformTransaction transaction() {
+        throw new IllegalStateException(this+" does not support adding new items");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/resources/META-INF/services/io.brooklyn.camp.brooklyn.spi.creation.service.ServiceTypeResolver
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/resources/META-INF/services/io.brooklyn.camp.brooklyn.spi.creation.service.ServiceTypeResolver b/usage/camp/src/main/resources/META-INF/services/io.brooklyn.camp.brooklyn.spi.creation.service.ServiceTypeResolver
deleted file mode 100644
index 1a48ccb..0000000
--- a/usage/camp/src/main/resources/META-INF/services/io.brooklyn.camp.brooklyn.spi.creation.service.ServiceTypeResolver
+++ /dev/null
@@ -1,22 +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.
-#
-io.brooklyn.camp.brooklyn.spi.creation.service.BrooklynServiceTypeResolver
-io.brooklyn.camp.brooklyn.spi.creation.service.CatalogServiceTypeResolver
-io.brooklyn.camp.brooklyn.spi.creation.service.ChefServiceTypeResolver
-io.brooklyn.camp.brooklyn.spi.creation.service.JavaServiceTypeResolver

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/main/resources/META-INF/services/org.apache.brooklyn.camp.brooklyn.spi.creation.service.ServiceTypeResolver
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/resources/META-INF/services/org.apache.brooklyn.camp.brooklyn.spi.creation.service.ServiceTypeResolver b/usage/camp/src/main/resources/META-INF/services/org.apache.brooklyn.camp.brooklyn.spi.creation.service.ServiceTypeResolver
new file mode 100644
index 0000000..9c941df
--- /dev/null
+++ b/usage/camp/src/main/resources/META-INF/services/org.apache.brooklyn.camp.brooklyn.spi.creation.service.ServiceTypeResolver
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+org.apache.brooklyn.camp.brooklyn.spi.creation.service.BrooklynServiceTypeResolver
+org.apache.brooklyn.camp.brooklyn.spi.creation.service.CatalogServiceTypeResolver
+org.apache.brooklyn.camp.brooklyn.spi.creation.service.ChefServiceTypeResolver
+org.apache.brooklyn.camp.brooklyn.spi.creation.service.JavaServiceTypeResolver

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e406d1ad/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java
deleted file mode 100644
index 226ec67..0000000
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/AbstractYamlRebindTest.java
+++ /dev/null
@@ -1,206 +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 io.brooklyn.camp.brooklyn;
-
-import io.brooklyn.camp.spi.Assembly;
-import io.brooklyn.camp.spi.AssemblyTemplate;
-
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.Set;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-
-import brooklyn.catalog.internal.CatalogUtils;
-import brooklyn.entity.Entity;
-import brooklyn.entity.basic.BrooklynTaskTags;
-import brooklyn.entity.basic.Entities;
-import brooklyn.entity.basic.StartableApplication;
-import brooklyn.entity.rebind.RebindOptions;
-import brooklyn.entity.rebind.RebindTestFixture;
-import brooklyn.management.ManagementContext;
-import brooklyn.management.Task;
-import brooklyn.management.internal.LocalManagementContext;
-import brooklyn.util.ResourceUtils;
-import brooklyn.util.config.ConfigBag;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-
-public class AbstractYamlRebindTest extends RebindTestFixture<StartableApplication> {
-
-    private static final Logger LOG = LoggerFactory.getLogger(AbstractYamlTest.class);
-    protected static final String TEST_VERSION = "0.1.2";
-
-    protected BrooklynCampPlatform platform;
-    protected BrooklynCampPlatformLauncherNoServer launcher;
-    private boolean forceUpdate;
-    
-    @BeforeMethod(alwaysRun = true)
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        launcher = new BrooklynCampPlatformLauncherNoServer() {
-            @Override
-            protected LocalManagementContext newMgmtContext() {
-                return (LocalManagementContext) mgmt();
-            }
-        };
-        launcher.launch();
-        platform = launcher.getCampPlatform();
-    }
-
-    @AfterMethod(alwaysRun = true)
-    @Override
-    public void tearDown() throws Exception {
-        try {
-            super.tearDown();
-        } finally {
-            if (launcher != null) launcher.stopServers();
-        }
-    }
-
-    protected StartableApplication rebind(RebindOptions options) throws Exception {
-        StartableApplication result = super.rebind(options);
-        if (launcher != null) {
-            launcher.stopServers();
-            launcher = new BrooklynCampPlatformLauncherNoServer() {
-                @Override
-                protected LocalManagementContext newMgmtContext() {
-                    return (LocalManagementContext) mgmt();
-                }
-            };
-            launcher.launch();
-            platform = launcher.getCampPlatform();
-        }
-        return result;
-    }
-    
-    @Override
-    protected StartableApplication createApp() {
-        return null;
-    }
-
-    protected ManagementContext mgmt() {
-        return (newManagementContext != null) ? newManagementContext : origManagementContext;
-    }
-    
-    ///////////////////////////////////////////////////
-    // TODO code below is duplicate of AbstractYamlTest
-    ///////////////////////////////////////////////////
-    
-    protected void waitForApplicationTasks(Entity app) {
-        Set<Task<?>> tasks = BrooklynTaskTags.getTasksInEntityContext(origManagementContext.getExecutionManager(), app);
-        getLogger().info("Waiting on " + tasks.size() + " task(s)");
-        for (Task<?> t : tasks) {
-            t.blockUntilEnded();
-        }
-    }
-
-    protected Reader loadYaml(String yamlFileName, String ...extraLines) throws Exception {
-        String input = new ResourceUtils(this).getResourceAsString(yamlFileName).trim();
-        StringBuilder builder = new StringBuilder(input);
-        for (String l: extraLines)
-            builder.append("\n").append(l);
-        return new StringReader(builder.toString());
-    }
-    
-    protected Entity createAndStartApplication(String... multiLineYaml) throws Exception {
-        return createAndStartApplication(joinLines(multiLineYaml));
-    }
-    
-    protected Entity createAndStartApplication(String input) throws Exception {
-        return createAndStartApplication(new StringReader(input));
-    }
-
-    protected Entity createAndStartApplication(Reader input) throws Exception {
-        AssemblyTemplate at = platform.pdp().registerDeploymentPlan(input);
-        Assembly assembly;
-        try {
-            assembly = at.getInstantiator().newInstance().instantiate(at, platform);
-        } catch (Exception e) {
-            getLogger().warn("Unable to instantiate " + at + " (rethrowing): " + e);
-            throw e;
-        }
-        getLogger().info("Test - created " + assembly);
-        final Entity app = origManagementContext.getEntityManager().getEntity(assembly.getId());
-        getLogger().info("App - " + app);
-        
-        // wait for app to have started
-        Set<Task<?>> tasks = origManagementContext.getExecutionManager().getTasksWithAllTags(ImmutableList.of(
-                BrooklynTaskTags.EFFECTOR_TAG, 
-                BrooklynTaskTags.tagForContextEntity(app), 
-                BrooklynTaskTags.tagForEffectorCall(app, "start", ConfigBag.newInstance(ImmutableMap.of("locations", ImmutableMap.of())))));
-        Iterables.getOnlyElement(tasks).get();
-        
-        return app;
-    }
-
-    protected Entity createStartWaitAndLogApplication(Reader input) throws Exception {
-        Entity app = createAndStartApplication(input);
-        waitForApplicationTasks(app);
-
-        getLogger().info("App started:");
-        Entities.dumpInfo(app);
-        
-        return app;
-    }
-
-    protected void addCatalogItems(Iterable<String> catalogYaml) {
-        addCatalogItems(joinLines(catalogYaml));
-    }
-
-    protected void addCatalogItems(String... catalogYaml) {
-        addCatalogItems(joinLines(catalogYaml));
-    }
-
-    protected void addCatalogItems(String catalogYaml) {
-        mgmt().getCatalog().addItems(catalogYaml, forceUpdate);
-    }
-
-    protected void deleteCatalogEntity(String catalogItem) {
-        mgmt().getCatalog().deleteCatalogItem(catalogItem, TEST_VERSION);
-    }
-
-    protected Logger getLogger() {
-        return LOG;
-    }
-
-    private String joinLines(Iterable<String> catalogYaml) {
-        return Joiner.on("\n").join(catalogYaml);
-    }
-
-    private String joinLines(String[] catalogYaml) {
-        return Joiner.on("\n").join(catalogYaml);
-    }
-
-    protected String ver(String id) {
-        return CatalogUtils.getVersionedId(id, TEST_VERSION);
-    }
-
-    public void forceCatalogUpdate() {
-        forceUpdate = true;
-    }
-
-}