You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/12/23 12:06:39 UTC

[16/71] [abbrv] incubator-brooklyn git commit: Merge commit 'e430723' into reorg2

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java
index 0000000,4641447..e78a1af
mode 000000,100644..100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityAsserts.java
@@@ -1,0 -1,228 +1,226 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.core.entity;
+ 
+ import com.google.common.annotations.Beta;
+ import com.google.common.base.Objects;
+ import com.google.common.base.Predicate;
+ import com.google.common.base.Predicates;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.Maps;
+ import com.google.common.collect.Sets;
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.entity.Group;
+ import org.apache.brooklyn.api.mgmt.SubscriptionHandle;
+ import org.apache.brooklyn.api.sensor.AttributeSensor;
+ import org.apache.brooklyn.api.sensor.SensorEvent;
+ import org.apache.brooklyn.api.sensor.SensorEventListener;
+ import org.apache.brooklyn.config.ConfigKey;
+ import org.apache.brooklyn.test.Asserts;
+ import org.apache.brooklyn.util.time.Duration;
+ 
+ import java.util.Collection;
+ import java.util.Map;
+ import java.util.Set;
+ import java.util.concurrent.Callable;
+ import java.util.concurrent.atomic.AtomicBoolean;
+ import java.util.concurrent.atomic.AtomicReference;
+ 
+ import static org.apache.brooklyn.test.Asserts.assertEquals;
+ 
+ /**
+  * Convenience class containing assertions that may be made about entities.
+  */
+ public class EntityAsserts {
+ 
+ 
+     public static <T> void assertAttributeEquals(Entity entity, AttributeSensor<T> attribute, T expected) {
+         assertEquals(entity.getAttribute(attribute), expected, "entity=" + entity + "; attribute=" + attribute);
+     }
+ 
+     public static <T> void assertConfigEquals(Entity entity, ConfigKey<T> configKey, T expected) {
+         assertEquals(entity.getConfig(configKey), expected, "entity=" + entity + "; configKey=" + configKey);
+     }
+ 
+     public static <T> void assertAttributeEqualsEventually(final Entity entity, final AttributeSensor<T> attribute, final T expected) {
+         assertAttributeEqualsEventually(Maps.newLinkedHashMap(), entity, attribute, expected);
+     }
+ 
+     public static <T> void assertAttributeEqualsEventually(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, final T expected) {
+         // Not using assertAttributeEventually(predicate) so get nicer error message
+         Asserts.succeedsEventually((Map) flags, new Runnable() {
+             @Override
+             public void run() {
+                 assertAttributeEquals(entity, attribute, expected);
+             }
+         });
+     }
+ 
+     public static <T> T assertAttributeEventuallyNonNull(final Entity entity, final AttributeSensor<T> attribute) {
+         return assertAttributeEventuallyNonNull(Maps.newLinkedHashMap(), entity, attribute);
+     }
+ 
+     public static <T> T assertAttributeEventuallyNonNull(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute) {
+         return assertAttributeEventually(flags, entity, attribute, Predicates.notNull());
+     }
+ 
+     public static <T> T assertAttributeEventually(final Entity entity, final AttributeSensor<T> attribute, Predicate<? super T> predicate) {
+         return assertAttributeEventually(ImmutableMap.of(), entity, attribute, predicate);
+     }
+ 
+     public static <T> T assertAttributeEventually(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, final Predicate<? super T> predicate) {
+         final AtomicReference<T> result = new AtomicReference<T>();
+         Asserts.succeedsEventually((Map)flags, new Runnable() {
+             @Override public void run() {
+                 T val = entity.getAttribute(attribute);
+                 Asserts.assertTrue(predicate.apply(val), "val=" + val);
+                 result.set(val);
+             }});
+         return result.get();
+     }
+ 
+     public static <T> T assertAttribute(final Entity entity, final AttributeSensor<T> attribute, final Predicate<? super T> predicate) {
+         T val = entity.getAttribute(attribute);
+         Asserts.assertTrue(predicate.apply(val), "val=" + val);
+         return val;
+     }
+ 
+ 
+     public static <T extends Entity> void assertPredicateEventuallyTrue(final T entity, final Predicate<? super T> predicate) {
+         assertPredicateEventuallyTrue(Maps.newLinkedHashMap(), entity, predicate);
+     }
+ 
+     public static <T extends Entity> void assertPredicateEventuallyTrue(Map<?,?> flags, final T entity, final Predicate<? super T> predicate) {
+         Asserts.succeedsEventually((Map)flags, new Runnable() {
+             @Override public void run() {
+                 Asserts.assertTrue(predicate.apply(entity), "predicate unsatisfied");
+             }});
+     }
+ 
+     public static <T> void assertAttributeEqualsContinually(final Entity entity, final AttributeSensor<T> attribute, final T expected) {
+         assertAttributeEqualsContinually(Maps.newLinkedHashMap(), entity, attribute, expected);
+     }
+ 
+     public static <T> void assertAttributeEqualsContinually(Map<?,?> flags, final Entity entity, final AttributeSensor<T> attribute, final T expected) {
+         Asserts.succeedsContinually(flags, new Runnable() {
+             @Override public void run() {
+                 assertAttributeEquals(entity, attribute, expected);
+             }});
+     }
+ 
+     public static void assertGroupSizeEqualsEventually(final Group group, int expected) {
+         assertGroupSizeEqualsEventually(ImmutableMap.of(), group, expected);
+     }
+ 
+     public static void assertGroupSizeEqualsEventually(Map<?,?> flags, final Group group, final int expected) {
+         Asserts.succeedsEventually((Map)flags, new Runnable() {
+             @Override public void run() {
+                 Collection<Entity> members = group.getMembers();
+                 assertEquals(members.size(), expected, "members=" + members);
+             }});
+     }
+ 
+ 
+     /**
+      * Asserts that the entity's value for this attribute changes, by registering a subscription and checking the value.
+      *
+      * @param entity The entity whose attribute will be checked.
+      * @param attribute The attribute to check on the entity.
+      *
+      * @throws AssertionError if the assertion fails.
+      */
+     public static void assertAttributeChangesEventually(final Entity entity, final AttributeSensor<?> attribute) {
+         final Object origValue = entity.getAttribute(attribute);
+         final AtomicBoolean changed = new AtomicBoolean();
+         SubscriptionHandle handle = entity.subscriptions().subscribe(entity, attribute, new SensorEventListener<Object>() {
+             @Override public void onEvent(SensorEvent<Object> event) {
+                 if (!Objects.equal(origValue, event.getValue())) {
+                     changed.set(true);
+                 }
+             }});
+         try {
+             Asserts.succeedsEventually(new Runnable() {
+                 @Override public void run() {
+                     Asserts.assertTrue(changed.get(), entity + " -> " + attribute + " not changed");
+                 }});
+         } finally {
+             entity.subscriptions().unsubscribe(entity, handle);
+         }
+     }
+ 
+ 
+     /**
+      * Assert that the given attribute of an entity does not take any of the disallowed values during a given period.
+      *
+      * This method relies on {@link Asserts#succeedsContinually(Runnable)}, therefore it loops comparing the value
+      * of the attribute to the disallowed values, rather than setting up a subscription.  It may therefore miss a
+      * situation where the attribute temporarily takes a disallowed value. This method is therefore suited for use
+      * where the attribute will take on a value permanently, which may or may not be disallowed.
+      *
+      * @param entity      The entity owning the attribute to check.
+      * @param attribute   The attribute on the entity.
+      * @param disallowed  The disallowed values for the entity.
+      * @param <T>         Type of the sensor.
+      */
+     @Beta @SafeVarargs
+     public static <T> void assertAttributeContinuallyNotEqualTo(final Entity entity, final AttributeSensor<T> attribute, T... disallowed) {
+         final Set<T> reject = Sets.newHashSet(disallowed);
+         Asserts.succeedsContinually(new Runnable() {
+             @Override
+             public void run() {
+                 T val = entity.getAttribute(attribute);
+                 Asserts.assertFalse(reject.contains(val),
+                         "Attribute " + attribute + " on " + entity + " has disallowed value " + val);
+             }
+         });
+     }
+ 
 -
 -
+     /**
+      * Assert that the given attribute of an entity does not take any of the disallowed values during a given period.
+      *
+      * This method relies on {@link Asserts#succeedsContinually(Runnable)}, therefore it loops comparing the value
+      * of the attribute to the disallowed values, rather than setting up a subscription.  It may therefore miss a
+      * situation where the attribute temporarily takes a disallowed value. This method is therefore suited for use
+      * where the attribute will take on a value permanently, which may or may not be disallowed.
+      *
+      * @param flags       Flags controlling the loop, with keys: <ul>
+      *                    <li>timeout: a {@link Duration} specification String for the duration for which to test the
+      *                    assertion. Default 1 second.</li>
+      *                    <li>period: a {@link Duration} specification String for the interval at which to perform polls
+      *                    on the attribute value. Default 10ms.</li>
+      *                   </ul>
+      * @param entity      The entity owning the attribute to check.
+      * @param attribute   The attribute on the entity.
+      * @param disallowed  The disallowed values for the entity.
+      * @param <T>         Type of the sensor.
+      */
+     @Beta @SafeVarargs
+     public static <T> void assertAttributeContinuallyNotEqualTo(final Map<?, ?> flags, final Entity entity, final AttributeSensor<T> attribute, T... disallowed) {
+         final Set<T> reject = Sets.newHashSet(disallowed);
+         Asserts.succeedsContinually(flags, new Runnable() {
+             @Override
+             public void run() {
+                 T val = entity.getAttribute(attribute);
+                 Asserts.assertFalse(reject.contains(val),
+                         "Attribute " + attribute + " on " + entity + " has disallowed value " + val);
+             }
+         });
+     }
+ 
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java
index 0000000,a4459ed..c65a176
mode 000000,100644..100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/EntityFunctions.java
@@@ -1,0 -1,291 +1,307 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.core.entity;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
+ 
+ import java.util.Collection;
+ import java.util.Map;
+ 
+ import org.apache.brooklyn.api.entity.Application;
+ import org.apache.brooklyn.api.entity.Entity;
+ import org.apache.brooklyn.api.entity.EntityLocal;
+ import org.apache.brooklyn.api.location.Location;
+ import org.apache.brooklyn.api.mgmt.ManagementContext;
+ import org.apache.brooklyn.api.objs.Identifiable;
+ import org.apache.brooklyn.api.sensor.AttributeSensor;
+ import org.apache.brooklyn.config.ConfigKey;
+ import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
+ import org.apache.brooklyn.util.core.flags.TypeCoercions;
+ import org.apache.brooklyn.util.guava.Functionals;
+ 
+ import com.google.common.base.Function;
+ import com.google.common.base.Predicate;
+ import com.google.common.base.Supplier;
+ import com.google.common.base.Suppliers;
+ import com.google.common.collect.Iterables;
+ 
+ public class EntityFunctions {
+ 
+     /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */
+     @SuppressWarnings("unused") @Deprecated 
+     private static <T> Function<Entity, T> attributeOld(final AttributeSensor<T> attribute) {
+         // TODO PERSISTENCE WORKAROUND
+         class GetEntityAttributeFunction implements Function<Entity, T> {
+             @Override public T apply(Entity input) {
+                 return (input == null) ? null : input.getAttribute(attribute);
+             }
 -        };
++        }
+         return new GetEntityAttributeFunction();
+     }
+     
+     /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */
+     @SuppressWarnings("unused") @Deprecated 
+     private static <T> Function<Entity, T> configOld(final ConfigKey<T> key) {
+         // TODO PERSISTENCE WORKAROUND
+         class GetEntityConfigFunction implements Function<Entity, T> {
+             @Override public T apply(Entity input) {
+                 return (input == null) ? null : input.getConfig(key);
+             }
 -        };
++        }
+         return new GetEntityConfigFunction();
+     }
+     
+     /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */
+     @SuppressWarnings("unused") @Deprecated 
+     private static Function<Entity, String> displayNameOld() {
+         // TODO PERSISTENCE WORKAROUND
+         class GetEntityDisplayName implements Function<Entity, String> {
+             @Override public String apply(Entity input) {
+                 return (input == null) ? null : input.getDisplayName();
+             }
 -        };
++        }
+         return new GetEntityDisplayName();
+     }
+     
+     /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */
+     @SuppressWarnings("unused") @Deprecated 
+     private static Function<Identifiable, String> idOld() {
+         // TODO PERSISTENCE WORKAROUND
+         class GetIdFunction implements Function<Identifiable, String> {
+             @Override public String apply(Identifiable input) {
+                 return (input == null) ? null : input.getId();
+             }
 -        };
++        }
+         return new GetIdFunction();
+     }
+ 
+     /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */
+     @SuppressWarnings("unused") @Deprecated 
+     private static Function<Entity,Void> settingSensorsConstantOld(final Map<AttributeSensor<?>,Object> values) {
+         // TODO PERSISTENCE WORKAROUND
+         class SettingSensorsConstantFunction implements Function<Entity, Void> {
+             @SuppressWarnings({ "unchecked", "rawtypes" })
+             @Override public Void apply(Entity input) {
+                 for (Map.Entry<AttributeSensor<?>,Object> entry : values.entrySet()) {
+                     AttributeSensor sensor = (AttributeSensor)entry.getKey();
+                     Object value = entry.getValue();
+                     if (value==Entities.UNCHANGED) {
+                         // nothing
+                     } else if (value==Entities.REMOVE) {
+                         ((EntityInternal)input).removeAttribute(sensor);
+                     } else {
+                         value = TypeCoercions.coerce(value, sensor.getTypeToken());
+                         ((EntityInternal)input).sensors().set(sensor, value);
+                     }
+                 }
+                 return null;
+             }
+         }
+         return new SettingSensorsConstantFunction();
+     }
+ 
+     /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */
+     @SuppressWarnings("unused") @Deprecated 
+     private static <K,V> Function<Entity, Void> updatingSensorMapEntryOld(final AttributeSensor<Map<K,V>> mapSensor, final K key, final Supplier<? extends V> valueSupplier) {
+         // TODO PERSISTENCE WORKAROUND
+         class UpdatingSensorMapEntryFunction implements Function<Entity, Void> {
+             @Override public Void apply(Entity input) {
+                 ServiceStateLogic.updateMapSensorEntry((EntityLocal)input, mapSensor, key, valueSupplier.get());
+                 return null;
+             }
+         }
+         return new UpdatingSensorMapEntryFunction();
+     }
+ 
+     /** @deprecated since 0.9.0 kept only to allow conversion of non-static inner classes */
+     @SuppressWarnings("unused") @Deprecated 
+     private static Supplier<Collection<Application>> applicationsOld(final ManagementContext mgmt) {
+         // TODO PERSISTENCE WORKAROUND
+         class AppsSupplier implements Supplier<Collection<Application>> {
+             @Override
+             public Collection<Application> get() {
+                 return mgmt.getApplications();
+             }
+         }
+         return new AppsSupplier();
+     }
+     
+     public static <T> Function<Entity, T> attribute(AttributeSensor<T> attribute) {
+         return new GetEntityAttributeFunction<T>(checkNotNull(attribute, "attribute"));
+     }
+ 
+     protected static class GetEntityAttributeFunction<T> implements Function<Entity, T> {
+         private final AttributeSensor<T> attribute;
+         protected GetEntityAttributeFunction(AttributeSensor<T> attribute) {
+             this.attribute = attribute;
+         }
+         @Override public T apply(Entity input) {
+             return (input == null) ? null : input.getAttribute(attribute);
+         }
 -    };
