brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From henev...@apache.org
Subject [15/36] incubator-brooklyn git commit: Rename o.a.b.sensor.core to o.a.b.core.sensor
Date Wed, 19 Aug 2015 22:54:03 GMT
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/core/sensor/DependentConfiguration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/DependentConfiguration.java b/core/src/main/java/org/apache/brooklyn/core/sensor/DependentConfiguration.java
new file mode 100644
index 0000000..b909856
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/DependentConfiguration.java
@@ -0,0 +1,823 @@
+/*
+ * 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.sensor;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import groovy.lang.Closure;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.mgmt.ExecutionContext;
+import org.apache.brooklyn.api.mgmt.SubscriptionHandle;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.api.mgmt.TaskAdaptable;
+import org.apache.brooklyn.api.mgmt.TaskFactory;
+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.core.entity.Attributes;
+import org.apache.brooklyn.core.entity.Entities;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.util.collections.CollectionFunctionals;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.task.BasicExecutionContext;
+import org.apache.brooklyn.util.core.task.BasicTask;
+import org.apache.brooklyn.util.core.task.DeferredSupplier;
+import org.apache.brooklyn.util.core.task.DynamicTasks;
+import org.apache.brooklyn.util.core.task.ParallelTask;
+import org.apache.brooklyn.util.core.task.TaskInternal;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.core.task.ValueResolver;
+import org.apache.brooklyn.util.exceptions.CompoundRuntimeException;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.exceptions.NotManagedException;
+import org.apache.brooklyn.util.exceptions.RuntimeTimeoutException;
+import org.apache.brooklyn.util.groovy.GroovyJavaMethods;
+import org.apache.brooklyn.util.guava.Functionals;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.CountdownTimer;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+/** Conveniences for making tasks which run in entity {@link ExecutionContext}s, subscribing to attributes from other entities, possibly transforming those;
+ * these {@link Task} instances are typically passed in {@link EntityLocal#setConfig(ConfigKey, Object)}.
+ * <p>
+ * If using a lot it may be useful to:
+ * <pre>
+ * {@code
+ *   import static brooklyn.event.basic.DependentConfiguration.*;
+ * }
+ * </pre>
+ */
+public class DependentConfiguration {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DependentConfiguration.class);
+    
+    //not instantiable, only a static helper
+    private DependentConfiguration() {}
+
+    /**
+     * Default readiness is Groovy truth.
+     * 
+     * @see #attributeWhenReady(Entity, AttributeSensor, Predicate)
+     */
+    public static <T> Task<T> attributeWhenReady(Entity source, AttributeSensor<T> sensor) {
+        return attributeWhenReady(source, sensor, GroovyJavaMethods.truthPredicate());
+    }
+    
+    public static <T> Task<T> attributeWhenReady(Entity source, AttributeSensor<T> sensor, Closure<Boolean> ready) {
+        Predicate<Object> readyPredicate = (ready != null) ? GroovyJavaMethods.<Object>predicateFromClosure(ready) : GroovyJavaMethods.truthPredicate();
+        return attributeWhenReady(source, sensor, readyPredicate);
+    }
+    
+    /** returns an unsubmitted {@link Task} which blocks until the given sensor on the given source entity gives a value that satisfies ready, then returns that value;
+     * particular useful in Entity configuration where config will block until Tasks have a value
+     */
+    public static <T> Task<T> attributeWhenReady(final Entity source, final AttributeSensor<T> sensor, final Predicate<? super T> ready) {
+        Builder<T, T> builder = builder().attributeWhenReady(source, sensor);
+        if (ready != null) builder.readiness(ready);
+        return builder.build();
+
+    }
+
+    public static <T,V> Task<V> attributePostProcessedWhenReady(Entity source, AttributeSensor<T> sensor, Closure<Boolean> ready, Closure<V> postProcess) {
+        Predicate<? super T> readyPredicate = (ready != null) ? GroovyJavaMethods.predicateFromClosure(ready) : GroovyJavaMethods.truthPredicate();
+        Function<? super T, V> postProcessFunction = GroovyJavaMethods.<T,V>functionFromClosure(postProcess);
+        return attributePostProcessedWhenReady(source, sensor, readyPredicate, postProcessFunction);
+    }
+
+    public static <T,V> Task<V> attributePostProcessedWhenReady(Entity source, AttributeSensor<T> sensor, Closure<V> postProcess) {
+        return attributePostProcessedWhenReady(source, sensor, GroovyJavaMethods.truthPredicate(), GroovyJavaMethods.<T,V>functionFromClosure(postProcess));
+    }
+
+    public static <T> Task<T> valueWhenAttributeReady(Entity source, AttributeSensor<T> sensor, T value) {
+        return DependentConfiguration.<T,T>attributePostProcessedWhenReady(source, sensor, GroovyJavaMethods.truthPredicate(), Functions.constant(value));
+    }
+
+    public static <T,V> Task<V> valueWhenAttributeReady(Entity source, AttributeSensor<T> sensor, Function<? super T,V> valueProvider) {
+        return attributePostProcessedWhenReady(source, sensor, GroovyJavaMethods.truthPredicate(), valueProvider);
+    }
+    
+    public static <T,V> Task<V> valueWhenAttributeReady(Entity source, AttributeSensor<T> sensor, Closure<V> valueProvider) {
+        return attributePostProcessedWhenReady(source, sensor, GroovyJavaMethods.truthPredicate(), valueProvider);
+    }
+    
+    public static <T,V> Task<V> attributePostProcessedWhenReady(final Entity source, final AttributeSensor<T> sensor, final Predicate<? super T> ready, final Closure<V> postProcess) {
+        return attributePostProcessedWhenReady(source, sensor, ready, GroovyJavaMethods.<T,V>functionFromClosure(postProcess));
+    }
+    
+    @SuppressWarnings("unchecked")
+    public static <T,V> Task<V> attributePostProcessedWhenReady(final Entity source, final AttributeSensor<T> sensor, final Predicate<? super T> ready, final Function<? super T,V> postProcess) {
+        Builder<T,T> builder1 = DependentConfiguration.builder().attributeWhenReady(source, sensor);
+        // messy generics here to support null postProcess; would be nice to disallow that here
+        Builder<T,V> builder;
+        if (postProcess != null) {
+            builder = builder1.postProcess(postProcess);
+        } else {
+            builder = (Builder<T,V>)builder1;
+        }
+        if (ready != null) builder.readiness(ready);
+        
+        return builder.build();
+    }
+
+    public static <T> T waitInTaskForAttributeReady(Entity source, AttributeSensor<T> sensor, Predicate<? super T> ready) {
+        return waitInTaskForAttributeReady(source, sensor, ready, ImmutableList.<AttributeAndSensorCondition<?>>of());
+    }
+
+    public static <T> T waitInTaskForAttributeReady(final Entity source, final AttributeSensor<T> sensor, Predicate<? super T> ready, List<AttributeAndSensorCondition<?>> abortConditions) {
+        String blockingDetails = "Waiting for ready from "+source+" "+sensor+" (subscription)";
+        return waitInTaskForAttributeReady(source, sensor, ready, abortConditions, blockingDetails);
+    }
+    
+    // TODO would be nice to have an easy semantics for whenServiceUp (cf DynamicWebAppClusterImpl.whenServiceUp)
+    
+    public static <T> T waitInTaskForAttributeReady(final Entity source, final AttributeSensor<T> sensor, Predicate<? super T> ready, List<AttributeAndSensorCondition<?>> abortConditions, String blockingDetails) {
+        return new WaitInTaskForAttributeReady<T,T>(source, sensor, ready, abortConditions, blockingDetails).call();
+    }
+    
+    protected static class WaitInTaskForAttributeReady<T,V> implements Callable<V> {
+
+        /* This is a change since before Oct 2014. Previously it would continue to poll,
+         * (maybe finding a different error) if the target entity becomes unmanaged. 
+         * Now it actively checks unmanaged by default, and still throws although it might 
+         * now find a different problem. */
+        private final static boolean DEFAULT_IGNORE_UNMANAGED = false;
+        
+        protected final Entity source;
+        protected final AttributeSensor<T> sensor;
+        protected final Predicate<? super T> ready;
+        protected final List<AttributeAndSensorCondition<?>> abortSensorConditions;
+        protected final String blockingDetails;
+        protected final Function<? super T,? extends V> postProcess;
+        protected final Duration timeout;
+        protected final Maybe<V> onTimeout;
+        protected final boolean ignoreUnmanaged;
+        protected final Maybe<V> onUnmanaged;
+        // TODO onError Continue / Throw / Return(V)
+        
+        protected WaitInTaskForAttributeReady(Builder<T, V> builder) {
+            this.source = builder.source;
+            this.sensor = builder.sensor;
+            this.ready = builder.readiness;
+            this.abortSensorConditions = builder.abortSensorConditions;
+            this.blockingDetails = builder.blockingDetails;
+            this.postProcess = builder.postProcess;
+            this.timeout = builder.timeout;
+            this.onTimeout = builder.onTimeout;
+            this.ignoreUnmanaged = builder.ignoreUnmanaged;
+            this.onUnmanaged = builder.onUnmanaged;
+        }
+        
+        private WaitInTaskForAttributeReady(Entity source, AttributeSensor<T> sensor, Predicate<? super T> ready,
+                List<AttributeAndSensorCondition<?>> abortConditions, String blockingDetails) {
+            this.source = source;
+            this.sensor = sensor;
+            this.ready = ready;
+            this.abortSensorConditions = abortConditions;
+            this.blockingDetails = blockingDetails;
+            
+            this.timeout = Duration.PRACTICALLY_FOREVER;
+            this.onTimeout = Maybe.absent();
+            this.ignoreUnmanaged = DEFAULT_IGNORE_UNMANAGED;
+            this.onUnmanaged = Maybe.absent();
+            this.postProcess = null;
+        }
+
+        @SuppressWarnings("unchecked")
+        protected V postProcess(T value) {
+            if (this.postProcess!=null) return postProcess.apply(value);
+            // if no post-processing assume the types are correct
+            return (V) value;
+        }
+        
+        protected boolean ready(T value) {
+            if (ready!=null) return ready.apply(value);
+            return GroovyJavaMethods.truth(value);
+        }
+        
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        @Override
+        public V call() {
+            T value = source.getAttribute(sensor);
+
+            // return immediately if either the ready predicate or the abort conditions hold
+            if (ready(value)) return postProcess(value);
+
+            final List<Exception> abortionExceptions = Lists.newCopyOnWriteArrayList();
+            long start = System.currentTimeMillis();
+            
+            for (AttributeAndSensorCondition abortCondition : abortSensorConditions) {
+                Object abortValue = abortCondition.source.getAttribute(abortCondition.sensor);
+                if (abortCondition.predicate.apply(abortValue)) {
+                    abortionExceptions.add(new Exception("Abort due to "+abortCondition.source+" -> "+abortCondition.sensor));
+                }
+            }
+            if (abortionExceptions.size() > 0) {
+                throw new CompoundRuntimeException("Aborted waiting for ready from "+source+" "+sensor, abortionExceptions);
+            }
+
+            TaskInternal<?> current = (TaskInternal<?>) Tasks.current();
+            if (current == null) throw new IllegalStateException("Should only be invoked in a running task");
+            Entity entity = BrooklynTaskTags.getTargetOrContextEntity(current);
+            if (entity == null) throw new IllegalStateException("Should only be invoked in a running task with an entity tag; "+
+                current+" has no entity tag ("+current.getStatusDetail(false)+")");
+            
+            final LinkedList<T> publishedValues = new LinkedList<T>();
+            final Semaphore semaphore = new Semaphore(0); // could use Exchanger
+            SubscriptionHandle subscription = null;
+            List<SubscriptionHandle> abortSubscriptions = Lists.newArrayList();
+            
+            try {
+                subscription = ((EntityInternal)entity).getSubscriptionContext().subscribe(source, sensor, new SensorEventListener<T>() {
+                    @Override public void onEvent(SensorEvent<T> event) {
+                        synchronized (publishedValues) { publishedValues.add(event.getValue()); }
+                        semaphore.release();
+                    }});
+                for (final AttributeAndSensorCondition abortCondition : abortSensorConditions) {
+                    abortSubscriptions.add(((EntityInternal)entity).getSubscriptionContext().subscribe(abortCondition.source, abortCondition.sensor, new SensorEventListener<Object>() {
+                        @Override public void onEvent(SensorEvent<Object> event) {
+                            if (abortCondition.predicate.apply(event.getValue())) {
+                                abortionExceptions.add(new Exception("Abort due to "+abortCondition.source+" -> "+abortCondition.sensor));
+                                semaphore.release();
+                            }
+                        }}));
+                    Object abortValue = abortCondition.source.getAttribute(abortCondition.sensor);
+                    if (abortCondition.predicate.apply(abortValue)) {
+                        abortionExceptions.add(new Exception("Abort due to "+abortCondition.source+" -> "+abortCondition.sensor));
+                    }
+                }
+                if (abortionExceptions.size() > 0) {
+                    throw new CompoundRuntimeException("Aborted waiting for ready from "+source+" "+sensor, abortionExceptions);
+                }
+
+                CountdownTimer timer = timeout!=null ? timeout.countdownTimer() : null;
+                Duration maxPeriod = ValueResolver.PRETTY_QUICK_WAIT;
+                Duration nextPeriod = ValueResolver.REAL_QUICK_PERIOD;
+                while (true) {
+                    // check the source on initial run (could be done outside the loop) 
+                    // and also (optionally) on each iteration in case it is more recent 
+                    value = source.getAttribute(sensor);
+                    if (ready(value)) break;
+
+                    if (timer!=null) {
+                        if (timer.getDurationRemaining().isShorterThan(nextPeriod)) {
+                            nextPeriod = timer.getDurationRemaining();
+                        }
+                        if (timer.isExpired()) {
+                            if (onTimeout.isPresent()) return onTimeout.get();
+                            throw new RuntimeTimeoutException("Unsatisfied after "+Duration.sinceUtc(start));
+                        }
+                    }
+
+                    String prevBlockingDetails = current.setBlockingDetails(blockingDetails);
+                    try {
+                        if (semaphore.tryAcquire(nextPeriod.toMilliseconds(), TimeUnit.MILLISECONDS)) {
+                            // immediately release so we are available for the next check
+                            semaphore.release();
+                            // if other permits have been made available (e.g. multiple notifications) drain them all as no point running multiple times
+                            semaphore.drainPermits();
+                        }
+                    } finally {
+                        current.setBlockingDetails(prevBlockingDetails);
+                    }
+
+                    // check any subscribed values which have come in first
+                    while (true) {
+                        synchronized (publishedValues) {
+                            if (publishedValues.isEmpty()) break;
+                            value = publishedValues.pop(); 
+                        }
+                        if (ready(value)) break;
+                    }
+
+                    // if unmanaged then ignore the other abort conditions
+                    if (!ignoreUnmanaged && Entities.isNoLongerManaged(entity)) {
+                        if (onUnmanaged.isPresent()) return onUnmanaged.get();
+                        throw new NotManagedException(entity);                        
+                    }
+                    
+                    if (abortionExceptions.size() > 0) {
+                        throw new CompoundRuntimeException("Aborted waiting for ready from "+source+" "+sensor, abortionExceptions);
+                    }
+
+                    nextPeriod = nextPeriod.times(2).upperBound(maxPeriod);
+                }
+                if (LOG.isDebugEnabled()) LOG.debug("Attribute-ready for {} in entity {}", sensor, source);
+                return postProcess(value);
+            } catch (InterruptedException e) {
+                throw Exceptions.propagate(e);
+            } finally {
+                if (subscription != null) {
+                    ((EntityInternal)entity).getSubscriptionContext().unsubscribe(subscription);
+                }
+                for (SubscriptionHandle handle : abortSubscriptions) {
+                    ((EntityInternal)entity).getSubscriptionContext().unsubscribe(handle);
+                }
+            }
+        }
+    }
+    
+    /**
+     * Returns a {@link Task} which blocks until the given job returns, then returns the value of that job.
+     * 
+     * @deprecated since 0.7; code will be moved into test utilities
+     */
+    @Deprecated
+    public static <T> Task<T> whenDone(Callable<T> job) {
+        return new BasicTask<T>(MutableMap.of("tag", "whenDone", "displayName", "waiting for job"), job);
+    }
+
+    /**
+     * Returns a {@link Task} which waits for the result of first parameter, then applies the function in the second
+     * parameter to it, returning that result.
+     *
+     * Particular useful in Entity configuration where config will block until Tasks have completed,
+     * allowing for example an {@link #attributeWhenReady(Entity, AttributeSensor, Predicate)} expression to be
+     * passed in the first argument then transformed by the function in the second argument to generate
+     * the value that is used for the configuration
+     */
+    public static <U,T> Task<T> transform(final Task<U> task, final Function<U,T> transformer) {
+        return transform(MutableMap.of("displayName", "transforming "+task), task, transformer);
+    }
+ 
+    /** @see #transform(Task, Function) */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static <U,T> Task<T> transform(Task<U> task, Closure transformer) {
+        return transform(task, GroovyJavaMethods.functionFromClosure(transformer));
+    }
+    
+    /** @see #transform(Task, Function) */
+    @SuppressWarnings({ "rawtypes" })
+    public static <U,T> Task<T> transform(final Map flags, final TaskAdaptable<U> task, final Function<U,T> transformer) {
+        return new BasicTask<T>(flags, new Callable<T>() {
+            public T call() throws Exception {
+                if (!task.asTask().isSubmitted()) {
+                    BasicExecutionContext.getCurrentExecutionContext().submit(task);
+                } 
+                return transformer.apply(task.asTask().get());
+            }});        
+    }
+     
+    /** Returns a task which waits for multiple other tasks (submitting if necessary)
+     * and performs arbitrary computation over the List of results.
+     * @see #transform(Task, Function) but note argument order is reversed (counterintuitive) to allow for varargs */
+    public static <U,T> Task<T> transformMultiple(Function<List<U>,T> transformer, @SuppressWarnings("unchecked") TaskAdaptable<U> ...tasks) {
+        return transformMultiple(MutableMap.of("displayName", "transforming multiple"), transformer, tasks);
+    }
+
+    /** @see #transformMultiple(Function, TaskAdaptable...) */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static <U,T> Task<T> transformMultiple(Closure transformer, TaskAdaptable<U> ...tasks) {
+        return transformMultiple(GroovyJavaMethods.functionFromClosure(transformer), tasks);
+    }
+
+    /** @see #transformMultiple(Function, TaskAdaptable...) */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static <U,T> Task<T> transformMultiple(Map flags, Closure transformer, TaskAdaptable<U> ...tasks) {
+        return transformMultiple(flags, GroovyJavaMethods.functionFromClosure(transformer), tasks);
+    }
+    
+    /** @see #transformMultiple(Function, TaskAdaptable...) */
+    @SuppressWarnings({ "rawtypes" })
+    public static <U,T> Task<T> transformMultiple(Map flags, final Function<List<U>,T> transformer, @SuppressWarnings("unchecked") TaskAdaptable<U> ...tasks) {
+        return transformMultiple(flags, transformer, Arrays.asList(tasks));
+    }
+    @SuppressWarnings({ "rawtypes" })
+    public static <U,T> Task<T> transformMultiple(Map flags, final Function<List<U>,T> transformer, Collection<? extends TaskAdaptable<U>> tasks) {
+        if (tasks.size()==1) {
+            return transform(flags, Iterables.getOnlyElement(tasks), new Function<U,T>() {
+                @Override
+                @Nullable
+                public T apply(@Nullable U input) {
+                    return transformer.apply(ImmutableList.of(input));
+                }
+            });
+        }
+        return transform(flags, new ParallelTask<U>(tasks), transformer);
+    }
+
+
+    /** Method which returns a Future containing a string formatted using String.format,
+     * where the arguments can be normal objects or tasks;
+     * tasks will be waited on (submitted if necessary) and their results substituted in the call
+     * to String.format.
+     * <p>
+     * Example:
+     * <pre>
+     * {@code
+     *   setConfig(URL, DependentConfiguration.formatString("%s:%s", 
+     *           DependentConfiguration.attributeWhenReady(target, Target.HOSTNAME),
+     *           DependentConfiguration.attributeWhenReady(target, Target.PORT) ) );
+     * }
+     * </pre>
+     */
+    @SuppressWarnings("unchecked")
+    public static Task<String> formatString(final String spec, final Object ...args) {
+        List<TaskAdaptable<Object>> taskArgs = Lists.newArrayList();
+        for (Object arg: args) {
+            if (arg instanceof TaskAdaptable) taskArgs.add((TaskAdaptable<Object>)arg);
+            else if (arg instanceof TaskFactory) taskArgs.add( ((TaskFactory<TaskAdaptable<Object>>)arg).newTask() );
+        }
+            
+        return transformMultiple(
+            MutableMap.<String,String>of("displayName", "formatting '"+spec+"' with "+taskArgs.size()+" task"+(taskArgs.size()!=1?"s":"")), 
+                new Function<List<Object>, String>() {
+            @Override public String apply(List<Object> input) {
+                Iterator<?> tri = input.iterator();
+                Object[] vv = new Object[args.length];
+                int i=0;
+                for (Object arg : args) {
+                    if (arg instanceof TaskAdaptable || arg instanceof TaskFactory) vv[i] = tri.next();
+                    else if (arg instanceof DeferredSupplier) vv[i] = ((DeferredSupplier<?>) arg).get();
+                    else vv[i] = arg;
+                    i++;
+                }
+                return String.format(spec, vv);
+            }},
+            taskArgs);
+    }
+
+    /** returns a task for parallel execution returning a list of values for the given sensor for the given entity list, 
+     * optionally when the values satisfy a given readiness predicate (defaulting to groovy truth if not supplied) */
+    public static <T> Task<List<T>> listAttributesWhenReady(AttributeSensor<T> sensor, Iterable<Entity> entities) {
+        return listAttributesWhenReady(sensor, entities, GroovyJavaMethods.truthPredicate());
+    }
+    
+    public static <T> Task<List<T>> listAttributesWhenReady(AttributeSensor<T> sensor, Iterable<Entity> entities, Closure<Boolean> readiness) {
+        Predicate<Object> readinessPredicate = (readiness != null) ? GroovyJavaMethods.<Object>predicateFromClosure(readiness) : GroovyJavaMethods.truthPredicate();
+        return listAttributesWhenReady(sensor, entities, readinessPredicate);
+    }
+    
+    /** returns a task for parallel execution returning a list of values of the given sensor list on the given entity, 
+     * optionally when the values satisfy a given readiness predicate (defaulting to groovy truth if not supplied) */    
+    public static <T> Task<List<T>> listAttributesWhenReady(final AttributeSensor<T> sensor, Iterable<Entity> entities, Predicate<? super T> readiness) {
+        if (readiness == null) readiness = GroovyJavaMethods.truthPredicate();
+        return builder().attributeWhenReadyFromMultiple(entities, sensor, readiness).build();
+    }
+
+    /** @see #waitForTask(Task, Entity, String) */
+    public static <T> T waitForTask(Task<T> t, Entity context) throws InterruptedException {
+        return waitForTask(t, context, null);
+    }
+    
+    /** blocks until the given task completes, submitting if necessary, returning the result of that task;
+     * optional contextMessage is available in status if this is running in a task
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T waitForTask(Task<T> t, Entity context, String contextMessage) throws InterruptedException {
+        try {
+            return (T) Tasks.resolveValue(t, Object.class, ((EntityInternal)context).getExecutionContext(), contextMessage);
+        } catch (ExecutionException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+    
+    public static class AttributeAndSensorCondition<T> {
+        protected final Entity source;
+        protected final AttributeSensor<T> sensor;
+        protected final Predicate<? super T> predicate;
+        
+        public AttributeAndSensorCondition(Entity source, AttributeSensor<T> sensor, Predicate<? super T> predicate) {
+            this.source = checkNotNull(source, "source");
+            this.sensor = checkNotNull(sensor, "sensor");
+            this.predicate = checkNotNull(predicate, "predicate");
+        }
+    }
+    
+    public static ProtoBuilder builder() {
+        return new ProtoBuilder();
+    }
+    
+    /**
+     * Builder for producing variants of attributeWhenReady.
+     */
+    @Beta
+    public static class ProtoBuilder {
+        /**
+         * Will wait for the attribute on the given entity.
+         * If that entity reports {@link Lifecycle#ON_FIRE} for its {@link Attributes#SERVICE_STATE} then it will abort. 
+         */
+        public <T2> Builder<T2,T2> attributeWhenReady(Entity source, AttributeSensor<T2> sensor) {
+            return new Builder<T2,T2>(source, sensor).abortIfOnFire();
+        }
+
+        /**
+         * Will wait for the attribute on the given entity, not aborting when it goes {@link Lifecycle#ON_FIRE}.
+         */
+        public <T2> Builder<T2,T2> attributeWhenReadyAllowingOnFire(Entity source, AttributeSensor<T2> sensor) {
+            return new Builder<T2,T2>(source, sensor);
+        }
+
+        /** Constructs a builder for task for parallel execution returning a list of values of the given sensor list on the given entity, 
+         * optionally when the values satisfy a given readiness predicate (defaulting to groovy truth if not supplied) */ 
+        @Beta
+        public <T> MultiBuilder<T, T, List<T>> attributeWhenReadyFromMultiple(Iterable<? extends Entity> sources, AttributeSensor<T> sensor) {
+            return attributeWhenReadyFromMultiple(sources, sensor, GroovyJavaMethods.truthPredicate());
+        }
+        /** As {@link #attributeWhenReadyFromMultiple(Iterable, AttributeSensor)} with an explicit readiness test. */
+        @Beta
+        public <T> MultiBuilder<T, T, List<T>> attributeWhenReadyFromMultiple(Iterable<? extends Entity> sources, AttributeSensor<T> sensor, Predicate<? super T> readiness) {
+            return new MultiBuilder<T, T, List<T>>(sources, sensor, readiness);
+        }
+    }
+
+    /**
+     * Builder for producing variants of attributeWhenReady.
+     */
+    public static class Builder<T,V> {
+        protected Entity source;
+        protected AttributeSensor<T> sensor;
+        protected Predicate<? super T> readiness;
+        protected Function<? super T, ? extends V> postProcess;
+        protected List<AttributeAndSensorCondition<?>> abortSensorConditions = Lists.newArrayList();
+        protected String blockingDetails;
+        protected Duration timeout;
+        protected Maybe<V> onTimeout;
+        protected  boolean ignoreUnmanaged = WaitInTaskForAttributeReady.DEFAULT_IGNORE_UNMANAGED;
+        protected Maybe<V> onUnmanaged;
+
+        protected Builder(Entity source, AttributeSensor<T> sensor) {
+            this.source = source;
+            this.sensor = sensor;
+        }
+        
+        /**
+         * Will wait for the attribute on the given entity.
+         * If that entity report {@link Lifecycle#ON_FIRE} for its {@link Attributes#SERVICE_STATE_ACTUAL} then it will abort.
+         * @deprecated since 0.7.0 use {@link DependentConfiguration#builder()} then {@link ProtoBuilder#attributeWhenReady(Entity, AttributeSensor)} then {@link #abortIfOnFire()} 
+         */
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        public <T2> Builder<T2,T2> attributeWhenReady(Entity source, AttributeSensor<T2> sensor) {
+            this.source = checkNotNull(source, "source");
+            this.sensor = (AttributeSensor) checkNotNull(sensor, "sensor");
+            abortIfOnFire();
+            return (Builder<T2, T2>) this;
+        }
+        public Builder<T,V> readiness(Closure<Boolean> val) {
+            this.readiness = GroovyJavaMethods.predicateFromClosure(checkNotNull(val, "val"));
+            return this;
+        }
+        public Builder<T,V> readiness(Predicate<? super T> val) {
+            this.readiness = checkNotNull(val, "ready");
+            return this;
+        }
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        public <V2> Builder<T,V2> postProcess(Closure<V2> val) {
+            this.postProcess = (Function) GroovyJavaMethods.<T,V2>functionFromClosure(checkNotNull(val, "postProcess"));
+            return (Builder<T,V2>) this;
+        }
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        public <V2> Builder<T,V2> postProcess(final Function<? super T, V2>  val) {
+            this.postProcess = (Function) checkNotNull(val, "postProcess");
+            return (Builder<T,V2>) this;
+        }
+        public <T2> Builder<T,V> abortIf(Entity source, AttributeSensor<T2> sensor) {
+            return abortIf(source, sensor, GroovyJavaMethods.truthPredicate());
+        }
+        public <T2> Builder<T,V> abortIf(Entity source, AttributeSensor<T2> sensor, Predicate<? super T2> predicate) {
+            abortSensorConditions.add(new AttributeAndSensorCondition<T2>(source, sensor, predicate));
+            return this;
+        }
+        public Builder<T,V> abortIfOnFire() {
+            abortIf(source, Attributes.SERVICE_STATE_ACTUAL, Predicates.equalTo(Lifecycle.ON_FIRE));
+            return this;
+        }
+        public Builder<T,V> blockingDetails(String val) {
+            blockingDetails = val;
+            return this;
+        }
+        /** specifies an optional timeout; by default it waits forever, or until unmanaged or other abort condition */
+        public Builder<T,V> timeout(Duration val) {
+            timeout = val;
+            return this;
+        }
+        public Builder<T,V> onTimeoutReturn(V val) {
+            onTimeout = Maybe.of(val);
+            return this;
+        }
+        public Builder<T,V> onTimeoutThrow() {
+            onTimeout = Maybe.<V>absent();
+            return this;
+        }
+        public Builder<T,V> onUnmanagedReturn(V val) {
+            onUnmanaged = Maybe.of(val);
+            return this;
+        }
+        public Builder<T,V> onUnmanagedThrow() {
+            onUnmanaged = Maybe.<V>absent();
+            return this;
+        }
+        /** @since 0.7.0 included in case old behaviour of not checking whether the entity is managed is required
+         * (I can't see why it is; polling will likely give errors, once it is unmanaged this will never completed,
+         * and before management the current code will continue, so long as there are no other errors) */ @Deprecated
+        public Builder<T,V> onUnmanagedContinue() {
+            ignoreUnmanaged = true;
+            return this;
+        }
+        /** take advantage of the fact that this builder can build multiple times, allowing subclasses 
+         * to change the source along the way */
+        protected Builder<T,V> source(Entity source) {
+            this.source = source;
+            return this;
+        }
+        /** as {@link #source(Entity)} */
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        protected Builder<T,V> sensor(AttributeSensor<? extends T> sensor) {
+            this.sensor = (AttributeSensor) sensor;
+            return this;
+        }
+        public Task<V> build() {
+            validate();
+            
+            return Tasks.<V>builder().dynamic(false)
+                .name("waiting on "+sensor.getName())
+                .description("Waiting on sensor "+sensor.getName()+" from "+source)
+                .tag("attributeWhenReady")
+                .body(new WaitInTaskForAttributeReady<T,V>(this))
+                .build();
+        }
+        
+        public V runNow() {
+            validate();
+            return new WaitInTaskForAttributeReady<T,V>(this).call();
+        }
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        private void validate() {
+            checkNotNull(source, "Entity source");
+            checkNotNull(sensor, "Sensor");
+            if (readiness == null) readiness = GroovyJavaMethods.truthPredicate();
+            if (postProcess == null) postProcess = (Function) Functions.identity();
+        }
+    }
+
+    /**
+     * Builder for producing variants of attributeWhenReady.
+     */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Beta
+    public static class MultiBuilder<T, V, V2> {
+        protected final String name;
+        protected final String descriptionBase;
+        protected final Builder<T,V> builder;
+        // if desired, the use of this multiSource could allow different conditions; 
+        // but probably an easier API just for the caller to build the parallel task  
+        protected final List<AttributeAndSensorCondition<?>> multiSource = Lists.newArrayList();
+        protected Function<? super List<V>, V2> postProcessFromMultiple;
+        
+        /** returns a task for parallel execution returning a list of values of the given sensor list on the given entity, 
+         * optionally when the values satisfy a given readiness predicate (defaulting to groovy truth if not supplied) */ 
+        @Beta
+        protected MultiBuilder(Iterable<? extends Entity> sources, AttributeSensor<T> sensor) {
+            this(sources, sensor, GroovyJavaMethods.truthPredicate());
+        }
+        @Beta
+        protected MultiBuilder(Iterable<? extends Entity> sources, AttributeSensor<T> sensor, Predicate<? super T> readiness) {
+            builder = new Builder<T,V>(null, sensor);
+            builder.readiness(readiness);
+            
+            for (Entity s : checkNotNull(sources, "sources")) {
+                multiSource.add(new AttributeAndSensorCondition<T>(s, sensor, readiness));
+            }
+            this.name = "waiting on "+sensor.getName();
+            this.descriptionBase = "waiting on "+sensor.getName()+" "+readiness
+                +" from "+Iterables.size(sources)+" entit"+Strings.ies(sources);
+        }
+        
+        /** Apply post-processing to the entire list of results */
+        public <V2b> MultiBuilder<T, V, V2b> postProcessFromMultiple(final Function<? super List<V>, V2b> val) {
+            this.postProcessFromMultiple = (Function) checkNotNull(val, "postProcessFromMulitple");
+            return (MultiBuilder<T,V, V2b>) this;
+        }
+        /** Apply post-processing to the entire list of results 
+         * See {@link CollectionFunctionals#all(Predicate)} and {@link CollectionFunctionals#quorum(org.apache.brooklyn.util.collections.QuorumCheck, Predicate)
+         * which allow useful arguments. */
+        public MultiBuilder<T, V, Boolean> postProcessFromMultiple(final Predicate<? super List<V>> val) {
+            return postProcessFromMultiple(Functions.forPredicate(val));
+        }
+        
+        public <V1> MultiBuilder<T, V1, V2> postProcess(Closure<V1> val) {
+            builder.postProcess(val);
+            return (MultiBuilder<T, V1, V2>) this;
+        }
+        public <V1> MultiBuilder<T, V1, V2> postProcess(final Function<? super T, V1>  val) {
+            builder.postProcess(val);
+            return (MultiBuilder<T, V1, V2>) this;
+        }
+        public <T2> MultiBuilder<T, V, V2> abortIf(Entity source, AttributeSensor<T2> sensor) {
+            builder.abortIf(source, sensor);
+            return this;
+        }
+        public <T2> MultiBuilder<T, V, V2> abortIf(Entity source, AttributeSensor<T2> sensor, Predicate<? super T2> predicate) {
+            builder.abortIf(source, sensor, predicate);
+            return this;
+        }
+        public MultiBuilder<T, V, V2> abortIfOnFire() {
+            builder.abortIfOnFire();
+            return this;
+        }
+        public MultiBuilder<T, V, V2> blockingDetails(String val) {
+            builder.blockingDetails(val);
+            return this;
+        }
+        public MultiBuilder<T, V, V2> timeout(Duration val) {
+            builder.timeout(val);
+            return this;
+        }
+        public MultiBuilder<T, V, V2> onTimeoutReturn(V val) {
+            builder.onTimeoutReturn(val);
+            return this;
+        }
+        public MultiBuilder<T, V, V2> onTimeoutThrow() {
+            builder.onTimeoutThrow();
+            return this;
+        }
+        public MultiBuilder<T, V, V2> onUnmanagedReturn(V val) {
+            builder.onUnmanagedReturn(val);
+            return this;
+        }
+        public MultiBuilder<T, V, V2> onUnmanagedThrow() {
+            builder.onUnmanagedThrow();
+            return this;
+        }
+        
+        public Task<V2> build() {
+            List<Task<V>> tasks = MutableList.of();
+            for (AttributeAndSensorCondition<?> source: multiSource) {
+                builder.source(source.source);
+                builder.sensor((AttributeSensor)source.sensor);
+                builder.readiness((Predicate)source.predicate);
+                tasks.add(builder.build());
+            }
+            final Task<List<V>> parallelTask = Tasks.<List<V>>builder().parallel(true).addAll(tasks)
+                .name(name)
+                .description(descriptionBase+
+                    (builder.timeout!=null ? ", timeout "+builder.timeout : ""))
+                .build();
+            
+            if (postProcessFromMultiple == null) {
+                // V2 should be the right type in normal operations
+                return (Task<V2>) parallelTask;
+            } else {
+                return Tasks.<V2>builder().name(name).description(descriptionBase)
+                    .tag("attributeWhenReady")
+                    .body(new Callable<V2>() {
+                        @Override public V2 call() throws Exception {
+                            List<V> prePostProgress = DynamicTasks.queue(parallelTask).get();
+                            return DynamicTasks.queue(
+                                Tasks.<V2>builder().name("post-processing").description("Applying "+postProcessFromMultiple)
+                                    .body(Functionals.callable(postProcessFromMultiple, prePostProgress))
+                                    .build()).get();
+                        }
+                    })
+                    .build();
+            }
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/core/sensor/HttpRequestSensor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/HttpRequestSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/HttpRequestSensor.java
new file mode 100644
index 0000000..79660ce
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/HttpRequestSensor.java
@@ -0,0 +1,96 @@
+/*
+ * 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.sensor;
+
+import java.net.URI;
+
+import net.minidev.json.JSONObject;
+
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.AddSensor;
+import org.apache.brooklyn.sensor.feed.http.HttpFeed;
+import org.apache.brooklyn.sensor.feed.http.HttpPollConfig;
+import org.apache.brooklyn.sensor.feed.http.HttpValueFunctions;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Functions;
+import com.google.common.base.Supplier;
+
+/**
+ * Configurable {@link org.apache.brooklyn.api.entity.EntityInitializer} which adds an HTTP sensor feed to retrieve the
+ * {@link JSONObject} from a JSON response in order to populate the sensor with the data at the {@code jsonPath}.
+ *
+ * @see SshCommandSensor
+ * @see JmxAttributeSensor
+ */
+@Beta
+public final class HttpRequestSensor<T> extends AddSensor<T> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(HttpRequestSensor.class);
+
+    public static final ConfigKey<String> SENSOR_URI = ConfigKeys.newStringConfigKey("uri", "HTTP URI to poll for JSON");
+    public static final ConfigKey<String> JSON_PATH = ConfigKeys.newStringConfigKey("jsonPath", "JSON path to select in HTTP response; default $", "$");
+    public static final ConfigKey<String> USERNAME = ConfigKeys.newStringConfigKey("username", "Username for HTTP request, if required");
+    public static final ConfigKey<String> PASSWORD = ConfigKeys.newStringConfigKey("password", "Password for HTTP request, if required");
+
+    protected final Supplier<URI> uri;
+    protected final String jsonPath;
+    protected final String username;
+    protected final String password;
+
+    public HttpRequestSensor(final ConfigBag params) {
+        super(params);
+
+        uri = new Supplier<URI>() {
+            @Override
+            public URI get() {
+                return URI.create(params.get(SENSOR_URI));
+            }
+        };
+        jsonPath = params.get(JSON_PATH);
+        username = params.get(USERNAME);
+        password = params.get(PASSWORD);
+    }
+
+    @Override
+    public void apply(final EntityLocal entity) {
+        super.apply(entity);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Adding HTTP JSON sensor {} to {}", name, entity);
+        }
+
+        HttpPollConfig<T> pollConfig = new HttpPollConfig<T>(sensor)
+                .checkSuccess(HttpValueFunctions.responseCodeEquals(200))
+                .onFailureOrException(Functions.constant((T) null))
+                .onSuccess(HttpValueFunctions.<T>jsonContentsFromPath(jsonPath))
+                .period(period);
+
+        HttpFeed.builder().entity(entity)
+                .baseUri(uri)
+                .credentialsIfNotNull(username, password)
+                .poll(pollConfig)
+                .build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/core/sensor/PortAttributeSensorAndConfigKey.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/PortAttributeSensorAndConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/sensor/PortAttributeSensorAndConfigKey.java
new file mode 100644
index 0000000..3de0de6
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/PortAttributeSensorAndConfigKey.java
@@ -0,0 +1,141 @@
+/*
+ * 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.sensor;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.Location;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.location.PortRange;
+import org.apache.brooklyn.api.location.PortSupplier;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.api.sensor.Sensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.internal.BrooklynInitialization;
+import org.apache.brooklyn.core.location.Locations;
+import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+/**
+ * A {@link Sensor} describing a port on a system,
+ * with a {@link ConfigKey} which can be configured with a port range
+ * (either a number e.g. 80, or a string e.g. "80" or "8080-8089" or even "80, 8080-8089, 8800+", or a list of these).
+ * <p>
+ * To convert at runtime a single port is chosen, respecting the entity.
+ */
+public class PortAttributeSensorAndConfigKey extends AttributeSensorAndConfigKey<PortRange,Integer> {
+
+    private static final long serialVersionUID = 4680651022807491321L;
+
+    public static final Logger LOG = LoggerFactory.getLogger(PortAttributeSensorAndConfigKey.class);
+
+    static { BrooklynInitialization.initAll(); }
+
+    public PortAttributeSensorAndConfigKey(String name) {
+        this(name, name, null);
+    }
+    public PortAttributeSensorAndConfigKey(String name, String description) {
+        this(name, description, null);
+    }
+    public PortAttributeSensorAndConfigKey(String name, String description, Object defaultValue) {
+        super(PortRange.class, Integer.class, name, description, defaultValue);
+    }
+    public PortAttributeSensorAndConfigKey(PortAttributeSensorAndConfigKey orig, Object defaultValue) {
+        super(orig, TypeCoercions.coerce(defaultValue, PortRange.class));
+    }
+    @Override
+    protected Integer convertConfigToSensor(PortRange value, Entity entity) {
+        if (value==null) return null;
+        Collection<? extends Location> locations = entity.getLocations();
+        if (!locations.isEmpty()) {
+            Maybe<? extends Location> lo = Locations.findUniqueMachineLocation(locations);
+            if (!lo.isPresent()) {
+                // Try a unique location which isn't a machine provisioner
+                Iterator<? extends Location> li = Iterables.filter(locations,
+                        Predicates.not(Predicates.instanceOf(MachineProvisioningLocation.class))).iterator();
+                if (li.hasNext()) lo = Maybe.of(li.next());
+                if (li.hasNext()) lo = Maybe.absent();
+            }
+            // Fall back to selecting the single location
+            if (!lo.isPresent() && locations.size() == 1) {
+                lo = Maybe.of(locations.iterator().next());
+            }
+            if (LOG.isTraceEnabled()) {
+                LOG.trace("Convert config to sensor for {} found locations: {}. Selected: {}", new Object[] {entity, locations, lo});
+            }
+            if (lo.isPresent()) {
+                Location l = lo.get();
+                Optional<Boolean> locationRunning = Optional.fromNullable(l.getConfig(BrooklynConfigKeys.SKIP_ENTITY_START_IF_RUNNING));
+                Optional<Boolean> entityRunning = Optional.fromNullable(entity.getConfig(BrooklynConfigKeys.SKIP_ENTITY_START_IF_RUNNING));
+                Optional<Boolean> locationInstalled = Optional.fromNullable(l.getConfig(BrooklynConfigKeys.SKIP_ENTITY_INSTALLATION));
+                Optional<Boolean> entityInstalled = Optional.fromNullable(entity.getConfig(BrooklynConfigKeys.SKIP_ENTITY_INSTALLATION));
+                Optional<Boolean> entityStarted = Optional.fromNullable(entity.getConfig(BrooklynConfigKeys.SKIP_ENTITY_START));
+                boolean skipCheck = locationRunning.or(entityRunning).or(locationInstalled).or(entityInstalled).or(entityStarted).or(false);
+                if (l instanceof PortSupplier) {
+                    int p = ((PortSupplier) l).obtainPort(value);
+                    if (p != -1) {
+                        LOG.debug("{}: choosing port {} for {}", new Object[] { entity, p, getName() });
+                        return p;
+                    }
+                    // If we are not skipping install or already started, fail now
+                    if (!skipCheck) {
+                        int rangeSize = Iterables.size(value);
+                        if (rangeSize == 0) {
+                            LOG.warn("{}: no port available for {} (empty range {})", new Object[] { entity, getName(), value });
+                        } else if (rangeSize == 1) {
+                            Integer pp = value.iterator().next();
+                            if (pp > 1024) {
+                                LOG.warn("{}: port {} not available for {}", new Object[] { entity, pp, getName() });
+                            } else {
+                                LOG.warn("{}: port {} not available for {} (root may be required?)", new Object[] { entity, pp, getName() });
+                            }
+                        } else {
+                            LOG.warn("{}: no port available for {} (tried range {})", new Object[] { entity, getName(), value });
+                        }
+                        return null; // Definitively, no ports available
+                    }
+                }
+                // Ports may be available, we just can't tell from the location
+                Integer v = (value.isEmpty() ? null : value.iterator().next());
+                LOG.debug("{}: choosing port {} (unconfirmed) for {}", new Object[] { entity, v, getName() });
+                return v;
+            } else {
+                LOG.warn("{}: ports not applicable, or not yet applicable, because has multiple locations {}; ignoring ", new Object[] { entity, locations, getName() });
+            }
+        } else {
+            LOG.warn("{}: ports not applicable, or not yet applicable, bacause has no locations; ignoring {}", entity, getName());
+        }
+        return null;
+    }
+
+    @Override
+    protected Integer convertConfigToSensor(PortRange value, ManagementContext managementContext) {
+        LOG.warn("ports not applicable, because given managementContext rather than entity; ignoring {}", getName());
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/core/sensor/Sensors.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/Sensors.java b/core/src/main/java/org/apache/brooklyn/core/sensor/Sensors.java
new file mode 100644
index 0000000..05227d0
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/Sensors.java
@@ -0,0 +1,164 @@
+/*
+ * 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.sensor;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URL;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.AttributeSensor.SensorPersistenceMode;
+import org.apache.brooklyn.core.config.render.RendererHints;
+import org.apache.brooklyn.util.net.UserAndHostAndPort;
+import org.apache.brooklyn.util.text.StringFunctions;
+import org.apache.brooklyn.util.time.Duration;
+import org.apache.brooklyn.util.time.Time;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.net.HostAndPort;
+import com.google.common.reflect.TypeToken;
+
+public class Sensors {
+
+    @Beta
+    public static <T> Builder<T> builder(TypeToken<T> type, String name) {
+        return new Builder<T>().type(type).name(name);
+    }
+
+    @Beta
+    public static <T> Builder<T> builder(Class<T> type, String name) {
+        return new Builder<T>().type(type).name(name);
+    }
+    
+    @Beta
+    public static class Builder<T> {
+        private String name;
+        private TypeToken<T> type;
+        private String description;
+        private SensorPersistenceMode persistence;
+        
+        protected Builder() { // use builder(type, name) instead
+        }
+        public Builder<T> name(String val) {
+            this.name = checkNotNull(val, "name"); return this;
+        }
+        public Builder<T> type(Class<T> val) {
+            return type(TypeToken.of(val));
+        }
+        public Builder<T> type(TypeToken<T> val) {
+            this.type = checkNotNull(val, "type"); return this;
+        }
+        public Builder<T> description(String val) {
+            this.description = val; return this;
+        }
+        public Builder<T> persistence(SensorPersistenceMode val) {
+            this.persistence = val; return this;
+        }
+        public AttributeSensor<T> build() {
+            return new BasicAttributeSensor<T>(type, name, description, persistence);
+        }
+    }
+
+    public static <T> AttributeSensor<T> newSensor(Class<T> type, String name) {
+        return new BasicAttributeSensor<T>(type, name);
+    }
+
+    public static <T> AttributeSensor<T> newSensor(Class<T> type, String name, String description) {
+        return new BasicAttributeSensor<T>(type, name, description);
+    }
+
+    public static <T> AttributeSensor<T> newSensor(TypeToken<T> type, String name, String description) {
+        return new BasicAttributeSensor<T>(type, name, description);
+    }
+
+    public static AttributeSensor<String> newStringSensor(String name) {
+        return newSensor(String.class, name);
+    }
+
+    public static AttributeSensor<String> newStringSensor(String name, String description) {
+        return newSensor(String.class, name, description);
+    }
+
+    public static AttributeSensor<Integer> newIntegerSensor(String name) {
+        return newSensor(Integer.class, name);
+    }
+
+    public static AttributeSensor<Integer> newIntegerSensor(String name, String description) {
+        return newSensor(Integer.class, name, description);
+    }
+
+    public static AttributeSensor<Long> newLongSensor(String name) {
+        return newSensor(Long.class, name);
+    }
+
+    public static AttributeSensor<Long> newLongSensor(String name, String description) {
+        return newSensor(Long.class, name, description);
+    }
+
+    public static AttributeSensor<Double> newDoubleSensor(String name) {
+        return newSensor(Double.class, name);
+    }
+
+    public static AttributeSensor<Double> newDoubleSensor(String name, String description) {
+        return newSensor(Double.class, name, description);
+    }
+
+    public static AttributeSensor<Boolean> newBooleanSensor(String name) {
+        return newSensor(Boolean.class, name);
+    }
+
+    public static AttributeSensor<Boolean> newBooleanSensor(String name, String description) {
+        return newSensor(Boolean.class, name, description);
+    }
+
+    // Extensions to sensors
+
+    public static <T> AttributeSensor<T> newSensorRenamed(String newName, AttributeSensor<T> sensor) {
+        return new BasicAttributeSensor<T>(sensor.getTypeToken(), newName, sensor.getDescription());
+    }
+
+    public static <T> AttributeSensor<T> newSensorWithPrefix(String prefix, AttributeSensor<T> sensor) {
+        return newSensorRenamed(prefix+sensor.getName(), sensor);
+    }
+
+    // Display hints for common utility objects
+
+    static {
+        RendererHints.register(Duration.class, RendererHints.displayValue(Time.fromDurationToTimeStringRounded()));
+        RendererHints.register(HostAndPort.class, RendererHints.displayValue(StringFunctions.toStringFunction()));
+        RendererHints.register(UserAndHostAndPort.class, RendererHints.displayValue(StringFunctions.toStringFunction()));
+        RendererHints.register(InetAddress.class, RendererHints.displayValue(new Function<InetAddress,String>() {
+            @Override
+            public String apply(@Nullable InetAddress input) {
+                return input == null ? null : input.getHostAddress();
+            }
+        }));
+
+        RendererHints.register(URL.class, RendererHints.displayValue(StringFunctions.toStringFunction()));
+        RendererHints.register(URL.class, RendererHints.openWithUrl(StringFunctions.toStringFunction()));
+        RendererHints.register(URI.class, RendererHints.displayValue(StringFunctions.toStringFunction()));
+        RendererHints.register(URI.class, RendererHints.openWithUrl(StringFunctions.toStringFunction()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java b/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java
new file mode 100644
index 0000000..4a7b1d4
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/StaticSensor.java
@@ -0,0 +1,72 @@
+/*
+ * 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.sensor;
+
+import org.apache.brooklyn.api.entity.EntityLocal;
+import org.apache.brooklyn.api.mgmt.Task;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.effector.AddSensor;
+import org.apache.brooklyn.sensor.enricher.Propagator;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.task.Tasks;
+import org.apache.brooklyn.util.core.task.ValueResolver;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Supplier;
+
+/** 
+ * Provides an initializer/feed which simply sets a given value.
+ * <p>
+ * {@link Task}/{@link Supplier} values are resolved when written,
+ * unlike config values which are resolved on each read.
+ * <p>
+ * This supports a {@link StaticSensor#SENSOR_PERIOD} 
+ * which can be useful if the supplied value is such a function.
+ * However when the source is another sensor,
+ * consider using {@link Propagator} which listens for changes instead. */
+public class StaticSensor<T> extends AddSensor<T> {
+
+    private static final Logger log = LoggerFactory.getLogger(StaticSensor.class);
+    
+    public static final ConfigKey<Object> STATIC_VALUE = ConfigKeys.newConfigKey(Object.class, "static.value");
+
+    private final Object value;
+
+    public StaticSensor(ConfigBag params) {
+        super(params);
+        value = params.get(STATIC_VALUE);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void apply(EntityLocal entity) {
+        super.apply(entity);
+        
+        Maybe<T> v = Tasks.resolving(value).as((Class<T>)sensor.getType()).timeout(ValueResolver.PRETTY_QUICK_WAIT).getMaybe();
+        if (v.isPresent()) {
+            log.debug(this+" setting sensor "+sensor+" to "+v.get());
+            entity.setAttribute(sensor, v.get());
+        } else {
+            log.debug(this+" not setting sensor "+sensor+"; cannot resolve "+value);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/core/sensor/TemplatedStringAttributeSensorAndConfigKey.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/sensor/TemplatedStringAttributeSensorAndConfigKey.java b/core/src/main/java/org/apache/brooklyn/core/sensor/TemplatedStringAttributeSensorAndConfigKey.java
new file mode 100644
index 0000000..953b7d8
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/sensor/TemplatedStringAttributeSensorAndConfigKey.java
@@ -0,0 +1,66 @@
+/*
+ * 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.sensor;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
+import org.apache.brooklyn.util.core.text.TemplateProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * A {@link ConfigKey} which takes a freemarker-templated string,
+ * and whose value is converted to a sensor by processing the template 
+ * with access to config and methods on the entity where it is set.
+ */
+public class TemplatedStringAttributeSensorAndConfigKey extends BasicAttributeSensorAndConfigKey<String> {
+    private static final long serialVersionUID = 4680651022807491321L;
+    
+    public static final Logger LOG = LoggerFactory.getLogger(TemplatedStringAttributeSensorAndConfigKey.class);
+
+    public TemplatedStringAttributeSensorAndConfigKey(String name) {
+        this(name, name, null);
+    }
+    public TemplatedStringAttributeSensorAndConfigKey(String name, String description) {
+        this(name, description, null);
+    }
+    public TemplatedStringAttributeSensorAndConfigKey(String name, String description, String defaultValue) {
+        super(String.class, name, description, defaultValue);
+    }
+    public TemplatedStringAttributeSensorAndConfigKey(TemplatedStringAttributeSensorAndConfigKey orig, String defaultValue) {
+        super(orig, defaultValue);
+    }
+    
+    @Override
+    protected String convertConfigToSensor(String value, Entity entity) {
+        if (value == null) return null;
+        return TemplateProcessor.processTemplateContents(value, (EntityInternal)entity, ImmutableMap.<String,Object>of());
+    }
+    
+    @Override
+    protected String convertConfigToSensor(String value, ManagementContext managementContext) {
+        if (value == null) return null;
+        return TemplateProcessor.processTemplateContents(value, (ManagementContextInternal)managementContext, ImmutableMap.<String,Object>of());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/core/server/entity/BrooklynMetrics.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/server/entity/BrooklynMetrics.java b/core/src/main/java/org/apache/brooklyn/core/server/entity/BrooklynMetrics.java
index 5f060c2..6e9cef5 100644
--- a/core/src/main/java/org/apache/brooklyn/core/server/entity/BrooklynMetrics.java
+++ b/core/src/main/java/org/apache/brooklyn/core/server/entity/BrooklynMetrics.java
@@ -21,8 +21,8 @@ package org.apache.brooklyn.core.server.entity;
 import org.apache.brooklyn.api.entity.Entity;
 import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
-import org.apache.brooklyn.sensor.core.BasicAttributeSensor;
-import org.apache.brooklyn.sensor.core.BasicAttributeSensorAndConfigKey;
+import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
+import org.apache.brooklyn.core.sensor.BasicAttributeSensorAndConfigKey;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 
 @ImplementedBy(BrooklynMetricsImpl.class)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroup.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroup.java b/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroup.java
index 00710ca..aa9ca90 100644
--- a/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroup.java
+++ b/core/src/main/java/org/apache/brooklyn/entity/group/AbstractGroup.java
@@ -27,7 +27,7 @@ import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ComputeServiceIndicatorsFromChildrenAndMembers;
 import org.apache.brooklyn.core.entity.trait.Changeable;
-import org.apache.brooklyn.sensor.core.Sensors;
+import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.collections.QuorumCheck;
 import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java
index bf39663..b6ee23a 100644
--- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java
+++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java
@@ -38,11 +38,11 @@ import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.factory.EntityFactory;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.entity.trait.MemberReplaceable;
+import org.apache.brooklyn.core.sensor.BasicAttributeSensor;
+import org.apache.brooklyn.core.sensor.BasicNotificationSensor;
+import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.entity.group.zoneaware.BalancingNodePlacementStrategy;
 import org.apache.brooklyn.entity.group.zoneaware.ProportionalZoneFailureDetector;
-import org.apache.brooklyn.sensor.core.BasicAttributeSensor;
-import org.apache.brooklyn.sensor.core.BasicNotificationSensor;
-import org.apache.brooklyn.sensor.core.Sensors;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 import org.apache.brooklyn.util.time.Duration;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabric.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabric.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabric.java
index fc53c21..943ed9b 100644
--- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabric.java
+++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabric.java
@@ -28,7 +28,7 @@ import org.apache.brooklyn.core.entity.Attributes;
 import org.apache.brooklyn.core.entity.factory.EntityFactory;
 import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
 import org.apache.brooklyn.core.entity.trait.Startable;
-import org.apache.brooklyn.sensor.core.Sensors;
+import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 
 import com.google.common.collect.ImmutableMap;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/entity/group/DynamicGroup.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicGroup.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicGroup.java
index bb43d3b..c06aa58 100644
--- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicGroup.java
+++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicGroup.java
@@ -30,7 +30,7 @@ import org.apache.brooklyn.core.annotation.Effector;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.effector.MethodEffector;
 import org.apache.brooklyn.core.entity.trait.Startable;
-import org.apache.brooklyn.sensor.core.Sensors;
+import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 
 import com.google.common.base.Predicate;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/entity/group/DynamicMultiGroup.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicMultiGroup.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicMultiGroup.java
index 5e9b75f..048a304 100644
--- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicMultiGroup.java
+++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicMultiGroup.java
@@ -27,7 +27,7 @@ import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.core.config.ConfigKeys;
-import org.apache.brooklyn.sensor.core.Sensors;
+import org.apache.brooklyn.core.sensor.Sensors;
 import org.apache.brooklyn.util.core.flags.SetFromFlag;
 
 import com.google.common.annotations.Beta;

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/entity/stock/DelegateEntity.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/entity/stock/DelegateEntity.java b/core/src/main/java/org/apache/brooklyn/entity/stock/DelegateEntity.java
index 645426a..793bac6 100644
--- a/core/src/main/java/org/apache/brooklyn/entity/stock/DelegateEntity.java
+++ b/core/src/main/java/org/apache/brooklyn/entity/stock/DelegateEntity.java
@@ -26,8 +26,8 @@ import org.apache.brooklyn.api.entity.ImplementedBy;
 import org.apache.brooklyn.api.sensor.AttributeSensor;
 import org.apache.brooklyn.core.config.ConfigKeys;
 import org.apache.brooklyn.core.config.render.RendererHints;
-import org.apache.brooklyn.sensor.core.AttributeSensorAndConfigKey;
-import org.apache.brooklyn.sensor.core.Sensors;
+import org.apache.brooklyn.core.sensor.AttributeSensorAndConfigKey;
+import org.apache.brooklyn.core.sensor.Sensors;
 
 import com.google.common.base.Function;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicy.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicy.java b/core/src/main/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicy.java
index 0d5828d..0945407 100644
--- a/core/src/main/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicy.java
+++ b/core/src/main/java/org/apache/brooklyn/location/winrm/AdvertiseWinrmLoginPolicy.java
@@ -26,9 +26,9 @@ import org.apache.brooklyn.api.sensor.SensorEvent;
 import org.apache.brooklyn.api.sensor.SensorEventListener;
 import org.apache.brooklyn.core.entity.AbstractEntity;
 import org.apache.brooklyn.core.policy.AbstractPolicy;
+import org.apache.brooklyn.core.sensor.Sensors;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.apache.brooklyn.sensor.core.Sensors;
 
 import com.google.common.annotations.Beta;
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2a78e273/core/src/main/java/org/apache/brooklyn/sensor/core/AttributeMap.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/sensor/core/AttributeMap.java b/core/src/main/java/org/apache/brooklyn/sensor/core/AttributeMap.java
deleted file mode 100644
index 4125a96..0000000
--- a/core/src/main/java/org/apache/brooklyn/sensor/core/AttributeMap.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.sensor.core;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Map;
-
-import org.apache.brooklyn.api.entity.Entity;
-import org.apache.brooklyn.api.sensor.AttributeSensor;
-import org.apache.brooklyn.core.BrooklynLogging;
-import org.apache.brooklyn.core.entity.AbstractEntity;
-import org.apache.brooklyn.util.core.flags.TypeCoercions;
-import org.apache.brooklyn.util.guava.Maybe;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Objects;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-
-/**
- * A {@link Map} of {@link Entity} attribute values.
- */
-public final class AttributeMap implements Serializable {
-
-    private static final long serialVersionUID = -6834883734250888344L;
-
-    static final Logger log = LoggerFactory.getLogger(AttributeMap.class);
-
-    private static enum Marker {
-        NULL;
-    }
-    
-    private final AbstractEntity entity;
-
-    // Assumed to be something like a ConcurrentMap passed in.
-    private final Map<Collection<String>, Object> values;
-
-    /**
-     * Creates a new AttributeMap.
-     *
-     * @param entity the EntityLocal this AttributeMap belongs to.
-     * @throws IllegalArgumentException if entity is null
-     */
-    public AttributeMap(AbstractEntity entity, Map<Collection<String>, Object> storage) {
-        this.entity = checkNotNull(entity, "entity must be specified");
-        this.values = checkNotNull(storage, "storage map must not be null");
-    }
-
-    public Map<Collection<String>, Object> asRawMap() {
-        return ImmutableMap.copyOf(values);
-    }
-
-    public Map<String, Object> asMap() {
-        Map<String, Object> result = Maps.newLinkedHashMap();
-        for (Map.Entry<Collection<String>, Object> entry : values.entrySet()) {
-            String sensorName = Joiner.on('.').join(entry.getKey());
-            Object val = (isNull(entry.getValue())) ? null : entry.getValue();
-            result.put(sensorName, val);
-        }
-        return result;
-    }
-    
-    /**
-     * Updates the value.
-     *
-     * @param path the path to the value.
-     * @param newValue the new value
-     * @return the old value.
-     * @throws IllegalArgumentException if path is null or empty
-     */
-    // TODO path must be ordered(and legal to contain duplicates like "a.b.a"; list would be better
-    public <T> T update(Collection<String> path, T newValue) {
-        checkPath(path);
-
-        if (newValue == null) {
-            newValue = typedNull();
-        }
-
-        if (log.isTraceEnabled()) {
-            log.trace("setting sensor {}={} for {}", new Object[] {path, newValue, entity});
-        }
-
-        @SuppressWarnings("unchecked")
-        T oldValue = (T) values.put(path, newValue);
-        return (isNull(oldValue)) ? null : oldValue;
-    }
-
-    private void checkPath(Collection<String> path) {
-        Preconditions.checkNotNull(path, "path can't be null");
-        Preconditions.checkArgument(!path.isEmpty(), "path can't be empty");
-    }
-
-    public <T> T update(AttributeSensor<T> attribute, T newValue) {
-        T oldValue = updateWithoutPublishing(attribute, newValue);
-        entity.emitInternal(attribute, newValue);
-        return oldValue;
-    }
-    
-    public <T> T updateWithoutPublishing(AttributeSensor<T> attribute, T newValue) {
-        if (log.isTraceEnabled()) {
-            Object oldValue = getValue(attribute);
-            if (!Objects.equal(oldValue, newValue != null)) {
-                log.trace("setting attribute {} to {} (was {}) on {}", new Object[] {attribute.getName(), newValue, oldValue, entity});
-            } else {
-                log.trace("setting attribute {} to {} (unchanged) on {}", new Object[] {attribute.getName(), newValue, this});
-            }
-        }
-
-        T oldValue = (T) update(attribute.getNameParts(), newValue);
-        
-        return (isNull(oldValue)) ? null : oldValue;
-    }
-
-    /**
-     * Where atomicity is desired, the methods in this class synchronize on the {@link #values} map.
-     */
-    public <T> T modify(AttributeSensor<T> attribute, Function<? super T, Maybe<T>> modifier) {
-        synchronized (values) {
-            T oldValue = getValue(attribute);
-            Maybe<? extends T> newValue = modifier.apply(oldValue);
-
-            if (newValue.isPresent()) {
-                if (log.isTraceEnabled()) log.trace("modified attribute {} to {} (was {}) on {}", new Object[] {attribute.getName(), newValue, oldValue, entity});
-                return update(attribute, newValue.get());
-            } else {
-                if (log.isTraceEnabled()) log.trace("modified attribute {} unchanged; not emitting on {}", new Object[] {attribute.getName(), newValue, this});
-                return oldValue;
-            }
-        }
-    }
-
-    public void remove(AttributeSensor<?> attribute) {
-        BrooklynLogging.log(log, BrooklynLogging.levelDebugOrTraceIfReadOnly(entity),
-            "removing attribute {} on {}", attribute.getName(), entity);
-
-        remove(attribute.getNameParts());
-    }
-
-    // TODO path must be ordered(and legal to contain duplicates like "a.b.a"; list would be better
-    public void remove(Collection<String> path) {
-        checkPath(path);
-
-        if (log.isTraceEnabled()) {
-            log.trace("removing sensor {} for {}", new Object[] {path, entity});
-        }
-
-        values.remove(path);
-    }
-
-    /**
-     * Gets the value
-     *
-     * @param path the path of the value to get
-     * @return the value
-     * @throws IllegalArgumentException path is null or empty.
-     */
-    public Object getValue(Collection<String> path) {
-        // TODO previously this would return a map of the sub-tree if the path matched a prefix of a group of sensors, 
-        // or the leaf value if only one value. Arguably that is not required - what is/was the use-case?
-        // 
-        checkPath(path);
-        Object result = values.get(path);
-        return (isNull(result)) ? null : result;
-    }
-
-    @SuppressWarnings("unchecked")
-    public <T> T getValue(AttributeSensor<T> sensor) {
-        return (T) TypeCoercions.coerce(getValue(sensor.getNameParts()), sensor.getType());
-    }
-
-    @SuppressWarnings("unchecked")
-    private <T> T typedNull() {
-        return (T) Marker.NULL;
-    }
-    
-    private boolean isNull(Object t) {
-        return t == Marker.NULL;
-    }
-}


Mime
View raw message