++    }
++
++    public static <T> Function<Object, T> attribute(Entity entity, AttributeSensor<T> attribute) {
++        return new GetFixedEntityAttributeFunction<>(entity, attribute);
++    }
++
++    protected static class GetFixedEntityAttributeFunction<T> implements Function<Object, T> {
++        private final Entity entity;
++        private final AttributeSensor<T> attribute;
++        protected GetFixedEntityAttributeFunction(Entity entity, AttributeSensor<T> attribute) {
++            this.entity = entity;
++            this.attribute = attribute;
++        }
++        @Override public T apply(Object input) {
++            return entity.getAttribute(attribute);
++        }
++    }
+ 
+     public static <T> Function<Entity, T> config(ConfigKey<T> key) {
+         return new GetEntityConfigFunction<T>(checkNotNull(key, "key"));
+     }
+ 
+     protected static class GetEntityConfigFunction<T> implements Function<Entity, T> {
+         private final ConfigKey<T> key;
+ 
+         protected GetEntityConfigFunction(ConfigKey<T> key) {
+             this.key = key;
+         }
+ 
+         @Override public T apply(Entity input) {
+             return (input == null) ? null : input.getConfig(key);
+         }
 -    };
++    }
+ 
+     public static Function<Entity, String> displayName() {
+         return GetEntityDisplayName.INSTANCE;
+     }
+ 
+     protected static class GetEntityDisplayName implements Function<Entity, String> {
+         public static final GetEntityDisplayName INSTANCE = new GetEntityDisplayName();
+         @Override public String apply(Entity input) {
+             return (input == null) ? null : input.getDisplayName();
+         }
 -    };
++    }
+ 
+     public static Function<Identifiable, String> id() {
+         return GetIdFunction.INSTANCE;
+     }
+     
+     protected static class GetIdFunction implements Function<Identifiable, String> {
+         public static final GetIdFunction INSTANCE = new GetIdFunction();
+         @Override public String apply(Identifiable input) {
+             return (input == null) ? null : input.getId();
+         }
 -    };
++    }
+ 
+ 
+     /** returns a function which sets the given sensors on the entity passed in,
+      * with {@link Entities#UNCHANGED} and {@link Entities#REMOVE} doing those actions. */
+     public static Function<Entity,Void> settingSensorsConstant(final Map<AttributeSensor<?>,Object> values) {
+         return new SettingSensorsConstantFunction(checkNotNull(values, "values"));
+     }
+ 
+     protected static class SettingSensorsConstantFunction implements Function<Entity, Void> {
+         private final Map<AttributeSensor<?>, Object> values;
+ 
+         protected SettingSensorsConstantFunction(Map<AttributeSensor<?>, Object> values) {
+             this.values = values;
+         }
+         @SuppressWarnings({ "unchecked", "rawtypes" })
+         @Override public Void apply(Entity input) {
+             for (Map.Entry<AttributeSensor<?>,Object> entry : values.entrySet()) {
+                 AttributeSensor sensor = (AttributeSensor)entry.getKey();
+                 Object value = entry.getValue();
+                 if (value==Entities.UNCHANGED) {
+                     // nothing
+                 } else if (value==Entities.REMOVE) {
+                     ((EntityInternal)input).sensors().remove(sensor);
+                 } else {
+                     value = TypeCoercions.coerce(value, sensor.getTypeToken());
+                     ((EntityInternal)input).sensors().set(sensor, value);
+                 }
+             }
+             return null;
+         }
+     }
+ 
+     /** as {@link #settingSensorsConstant(Map)} but as a {@link Runnable} */
+     public static Runnable settingSensorsConstant(final Entity entity, final Map<AttributeSensor<?>,Object> values) {
+         checkNotNull(entity, "entity");
+         checkNotNull(values, "values");
+         return Functionals.runnable(Suppliers.compose(settingSensorsConstant(values), Suppliers.ofInstance(entity)));
+     }
+ 
+     public static <K,V> Function<Entity, Void> updatingSensorMapEntry(final AttributeSensor<Map<K,V>> mapSensor, final K key, final Supplier<? extends V> valueSupplier) {
+         return new UpdatingSensorMapEntryFunction<K,V>(mapSensor, key, valueSupplier);
+     }
+     
+     protected static class UpdatingSensorMapEntryFunction<K, V> implements Function<Entity, Void> {
+         private final AttributeSensor<Map<K, V>> mapSensor;
+         private final K key;
+         private final Supplier<? extends V> valueSupplier;
+ 
+         public UpdatingSensorMapEntryFunction(AttributeSensor<Map<K, V>> mapSensor, K key, Supplier<? extends V> valueSupplier) {
+             this.mapSensor = mapSensor;
+             this.key = key;
+             this.valueSupplier = valueSupplier;
+         }
+         @Override public Void apply(Entity input) {
+             ServiceStateLogic.updateMapSensorEntry((EntityLocal)input, mapSensor, key, valueSupplier.get());
+             return null;
+         }
+     }
+ 
+     public static <K,V> Runnable updatingSensorMapEntry(final Entity entity, final AttributeSensor<Map<K,V>> mapSensor, final K key, final Supplier<? extends V> valueSupplier) {
+         return Functionals.runnable(Suppliers.compose(updatingSensorMapEntry(mapSensor, key, valueSupplier), Suppliers.ofInstance(entity)));
+     }
+ 
+     public static Supplier<Collection<Application>> applications(ManagementContext mgmt) {
+         return new AppsSupplier(checkNotNull(mgmt, "mgmt"));
+     }
+     
+     protected static class AppsSupplier implements Supplier<Collection<Application>> {
+         private final ManagementContext mgmt;
+ 
+         public AppsSupplier(ManagementContext mgmt) {
+             this.mgmt = mgmt;
+         }
+         @Override
+         public Collection<Application> get() {
+             return mgmt.getApplications();
+         }
+     }
+ 
+     public static Function<Entity, Location> locationMatching(Predicate<? super Location> filter) {
+         return new LocationMatching(filter);
+     }
+     
+     private static class LocationMatching implements Function<Entity, Location> {
+         private Predicate<? super Location> filter;
+         
+         private LocationMatching() { /* for xstream */
+         }
+         public LocationMatching(Predicate<? super Location> filter) {
+             this.filter = filter;
+         }
+         @Override public Location apply(Entity input) {
+             return Iterables.find(input.getLocations(), filter);
+         }
+     }
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java
index 0000000,111eee0..da209e1
mode 000000,100644..100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/entity/internal/EntityConfigMap.java
@@@ -1,0 -1,306 +1,319 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.core.entity.internal;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
+ import static org.apache.brooklyn.util.groovy.GroovyJavaMethods.elvis;
+ 
++import java.util.Collection;
+ import java.util.Collections;
+ import java.util.LinkedHashMap;
+ import java.util.Map;
+ import java.util.Set;
+ 
+ import org.apache.brooklyn.api.mgmt.ExecutionContext;
+ import org.apache.brooklyn.api.mgmt.Task;
+ import org.apache.brooklyn.config.ConfigInheritance;
+ import org.apache.brooklyn.config.ConfigKey;
+ import org.apache.brooklyn.core.config.Sanitizer;
+ import org.apache.brooklyn.core.config.StructuredConfigKey;
+ import org.apache.brooklyn.core.config.internal.AbstractConfigMapImpl;
+ import org.apache.brooklyn.core.entity.AbstractEntity;
+ import org.apache.brooklyn.util.collections.MutableMap;
+ import org.apache.brooklyn.util.core.config.ConfigBag;
+ import org.apache.brooklyn.util.core.flags.FlagUtils;
+ import org.apache.brooklyn.util.core.flags.SetFromFlag;
+ import org.apache.brooklyn.util.core.flags.TypeCoercions;
+ import org.apache.brooklyn.util.core.internal.ConfigKeySelfExtracting;
+ import org.apache.brooklyn.util.guava.Maybe;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ import com.google.common.base.Predicate;
+ import com.google.common.collect.Maps;
+ import com.google.common.collect.Sets;
+ 
+ public class EntityConfigMap extends AbstractConfigMapImpl {
+ 
+     private static final Logger LOG = LoggerFactory.getLogger(EntityConfigMap.class);
+ 
+     /** entity against which config resolution / task execution will occur */
+     private final AbstractEntity entity;
+ 
+     /**
+      * Map of configuration information that is defined at start-up time for the entity. These
+      * configuration parameters are shared and made accessible to the "children" of this
+      * entity.
+      */
+     private final Map<ConfigKey<?>,Object> inheritedConfig = Collections.synchronizedMap(new LinkedHashMap<ConfigKey<?>, Object>());
+     // TODO do we really want to have *both* bags and maps for these?  danger that they get out of synch.
+     // have added some logic (Oct 2014) so that the same changes are applied to both, in most places at least;
+     // i (alex) think we should prefer ConfigBag (the input keys don't matter, it is more a question of retrieval keys),
+     // but first we need ConfigBag to support StructuredConfigKeys 
+     private final ConfigBag localConfigBag;
+     private final ConfigBag inheritedConfigBag;
+ 
++    public EntityConfigMap(AbstractEntity entity) {
++        // Not using ConcurrentMap, because want to (continue to) allow null values.
++        // Could use ConcurrentMapAcceptingNullVals (with the associated performance hit on entrySet() etc).
++        this(entity, Collections.synchronizedMap(Maps.<ConfigKey<?>, Object>newLinkedHashMap()));
++    }
++    
+     public EntityConfigMap(AbstractEntity entity, Map<ConfigKey<?>, Object> storage) {
+         this.entity = checkNotNull(entity, "entity must be specified");
+         this.ownConfig = checkNotNull(storage, "storage map must be specified");
+         
+         // TODO store ownUnused in backing-storage
+         this.localConfigBag = ConfigBag.newInstance();
+         this.inheritedConfigBag = ConfigBag.newInstance();
+     }
+ 
+     @SuppressWarnings("unchecked")
+     public <T> T getConfig(ConfigKey<T> key, T defaultValue) {
+         // FIXME What about inherited task in config?!
+         //              alex says: think that should work, no?
+         // FIXME What if someone calls getConfig on a task, before setting parent app?
+         //              alex says: not supported (throw exception, or return the task)
+         
+         // In case this entity class has overridden the given key (e.g. to set default), then retrieve this entity's key
+         // TODO If ask for a config value that's not in our configKeys, should we really continue with rest of method and return key.getDefaultValue?
+         //      e.g. SshBasedJavaAppSetup calls setAttribute(JMX_USER), which calls getConfig(JMX_USER)
+         //           but that example doesn't have a default...
+         ConfigKey<T> ownKey = entity!=null ? (ConfigKey<T>)elvis(entity.getEntityType().getConfigKey(key.getName()), key) : key;
+         
+         ConfigInheritance inheritance = key.getInheritance();
+         if (inheritance==null) inheritance = ownKey.getInheritance(); 
+         if (inheritance==null) {
+             // TODO we could warn by introducing a temporary "ALWAYS_BUT_WARNING" instance
+             inheritance = getDefaultInheritance(); 
+         }
+         
+         // TODO We're notifying of config-changed because currently persistence needs to know when the
+         // attributeWhenReady is complete (so it can persist the result).
+         // Long term, we'll just persist tasks properly so the call to onConfigChanged will go!
+ 
+         // Don't use groovy truth: if the set value is e.g. 0, then would ignore set value and return default!
+         if (ownKey instanceof ConfigKeySelfExtracting) {
+             Object rawval = ownConfig.get(key);
+             T result = null;
+             boolean complete = false;
+             if (((ConfigKeySelfExtracting<T>)ownKey).isSet(ownConfig)) {
+                 ExecutionContext exec = entity.getExecutionContext();
+                 result = ((ConfigKeySelfExtracting<T>)ownKey).extractValue(ownConfig, exec);
+                 complete = true;
+             } else if (isInherited(ownKey, inheritance) && 
+                     ((ConfigKeySelfExtracting<T>)ownKey).isSet(inheritedConfig)) {
+                 ExecutionContext exec = entity.getExecutionContext();
+                 result = ((ConfigKeySelfExtracting<T>)ownKey).extractValue(inheritedConfig, exec);
+                 complete = true;
+             } else if (localConfigBag.containsKey(ownKey)) {
+                 // TODO configBag.get doesn't handle tasks/attributeWhenReady - it only uses TypeCoercions
+                 result = localConfigBag.get(ownKey);
+                 complete = true;
+             } else if (isInherited(ownKey, inheritance) && 
+                     inheritedConfigBag.containsKey(ownKey)) {
+                 result = inheritedConfigBag.get(ownKey);
+                 complete = true;
+             }
+ 
+             if (rawval instanceof Task) {
+                 entity.getManagementSupport().getEntityChangeListener().onConfigChanged(key);
+             }
+             if (complete) {
+                 return result;
+             }
+         } else {
+             LOG.warn("Config key {} of {} is not a ConfigKeySelfExtracting; cannot retrieve value; returning default", ownKey, this);
+         }
+         return TypeCoercions.coerce((defaultValue != null) ? defaultValue : ownKey.getDefaultValue(), key.getTypeToken());
+     }
+ 
+     private <T> boolean isInherited(ConfigKey<T> key) {
+         return isInherited(key, key.getInheritance());
+     }
+     private <T> boolean isInherited(ConfigKey<T> key, ConfigInheritance inheritance) {
+         if (inheritance==null) inheritance = getDefaultInheritance(); 
+         return inheritance.isInherited(key, entity.getParent(), entity);
+     }
+     private ConfigInheritance getDefaultInheritance() {
+         return ConfigInheritance.ALWAYS; 
+     }
+ 
+     @Override
+     public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited) {
+         if (ownConfig.containsKey(key)) return Maybe.of(ownConfig.get(key));
+         if (includeInherited && inheritedConfig.containsKey(key)) return Maybe.of(inheritedConfig.get(key));
+         return Maybe.absent();
+     }
+     
+     /** an immutable copy of the config visible at this entity, local and inherited (preferring local) */
+     public Map<ConfigKey<?>,Object> getAllConfig() {
+         Map<ConfigKey<?>,Object> result = new LinkedHashMap<ConfigKey<?>,Object>(inheritedConfig.size()+ownConfig.size());
+         result.putAll(inheritedConfig);
+         result.putAll(ownConfig);
+         return Collections.unmodifiableMap(result);
+     }
+ 
+     /** an immutable copy of the config defined at this entity, ie not inherited */
+     public Map<ConfigKey<?>,Object> getLocalConfig() {
+         Map<ConfigKey<?>,Object> result = new LinkedHashMap<ConfigKey<?>,Object>(ownConfig.size());
+         result.putAll(ownConfig);
+         return Collections.unmodifiableMap(result);
+     }
+     
+     /** Creates an immutable copy of the config visible at this entity, local and inherited (preferring local), including those that did not match config keys */
+     public ConfigBag getAllConfigBag() {
+         return ConfigBag.newInstanceCopying(localConfigBag)
+                 .putAll(ownConfig)
+                 .putIfAbsent(inheritedConfig)
+                 .putIfAbsent(inheritedConfigBag)
+                 .seal();
+     }
+ 
+     /** Creates an immutable copy of the config defined at this entity, ie not inherited, including those that did not match config keys */
+     public ConfigBag getLocalConfigBag() {
+         return ConfigBag.newInstanceCopying(localConfigBag)
+                 .putAll(ownConfig)
+                 .seal();
+     }
+ 
+     @SuppressWarnings("unchecked")
+     public Object setConfig(ConfigKey<?> key, Object v) {
+         Object val = coerceConfigVal(key, v);
+         Object oldVal;
+         if (key instanceof StructuredConfigKey) {
+             oldVal = ((StructuredConfigKey)key).applyValueToMap(val, ownConfig);
+             // TODO ConfigBag does not handle structured config keys; quick fix is to remove (and should also remove any subkeys;
+             // as it stands if someone set string a.b.c in the config bag then removed structured key a.b, then got a.b.c they'd get a vale);
+             // long term fix is to support structured config keys in ConfigBag, at which point i think we could remove ownConfig altogether
+             localConfigBag.remove(key);
+         } else {
+             oldVal = ownConfig.put(key, val);
+             localConfigBag.put((ConfigKey<Object>)key, v);
+         }
+         entity.config().refreshInheritedConfigOfChildren();
+         return oldVal;
+     }
+     
+     public void setLocalConfig(Map<ConfigKey<?>, ?> vals) {
+         ownConfig.clear();
+         localConfigBag.clear();
+         ownConfig.putAll(vals);
+         localConfigBag.putAll(vals);
+     }
+     
+     public void setInheritedConfig(Map<ConfigKey<?>, ?> valsO, ConfigBag configBagVals) {
+         Map<ConfigKey<?>, ?> vals = filterUninheritable(valsO);
+         
+         inheritedConfig.clear();
+         inheritedConfig.putAll(vals);
+ 
+         // The configBagVals contains all inherited, including strings that did not match a config key on the parent.
+         // They might match a config-key on this entity though, so need to check that:
+         //   - if it matches one of our keys, set it in inheritedConfig
+         //   - otherwise add it to our inheritedConfigBag
+         Set<String> valKeyNames = Sets.newLinkedHashSet();
+         for (ConfigKey<?> key : vals.keySet()) {
+             valKeyNames.add(key.getName());
+         }
+         Map<String,Object> valsUnmatched = MutableMap.<String,Object>builder()
+                 .putAll(configBagVals.getAllConfig())
+                 .removeAll(valKeyNames)
+                 .build();
+         inheritedConfigBag.clear();
+         Map<ConfigKey<?>, SetFromFlag> annotatedConfigKeys = FlagUtils.getAnnotatedConfigKeys(entity.getClass());
+         Map<String, ConfigKey<?>> renamedConfigKeys = Maps.newLinkedHashMap();
+         for (Map.Entry<ConfigKey<?>, SetFromFlag> entry: annotatedConfigKeys.entrySet()) {
+             String rename = entry.getValue().value();
+             if (rename != null) {
+                 renamedConfigKeys.put(rename, entry.getKey());
+             }
+         }
+         for (Map.Entry<String,Object> entry : valsUnmatched.entrySet()) {
+             String name = entry.getKey();
+             Object value = entry.getValue();
+             ConfigKey<?> key = renamedConfigKeys.get(name);
+             if (key == null) key = entity.getEntityType().getConfigKey(name);
+             if (key != null) {
+                 if (!isInherited(key)) {
+                     // no-op
+                 } else if (inheritedConfig.containsKey(key)) {
+                     LOG.warn("Entity "+entity+" inherited duplicate config for key "+key+", via explicit config and string name "+name+"; using value of key");
+                 } else {
+                     inheritedConfig.put(key, value);
+                 }
+             } else {
+                 // a config bag has discarded the keys, so we must assume default inheritance for things given that way
+                 // unless we can infer a key; not a big deal, as we should have the key in inheritedConfig for everything
+                 // which originated with a key ... but still, it would be nice to clean up the use of config bag!
+                 inheritedConfigBag.putStringKey(name, value);
+             }
+         }
+     }
+     
+     private Map<ConfigKey<?>, ?> filterUninheritable(Map<ConfigKey<?>, ?> vals) {
+         Map<ConfigKey<?>, Object> result = Maps.newLinkedHashMap();
+         for (Map.Entry<ConfigKey<?>, ?> entry : vals.entrySet()) {
+             if (isInherited(entry.getKey())) {
+                 result.put(entry.getKey(), entry.getValue());
+             }
+         }
+         return result;
+     }
+     
+     public void addToLocalBag(Map<String,?> vals) {
+         localConfigBag.putAll(vals);
+         // quick fix for problem that ownConfig can get out of synch
+         ownConfig.putAll(localConfigBag.getAllConfigAsConfigKeyMap());
+     }
+ 
+     public void removeFromLocalBag(String key) {
+         localConfigBag.remove(key);
+         ownConfig.remove(key);
+     }
+ 
+     public void clearInheritedConfig() {
+         inheritedConfig.clear();
+         inheritedConfigBag.clear();
+     }
+ 
+     @Override
+     public EntityConfigMap submap(Predicate<ConfigKey<?>> filter) {
+         EntityConfigMap m = new EntityConfigMap(entity, Maps.<ConfigKey<?>, Object>newLinkedHashMap());
+         for (Map.Entry<ConfigKey<?>,Object> entry: inheritedConfig.entrySet())
+             if (filter.apply(entry.getKey()))
+                 m.inheritedConfig.put(entry.getKey(), entry.getValue());
 -        for (Map.Entry<ConfigKey<?>,Object> entry: ownConfig.entrySet())
 -            if (filter.apply(entry.getKey()))
 -                m.ownConfig.put(entry.getKey(), entry.getValue());
++        synchronized (ownConfig) {
++            for (Map.Entry<ConfigKey<?>,Object> entry: ownConfig.entrySet())
++                if (filter.apply(entry.getKey()))
++                    m.ownConfig.put(entry.getKey(), entry.getValue());
++        }
+         return m;
+     }
+ 
+     @Override
+     public String toString() {
 -        return super.toString()+"[own="+Sanitizer.sanitize(ownConfig)+"; inherited="+Sanitizer.sanitize(inheritedConfig)+"]";
++        Map<ConfigKey<?>, Object> sanitizeConfig;
++        synchronized (ownConfig) {
++            sanitizeConfig = Sanitizer.sanitize(ownConfig);
++        }
++        return super.toString()+"[own="+sanitizeConfig+"; inherited="+Sanitizer.sanitize(inheritedConfig)+"]";
+     }
+     
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java
index 0000000,4d06680..b9662ef
mode 000000,100644..100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/feed/FeedConfig.java
@@@ -1,0 -1,297 +1,307 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.core.feed;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
+ 
+ import org.apache.brooklyn.api.sensor.AttributeSensor;
+ import org.apache.brooklyn.core.entity.Entities;
+ import org.apache.brooklyn.core.sensor.Sensors;
+ import org.apache.brooklyn.feed.http.HttpPollConfig;
+ import org.apache.brooklyn.util.collections.MutableList;
+ import org.apache.brooklyn.util.guava.Functionals;
+ import org.apache.brooklyn.util.javalang.JavaClassNames;
+ import org.apache.brooklyn.util.text.Strings;
+ 
+ import com.google.common.base.Function;
+ import com.google.common.base.Functions;
+ import com.google.common.base.Objects;
+ import com.google.common.base.Predicate;
+ 
+ /**
+  * Configuration for a poll, or a subscription etc, that is being added to a feed.
+  * 
+  * @author aled
+  */
+ public class FeedConfig<V, T, F extends FeedConfig<V, T, F>> {
+ 
+     /** The onSuccess or onError functions can return this value to indicate that the sensor should not change. 
+      * @deprecated since 0.7.0 use UNCHANGED */
+     public static final Object UNSET = Entities.UNCHANGED;
+     /** The onSuccess or onError functions can return this value to indicate that the sensor should not change. */ 
+     public static final Object UNCHANGED = Entities.UNCHANGED;
+     /** The onSuccess or onError functions can return this value to indicate that the sensor value should be removed
+      * (cf 'null', but useful in dynamic situations) */ 
+     public static final Object REMOVE = Entities.REMOVE;
+     
+     /** Indicates that no sensor is being used here. This sensor is suppressed,
+      * but is useful where you want to use the feeds with custom success/exception/failure functions
+      * which directly set multiple sensors, e.g. dynamically based on the poll response.
+      * <p>
+      * See {@link HttpPollConfig#forMultiple()} and its usages.
+      * (It can work for any poll config, but conveniences have not been supplied for others.)  */
+     public static final AttributeSensor<Void> NO_SENSOR = Sensors.newSensor(Void.class, "brooklyn.no.sensor");
+     
+     private final AttributeSensor<T> sensor;
+     private Function<? super V, T> onsuccess;
+     private Function<? super V, T> onfailure;
+     private Function<? super Exception, T> onexception;
+     private Predicate<? super V> checkSuccess;
+     private boolean suppressDuplicates;
+     private boolean enabled = true;
+     
+     public FeedConfig(AttributeSensor<T> sensor) {
+         this.sensor = checkNotNull(sensor, "sensor");
+     }
+ 
+     public FeedConfig(FeedConfig<V, T, F> other) {
+         this.sensor = other.sensor;
+         this.onsuccess = other.onsuccess;
+         this.onfailure = other.onfailure;
+         this.onexception = other.onexception;
+         this.checkSuccess = other.checkSuccess;
+         this.suppressDuplicates = other.suppressDuplicates;
+         this.enabled = other.enabled;
+     }
+ 
+     @SuppressWarnings("unchecked")
+     protected F self() {
+         return (F) this;
+     }
+     
+     public AttributeSensor<T> getSensor() {
+         return sensor;
+     }
+ 
+     public Predicate<? super V> getCheckSuccess() {
+         return checkSuccess;
+     }
+     
+     public Function<? super V, T> getOnSuccess() {
+         return onsuccess;
+     }
+ 
+     public Function<? super V, T> getOnFailure() {
+         return onfailure;
+     }
+     
+     public Function<? super Exception, T> getOnException() {
+         return onexception;
+     }
+ 
+     public boolean getSupressDuplicates() {
+         return suppressDuplicates;
+     }
+     
+     public boolean isEnabled() {
+         return enabled;
+     }
+     
+     /** sets the predicate used to check whether a feed run is successful */
+     public F checkSuccess(Predicate<? super V> val) {
+         this.checkSuccess = checkNotNull(val, "checkSuccess");
+         return self();
+     }
+     /** as {@link #checkSuccess(Predicate)} */
+     public F checkSuccess(final Function<? super V,Boolean> val) {
+         return checkSuccess(Functionals.predicate(val));
+     }
++
+     @SuppressWarnings("unused")
+     /** @deprecated since 0.7.0, kept for rebind */ @Deprecated
+     private F checkSuccessLegacy(final Function<? super V,Boolean> val) {
+         return checkSuccess(new Predicate<V>() {
+             @Override
+             public boolean apply(V input) {
+                 return val.apply(input);
+             }
+         });
+     }
+ 
+     public F onSuccess(Function<? super V,T> val) {
+         this.onsuccess = checkNotNull(val, "onSuccess");
+         return self();
+     }
 -    
++
+     public F setOnSuccess(T val) {
+         return onSuccess(Functions.constant(val));
+     }
 -    
 -    /** a failure is when the connection is fine (no exception) but the other end returns a result object V 
 -     * which the feed can tell indicates a failure (e.g. HTTP code 404) */
++
++    /**
++     * A failure is when the connection is fine (no exception) but the other end returns a result object V
++     * which the feed can tell indicates a failure (e.g. HTTP code 404)
++     */
+     public F onFailure(Function<? super V,T> val) {
+         this.onfailure = checkNotNull(val, "onFailure");
+         return self();
+     }
+ 
++    /** @see #onFailure(Function) */
+     public F setOnFailure(T val) {
+         return onFailure(Functions.constant(val));
+     }
+ 
 -    /** registers a callback to be used {@link #onSuccess(Function)} and {@link #onFailure(Function)}, 
 -     * i.e. whenever a result comes back, but not in case of exceptions being thrown (ie problems communicating) */
++    /**
++     * Registers a callback to be used by {@link #onSuccess(Function)} and {@link #onFailure(Function)},
++     * i.e. whenever a result comes back, but not in case of exceptions being thrown (ie problems communicating)
++     */
+     public F onResult(Function<? super V, T> val) {
+         onSuccess(val);
+         return onFailure(val);
+     }
+ 
++    /** @see #onResult(Function) */
+     public F setOnResult(T val) {
+         return onResult(Functions.constant(val));
+     }
+ 
 -    /** an exception is when there is an error in the communication */
++    /**
++     * An exception is when there is an error in the communication
++     */
+     public F onException(Function<? super Exception,T> val) {
+         this.onexception = checkNotNull(val, "onException");
+         return self();
+     }
 -    
++
++    /** @see #onException(Function) */
+     public F setOnException(T val) {
+         return onException(Functions.constant(val));
+     }
+ 
 -    /** convenience for indicating a behaviour to occur for both
 -     * {@link #onException(Function)}
 -     * (error connecting) and 
 -     * {@link #onFailure(Function)} 
 -     * (successful communication but failure report from remote end) */
++    /**
++     * A convenience for indicating a behaviour to occur for both {@link #onException(Function)}
++     * (error connecting) and {@link #onFailure(Function)} (successful communication but failure
++     * report from remote end)
++     */
+     public F onFailureOrException(Function<Object,T> val) {
+         onFailure(val);
+         return onException(val);
+     }
 -    
++
++    /** @see #onFailureOrException(Function) */
+     public F setOnFailureOrException(T val) {
+         return onFailureOrException(Functions.constant(val));
+     }
+ 
+     public F suppressDuplicates(boolean val) {
+         suppressDuplicates = val;
+         return self();
+     }
 -    
++
+     /**
+      * Whether this feed is enabled (defaulting to true).
+      */
+     public F enabled(boolean val) {
+         enabled = val;
+         return self();
+     }
 -    
++
+     public boolean hasSuccessHandler() {
+         return this.onsuccess != null;
+     }
+ 
+     public boolean hasFailureHandler() {
+         return this.onfailure != null;
+     }
+ 
+     public boolean hasExceptionHandler() {
+         return this.onexception != null;
+     }
+ 
+     public boolean hasCheckSuccessHandler() {
+         return this.checkSuccess != null;
+     }
 -
+     
+     @Override
+     public String toString() {
+         StringBuilder result = new StringBuilder();
+         result.append(toStringBaseName());
+         result.append("[");
+         boolean contents = false;
+         Object source = toStringPollSource();
+         AttributeSensor<T> s = getSensor();
+         if (Strings.isNonBlank(Strings.toString(source))) {
+             result.append(Strings.toUniqueString(source, 40));
+             if (s!=null) {
+                 result.append("->");
+                 result.append(s.getName());
+             }
+             contents = true;
+         } else if (s!=null) {
+             result.append(s.getName());
+             contents = true;
+         }
+         MutableList<Object> fields = toStringOtherFields();
+         if (fields!=null) {
+             for (Object field: fields) {
+                 if (Strings.isNonBlank(Strings.toString(field))) {
+                     if (contents) result.append(";");
+                     contents = true;
+                     result.append(field);
+                 }
+             }
+         }
+         result.append("]");
+         return result.toString();
+     }
+ 
+     /** can be overridden to supply a simpler base name than the class name */
+     protected String toStringBaseName() {
+         return JavaClassNames.simpleClassName(this);
+     }
+     /** can be overridden to supply add'l info for the {@link #toString()}; subclasses can add to the returned value */
+     protected MutableList<Object> toStringOtherFields() {
+         return MutableList.<Object>of();
+     }
+     /** can be overridden to supply add'l info for the {@link #toString()}, placed before the sensor with -> */
+     protected Object toStringPollSource() {
+         return null;
+     }
+     /** all configs should supply a unique tag element, inserted into the feed */
+     protected String getUniqueTag() {
+         return toString();
+     }
+ 
+     /** returns fields which should be used for equality, including by default {@link #toStringOtherFields()} and {@link #toStringPollSource()};
+      * subclasses can add to the returned value */
+     protected MutableList<Object> equalsFields() {
+         MutableList<Object> result = MutableList.of().appendIfNotNull(getSensor()).appendIfNotNull(toStringPollSource());
+         for (Object field: toStringOtherFields()) result.appendIfNotNull(field);
+         return result;
+     }
+ 
+     @Override
+     public int hashCode() { 
+         int hc = super.hashCode();
+         for (Object f: equalsFields())
+             hc = Objects.hashCode(hc, f);
+         return hc;
+     }
+ 
+     @Override
+     public boolean equals(Object obj) {
+         if (this == obj) return true;
+         if (!super.equals(obj)) return false;
+         PollConfig<?,?,?> other = (PollConfig<?,?,?>) obj;
+         if (!Objects.equal(getUniqueTag(), other.getUniqueTag())) return false;
+         if (!Objects.equal(equalsFields(), other.equalsFields())) return false;
+         return true;
+     }
+ 
+ }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java
----------------------------------------------------------------------
diff --cc brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java
index 0000000,c1ffb42..75b7b34
mode 000000,100644..100644
--- a/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java
+++ b/brooklyn-server/core/src/main/java/org/apache/brooklyn/core/internal/BrooklynProperties.java
@@@ -1,0 -1,481 +1,305 @@@
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *     http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.brooklyn.core.internal;
+ 
+ import static com.google.common.base.Preconditions.checkNotNull;
 -import groovy.lang.Closure;
+ 
+ import java.io.File;
 -import java.io.FileInputStream;
 -import java.io.FileNotFoundException;
 -import java.io.IOException;
+ import java.io.InputStream;
+ import java.net.URL;
 -import java.util.Arrays;
 -import java.util.LinkedHashMap;
+ import java.util.Map;
 -import java.util.NoSuchElementException;
 -import java.util.Properties;
+ 
+ import org.apache.brooklyn.config.ConfigKey;
+ import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
+ import org.apache.brooklyn.config.StringConfigMap;
 -import org.apache.brooklyn.core.config.BasicConfigKey;
 -import org.apache.brooklyn.util.collections.MutableMap;
+ import org.apache.brooklyn.util.core.ResourceUtils;
+ import org.apache.brooklyn.util.core.config.ConfigBag;
 -import org.apache.brooklyn.util.core.flags.TypeCoercions;
+ import org.apache.brooklyn.util.guava.Maybe;
+ import org.apache.brooklyn.util.os.Os;
 -import org.apache.brooklyn.util.text.StringFunctions;
 -import org.apache.brooklyn.util.text.Strings;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ import com.google.common.annotations.Beta;
 -import com.google.common.base.CharMatcher;
+ import com.google.common.base.Objects;
+ import com.google.common.base.Predicate;
 -import com.google.common.base.Throwables;
 -import com.google.common.collect.Maps;
+ 
 -/** utils for accessing command-line and system-env properties;
++/** 
++ * Utils for accessing command-line and system-env properties;
+  * doesn't resolve anything (unless an execution context is supplied)
+  * and treats ConfigKeys as of type object when in doubt,
+  * or string when that is likely wanted (e.g. {@link #getFirst(Map, String...)}
+  * <p>
 - * TODO methods in this class are not thread safe.
 - * intention is that they are set during startup and not modified thereafter. */
++ * Intention for normal use is that they are set during startup and not modified 
++ * thereafter.
++ */
+ @SuppressWarnings("rawtypes")
 -public class BrooklynProperties extends LinkedHashMap implements StringConfigMap {
 -
 -    private static final long serialVersionUID = -945875483083108978L;
 -    private static final Logger LOG = LoggerFactory.getLogger(BrooklynProperties.class);
++public interface BrooklynProperties extends Map, StringConfigMap {
+ 
+     public static class Factory {
++        private static final Logger LOG = LoggerFactory.getLogger(BrooklynProperties.Factory.class);
++        
+         /** creates a new empty {@link BrooklynProperties} */
+         public static BrooklynProperties newEmpty() {
 -            return new BrooklynProperties();
++            return new BrooklynPropertiesImpl();
+         }
+ 
+         /** creates a new {@link BrooklynProperties} with contents loaded 
+          * from the usual places, including *.properties files and environment variables */
+         public static BrooklynProperties newDefault() {
+             return new Builder(true).build();
+         }
+ 
+         public static Builder builderDefault() {
+             return new Builder(true);
+         }
+ 
+         public static Builder builderEmpty() {
+             return new Builder(false);
+         }
+ 
+         public static class Builder {
+             private String defaultLocationMetadataUrl;
+             private String globalLocationMetadataFile = null;
+             private String globalPropertiesFile = null;
+             private String localPropertiesFile = null;
+             private BrooklynProperties originalProperties = null;
+             
+             /** @deprecated since 0.7.0 use static methods in {@link Factory} to create */
+             public Builder() {
+                 this(true);
+             }
+             
+             private Builder(boolean setGlobalFileDefaults) {
+                 resetDefaultLocationMetadataUrl();
+                 if (setGlobalFileDefaults) {
+                     resetGlobalFiles();
+                 }
+             }
+             
+             public Builder resetDefaultLocationMetadataUrl() {
+                 defaultLocationMetadataUrl = "classpath://brooklyn/location-metadata.properties";
+                 return this;
+             }
+             public Builder resetGlobalFiles() {
+                 defaultLocationMetadataUrl = "classpath://brooklyn/location-metadata.properties";
+                 globalLocationMetadataFile = Os.mergePaths(Os.home(), ".brooklyn", "location-metadata.properties");
+                 globalPropertiesFile = Os.mergePaths(Os.home(), ".brooklyn", "brooklyn.properties");
+                 return this;
+             }
+             
+             /**
+              * Creates a Builder that when built, will return the BrooklynProperties passed to this constructor
+              */
+             private Builder(BrooklynProperties originalProperties) {
 -                this.originalProperties = new BrooklynProperties().addFromMap(originalProperties);
++                this.originalProperties = new BrooklynPropertiesImpl().addFromMap(originalProperties);
+             }
+             
+             /**
+              * The URL of a default location-metadata.properties (for meta-data about different locations, such as iso3166 and global lat/lon). 
+              * Defaults to classpath://brooklyn/location-metadata.properties
+              */
+             public Builder defaultLocationMetadataUrl(String val) {
+                 defaultLocationMetadataUrl = checkNotNull(val, "file");
+                 return this;
+             }
+             
+             /**
+              * The URL of a location-metadata.properties file that appends to and overwrites values in the locationMetadataUrl. 
+              * Defaults to ~/.brooklyn/location-metadata.properties
+              */
+             public Builder globalLocationMetadataFile(String val) {
+                 globalLocationMetadataFile = checkNotNull(val, "file");
+                 return this;
+             }
+             
+             /**
+              * The URL of a shared brooklyn.properties file. Defaults to ~/.brooklyn/brooklyn.properties.
+              * Can be null to disable.
+              */
+             public Builder globalPropertiesFile(String val) {
+                 globalPropertiesFile = val;
+                 return this;
+             }
+             
+             @Beta
+             public boolean hasDelegateOriginalProperties() {
+                 return this.originalProperties==null;
+             }
+             
+             /**
+              * The URL of a brooklyn.properties file specific to this launch. Appends to and overwrites values in globalPropertiesFile.
+              */
+             public Builder localPropertiesFile(String val) {
+                 localPropertiesFile = val;
+                 return this;
+             }
+             
+             public BrooklynProperties build() {
+                 if (originalProperties != null) 
 -                    return new BrooklynProperties().addFromMap(originalProperties);
++                    return new BrooklynPropertiesImpl().addFromMap(originalProperties);
+                 
 -                BrooklynProperties properties = new BrooklynProperties();
++                BrooklynProperties properties = new BrooklynPropertiesImpl();
+ 
+                 // TODO Could also read from http://brooklyn.io, for up-to-date values?
+                 // But might that make unit tests run very badly when developer is offline?
+                 addPropertiesFromUrl(properties, defaultLocationMetadataUrl, false);
+                 
+                 addPropertiesFromFile(properties, globalLocationMetadataFile);
+                 addPropertiesFromFile(properties, globalPropertiesFile);
+                 addPropertiesFromFile(properties, localPropertiesFile);
+                 
+                 properties.addEnvironmentVars();
+                 properties.addSystemProperties();
+ 
+                 return properties;
+             }
+ 
+             public static Builder fromProperties(BrooklynProperties brooklynProperties) {
+                 return new Builder(brooklynProperties);
+             }
+ 
+             @Override
+             public String toString() {
+                 return Objects.toStringHelper(this)
+                         .omitNullValues()
+                         .add("originalProperties", originalProperties)
+                         .add("defaultLocationMetadataUrl", defaultLocationMetadataUrl)
+                         .add("globalLocationMetadataUrl", globalLocationMetadataFile)
+                         .add("globalPropertiesFile", globalPropertiesFile)
+                         .add("localPropertiesFile", localPropertiesFile)
+                         .toString();
+             }
+         }
+         
+         private static void addPropertiesFromUrl(BrooklynProperties p, String url, boolean warnIfNotFound) {
+             if (url==null) return;
+             
+             try {
+                 p.addFrom(ResourceUtils.create(BrooklynProperties.class).getResourceFromUrl(url));
+             } catch (Exception e) {
+                 if (warnIfNotFound)
+                     LOG.warn("Could not load {}; continuing", url);
+                 if (LOG.isTraceEnabled()) LOG.trace("Could not load "+url+"; continuing", e);
+             }
+         }
+         
+         private static void addPropertiesFromFile(BrooklynProperties p, String file) {
+             if (file==null) return;
+             
+             String fileTidied = Os.tidyPath(file);
+             File f = new File(fileTidied);
+ 
+             if (f.exists()) {
+                 p.addFrom(f);
+             }
+         }
+     }
+ 
 -    protected BrooklynProperties() {
 -    }
++    public BrooklynProperties addEnvironmentVars();
+ 
 -    public BrooklynProperties addEnvironmentVars() {
 -        addFrom(System.getenv());
 -        return this;
 -    }
 -
 -    public BrooklynProperties addSystemProperties() {
 -        addFrom(System.getProperties());
 -        return this;
 -    }
++    public BrooklynProperties addSystemProperties();
+ 
 -    public BrooklynProperties addFrom(ConfigBag cfg) {
 -        addFrom(cfg.getAllConfig());
 -        return this;
 -    }
++    public BrooklynProperties addFrom(ConfigBag cfg);
+ 
 -    @SuppressWarnings("unchecked")
 -    public BrooklynProperties addFrom(Map map) {
 -        putAll(Maps.transformValues(map, StringFunctions.trim()));
 -        return this;
 -    }
++    public BrooklynProperties addFrom(Map map);
+ 
 -    public BrooklynProperties addFrom(InputStream i) {
 -        // Ugly way to load them in order, but Properties is a Hashtable so loses order otherwise.
 -        @SuppressWarnings({ "serial" })
 -        Properties p = new Properties() {
 -            @Override
 -            public synchronized Object put(Object key, Object value) {
 -                // Trim the string values to remove leading and trailing spaces
 -                String s = (String) value;
 -                if (Strings.isBlank(s)) {
 -                    s = Strings.EMPTY;
 -                } else {
 -                    s = CharMatcher.BREAKING_WHITESPACE.trimFrom(s);
 -                }
 -                return BrooklynProperties.this.put(key, s);
 -            }
 -        };
 -        try {
 -            p.load(i);
 -        } catch (IOException e) {
 -            throw Throwables.propagate(e);
 -        }
 -        return this;
 -    }
++    public BrooklynProperties addFrom(InputStream i);
+     
 -    public BrooklynProperties addFrom(File f) {
 -        if (!f.exists()) {
 -            LOG.warn("Unable to find file '"+f.getAbsolutePath()+"' when loading properties; ignoring");
 -            return this;
 -        } else {
 -            try {
 -                return addFrom(new FileInputStream(f));
 -            } catch (FileNotFoundException e) {
 -                throw Throwables.propagate(e);
 -            }
 -        }
 -    }
 -    public BrooklynProperties addFrom(URL u) {
 -        try {
 -            return addFrom(u.openStream());
 -        } catch (IOException e) {
 -            throw new RuntimeException("Error reading properties from "+u+": "+e, e);
 -        }
 -    }
++    public BrooklynProperties addFrom(File f);
++
++    public BrooklynProperties addFrom(URL u);
++
+     /**
+      * @see ResourceUtils#getResourceFromUrl(String)
+      *
+      * of the form form file:///home/... or http:// or classpath://xx ;
+      * for convenience if not starting with xxx: it is treated as a classpath reference or a file;
+      * throws if not found (but does nothing if argument is null)
+      */
 -    public BrooklynProperties addFromUrl(String url) {
 -        try {
 -            if (url==null) return this;
 -            return addFrom(ResourceUtils.create(this).getResourceFromUrl(url));
 -        } catch (Exception e) {
 -            throw new RuntimeException("Error reading properties from "+url+": "+e, e);
 -        }
 -    }
++    public BrooklynProperties addFromUrl(String url);
+ 
+     /** expects a property already set in scope, whose value is acceptable to {@link #addFromUrl(String)};
+      * if property not set, does nothing */
 -    public BrooklynProperties addFromUrlProperty(String urlProperty) {
 -        String url = (String) get(urlProperty);
 -        if (url==null) addFromUrl(url);
 -        return this;
 -    }
++    public BrooklynProperties addFromUrlProperty(String urlProperty);
+ 
+     /**
+     * adds the indicated properties
+     */
 -    public BrooklynProperties addFromMap(Map properties) {
 -        putAll(properties);
 -        return this;
 -    }
++    public BrooklynProperties addFromMap(Map properties);
+ 
+     /** inserts the value under the given key, if it was not present */
 -    public boolean putIfAbsent(String key, Object value) {
 -        if (containsKey(key)) return false;
 -        put(key, value);
 -        return true;
 -    }
++    public boolean putIfAbsent(String key, Object value);
+ 
+    /** @deprecated attempts to call get with this syntax are probably mistakes; get(key, defaultValue) is fine but
+     * Map is unlikely the key, much more likely they meant getFirst(flags, key).
+     */
+    @Deprecated
 -   public String get(Map flags, String key) {
 -       LOG.warn("Discouraged use of 'BrooklynProperties.get(Map,String)' (ambiguous); use getFirst(Map,String) or get(String) -- assuming the former");
 -       LOG.debug("Trace for discouraged use of 'BrooklynProperties.get(Map,String)'",
 -           new Throwable("Arguments: "+flags+" "+key));
 -       return getFirst(flags, key);
 -   }
++   public String get(Map flags, String key);
+ 
+     /** returns the value of the first key which is defined
+      * <p>
+      * takes the following flags:
+      * 'warnIfNone', 'failIfNone' (both taking a boolean (to use default message) or a string (which is the message));
+      * and 'defaultIfNone' (a default value to return if there is no such property); defaults to no warning and null response */
+     @Override
 -    public String getFirst(String ...keys) {
 -       return getFirst(MutableMap.of(), keys);
 -    }
 -    @Override
 -    public String getFirst(Map flags, String ...keys) {
 -        for (String k: keys) {
 -            if (k!=null && containsKey(k)) return (String) get(k);
 -        }
 -        if (flags.get("warnIfNone")!=null && !Boolean.FALSE.equals(flags.get("warnIfNone"))) {
 -            if (Boolean.TRUE.equals(flags.get("warnIfNone")))
 -                LOG.warn("Unable to find Brooklyn property "+keys);
 -            else
 -                LOG.warn(""+flags.get("warnIfNone"));
 -        }
 -        if (flags.get("failIfNone")!=null && !Boolean.FALSE.equals(flags.get("failIfNone"))) {
 -            Object f = flags.get("failIfNone");
 -            if (f instanceof Closure)
 -                ((Closure)f).call((Object[])keys);
 -            if (Boolean.TRUE.equals(f))
 -                throw new NoSuchElementException("Brooklyn unable to find mandatory property "+keys[0]+
 -                    (keys.length>1 ? " (or "+(keys.length-1)+" other possible names, full list is "+Arrays.asList(keys)+")" : "") );
 -            else
 -                throw new NoSuchElementException(""+f);
 -        }
 -        if (flags.get("defaultIfNone")!=null) {
 -            return (String) flags.get("defaultIfNone");
 -        }
 -        return null;
 -    }
++    public String getFirst(String ...keys);
+ 
+     @Override
 -    public String toString() {
 -        return "BrooklynProperties["+size()+"]";
 -    }
++    public String getFirst(Map flags, String ...keys);
+ 
+     /** like normal map.put, except config keys are dereferenced on the way in */
 -    @SuppressWarnings("unchecked")
 -    public Object put(Object key, Object value) {
 -        if (key instanceof HasConfigKey) key = ((HasConfigKey)key).getConfigKey().getName();
 -        if (key instanceof ConfigKey) key = ((ConfigKey)key).getName();
 -        return super.put(key, value);
 -    }
++    public Object put(Object key, Object value);
+ 
+     /** like normal map.putAll, except config keys are dereferenced on the way in */
+     @Override
 -    public void putAll(Map vals) {
 -        for (Map.Entry<?,?> entry : ((Map<?,?>)vals).entrySet()) {
 -            put(entry.getKey(), entry.getValue());
 -        }
 -    }
++    public void putAll(Map vals);
+     
 -    @SuppressWarnings("unchecked")
 -    public <T> Object put(HasConfigKey<T> key, T value) {
 -        return super.put(key.getConfigKey().getName(), value);
 -    }
++    public <T> Object put(HasConfigKey<T> key, T value);
+ 
 -    @SuppressWarnings("unchecked")
 -    public <T> Object put(ConfigKey<T> key, T value) {
 -        return super.put(key.getName(), value);
 -    }
++    public <T> Object put(ConfigKey<T> key, T value);
+     
 -    public <T> boolean putIfAbsent(ConfigKey<T> key, T value) {
 -        return putIfAbsent(key.getName(), value);
 -    }
++    public <T> boolean putIfAbsent(ConfigKey<T> key, T value);
+     
+     @Override
 -    public <T> T getConfig(ConfigKey<T> key) {
 -        return getConfig(key, null);
 -    }
++    public <T> T getConfig(ConfigKey<T> key);
+ 
+     @Override
 -    public <T> T getConfig(HasConfigKey<T> key) {
 -        return getConfig(key.getConfigKey(), null);
 -    }
++    public <T> T getConfig(HasConfigKey<T> key);
+ 
+     @Override
 -    public <T> T getConfig(HasConfigKey<T> key, T defaultValue) {
 -        return getConfig(key.getConfigKey(), defaultValue);
 -    }
++    public <T> T getConfig(HasConfigKey<T> key, T defaultValue);
+ 
+     @Override
 -    public <T> T getConfig(ConfigKey<T> key, T defaultValue) {
 -        // TODO does not support MapConfigKey etc where entries use subkey notation; for now, access using submap
 -        if (!containsKey(key.getName())) {
 -            if (defaultValue!=null) return defaultValue;
 -            return key.getDefaultValue();
 -        }
 -        Object value = get(key.getName());
 -        if (value==null) return null;
 -        // no evaluation / key extraction here
 -        return TypeCoercions.coerce(value, key.getTypeToken());
 -    }
++    public <T> T getConfig(ConfigKey<T> key, T defaultValue);
+ 
+     @Override
 -    public Object getRawConfig(ConfigKey<?> key) {
 -        return get(key.getName());
 -    }
++    public Object getRawConfig(ConfigKey<?> key);
+     
+     @Override
 -    public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited) {
 -        if (containsKey(key.getName())) return Maybe.of(get(key.getName()));
 -        return Maybe.absent();
 -    }
++    public Maybe<Object> getConfigRaw(ConfigKey<?> key, boolean includeInherited);
+ 
+     @Override
 -    public Map<ConfigKey<?>, Object> getAllConfig() {
 -        Map<ConfigKey<?>, Object> result = new LinkedHashMap<ConfigKey<?>, Object>();
 -        for (Object entry: entrySet())
 -            result.put(new BasicConfigKey<Object>(Object.class, ""+((Map.Entry)entry).getKey()), ((Map.Entry)entry).getValue());
 -        return result;
 -    }
++    public Map<ConfigKey<?>, Object> getAllConfig();
+ 
+     @Override
 -    public BrooklynProperties submap(Predicate<ConfigKey<?>> filter) {
 -        BrooklynProperties result = Factory.newEmpty();
 -        for (Object entry: entrySet()) {
 -            ConfigKey<?> k = new BasicConfigKey<Object>(Object.class, ""+((Map.Entry)entry).getKey());
 -            if (filter.apply(k))
 -                result.put(((Map.Entry)entry).getKey(), ((Map.Entry)entry).getValue());
 -        }
 -        return result;
 -    }
++    public BrooklynProperties submap(Predicate<ConfigKey<?>> filter);
+ 
 -    @SuppressWarnings("unchecked")
+     @Override
 -    public Map<String, Object> asMapWithStringKeys() {
 -        return this;
 -    }
 -
++    public Map<String, Object> asMapWithStringKeys();
+ }