expands power of Tasks.resolveValue with new supporting class ValueResolver, 
supporting timeouts on resolving value and forcing submission of execution to a 
task context where needed;
means that config arguments can be resolved in more places, such as effector 
default values from the GUI


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

Branch: refs/heads/master
Commit: 05a39488dca27887f563c20ad1110ed3916c25f5
Parents: 4734ad8
Author: Alex Heneveld <[email protected]>
Authored: Fri Aug 29 01:32:52 2014 -0400
Committer: Alex Heneveld <[email protected]>
Committed: Mon Sep 1 17:07:00 2014 +0100

----------------------------------------------------------------------
 .../java/brooklyn/util/flags/TypeCoercions.java |  11 +-
 .../src/main/java/brooklyn/util/task/Tasks.java | 124 ++------
 .../java/brooklyn/util/task/ValueResolver.java  | 284 +++++++++++++++++++
 .../test/java/brooklyn/util/task/TasksTest.java |   1 +
 .../brooklyn/util/task/ValueResolverTest.java   |  75 +++++
 .../BrooklynPropertiesSecurityFilter.java       |   9 +-
 .../rest/transform/EffectorTransformer.java     |  28 +-
 .../main/java/brooklyn/util/time/Durations.java |  26 ++
 8 files changed, 439 insertions(+), 119 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/05a39488/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java 
b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
index 7e92ed5..a3864a3 100644
--- a/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
+++ b/core/src/main/java/brooklyn/util/flags/TypeCoercions.java
@@ -103,6 +103,15 @@ public class TypeCoercions {
         return coerce(value, TypeToken.of(targetType));
     }
 
+    public static <T> Maybe<T> tryCoerce(Object value, TypeToken<T> 
targetTypeToken) {
+        try {
+            return Maybe.of( coerce(value, targetTypeToken) );
+        } catch (Throwable t) {
+            Exceptions.propagateIfFatal(t);
+            return Maybe.absent(t); 
+        }
+    }
+    
     /** @see #coerce(Object, Class) */
     @SuppressWarnings({ "unchecked" })
     public static <T> T coerce(Object value, TypeToken<T> targetTypeToken) {
@@ -622,7 +631,7 @@ public class TypeCoercions {
         registerAdapter(String.class, AttributeSensor.class, new 
Function<String,AttributeSensor>() {
             @Override
             public AttributeSensor apply(final String input) {
-                return new BasicAttributeSensor(Object.class, input);
+                return new BasicAttributeSensor<Object>(Object.class, input);
             }
         });
         registerAdapter(String.class, List.class, new Function<String,List>() {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/05a39488/core/src/main/java/brooklyn/util/task/Tasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/Tasks.java 
b/core/src/main/java/brooklyn/util/task/Tasks.java
index 9a7a062..71b43b3 100644
--- a/core/src/main/java/brooklyn/util/task/Tasks.java
+++ b/core/src/main/java/brooklyn/util/task/Tasks.java
@@ -20,13 +20,9 @@ package brooklyn.util.task;
 
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicReference;
 
 import javax.annotation.Nullable;
 
@@ -40,7 +36,6 @@ import brooklyn.management.TaskAdaptable;
 import brooklyn.management.TaskFactory;
 import brooklyn.management.TaskQueueingContext;
 import brooklyn.util.exceptions.Exceptions;
-import brooklyn.util.flags.TypeCoercions;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Function;
@@ -48,9 +43,6 @@ import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 
 public class Tasks {
     
@@ -109,109 +101,29 @@ public class Tasks {
     @SuppressWarnings("rawtypes")
     public static Task current() { return 
BasicExecutionManager.getPerThreadCurrentTask().get(); }
 
+    /** creates a {@link ValueResolver} instance which allows significantly 
more customization than
+     * the various {@link #resolveValue(Object, Class, ExecutionContext)} 
methods here */
+    public static <T> ValueResolver<T> resolving(Object v, Class<T> type) {
+        return new ValueResolver<T>(v, type);
+    }
+
+    public static ValueResolver.ResolverBuilderPretype resolving(Object v) {
+        return new ValueResolver.ResolverBuilderPretype(v);
+    }
+
     /** @see #resolveValue(Object, Class, ExecutionContext, String) */
-    public static <T> T resolveValue(Object v, Class<T> type, ExecutionContext 
exec) throws ExecutionException, InterruptedException {
-        return resolveValue(v, type, exec, null);
+    public static <T> T resolveValue(Object v, Class<T> type, @Nullable 
ExecutionContext exec) throws ExecutionException, InterruptedException {
+        return new ValueResolver<T>(v, type).context(exec).get();
     }
     
     /** attempt to resolve the given value as the given type, waiting on 
futures, submitting if necessary,
      * and coercing as allowed by TypeCoercions;
-     * contextMessage (optional) will be displayed in status reports while it 
waits (e.g. the name of the config key being looked up) */
-    public static <T> T resolveValue(Object v, Class<T> type, ExecutionContext 
exec, String contextMessage) throws ExecutionException, InterruptedException {
-        return resolveValue(v, type, exec, contextMessage, false);
+     * contextMessage (optional) will be displayed in status reports while it 
waits (e.g. the name of the config key being looked up).
+     * if no execution context supplied (null) this method will throw an 
exception if the object is an unsubmitted task */
+    public static <T> T resolveValue(Object v, Class<T> type, @Nullable 
ExecutionContext exec, String contextMessage) throws ExecutionException, 
InterruptedException {
+        return new ValueResolver<T>(v, 
type).context(exec).description(contextMessage).get();
     }
     
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    private static <T> T resolveValue(Object v, Class<T> type, 
ExecutionContext exec, String contextMessage, boolean forceDeep) throws 
ExecutionException, InterruptedException {
-        if (type==null) 
-            throw new NullPointerException("type cannot be null in 
resolveValue, for '"+v+"'"+(contextMessage!=null ? ", "+contextMessage : ""));
-        //if the expected type is a closure or map and that's what we have, 
we're done (or if it's null);
-        //but not allowed to return a future or DeferredSupplier as the 
resolved value
-        if (v==null || (!forceDeep && type.isInstance(v) && 
!Future.class.isInstance(v) && !DeferredSupplier.class.isInstance(v)))
-            return (T) v;
-        try {
-            //if it's a task or a future, we wait for the task to complete
-               if (v instanceof TaskAdaptable<?>) {
-                //if it's a task, we make sure it is submitted
-                //(perhaps could run it here? ... tbd)
-                if (!((TaskAdaptable<?>) v).asTask().isSubmitted() ) {
-                    exec.submit(((TaskAdaptable<?>) v).asTask());
-                }
-            }
-            
-            if (v instanceof Future) {
-                final Future<?> vfuture = (Future<?>) v;
-                
-                //including tasks, above
-                if (!vfuture.isDone()) {
-                    final AtomicReference<Object> vref = new 
AtomicReference<Object>(v);
-                    
-                    withBlockingDetails("Waiting for "+(contextMessage!=null ? 
contextMessage : ""+v), 
-                            new Callable<Void>() {
-                        public Void call() throws Exception {
-                            vref.set( vfuture.get() );
-                            return null;
-                        }
-                    });
-                    
-                    v = vref.get();
-                    
-                } else {
-                    v = vfuture.get();
-                }
-                
-            } else if (v instanceof DeferredSupplier<?>) {
-                v = ((DeferredSupplier<?>) v).get();
-                
-            } else if (v instanceof Map) {
-                //and if a map or list we look inside
-                Map result = Maps.newLinkedHashMap();
-                for (Map.Entry<?,?> entry : ((Map<?,?>)v).entrySet()) {
-                    result.put(entry.getKey(), resolveValue(entry.getValue(), 
type, exec,
-                            (contextMessage!=null ? contextMessage+", " : "") 
+ "map entry "+entry.getKey(), forceDeep));
-                }
-                return (T) result;
-                
-            } else if (v instanceof List) {
-                List result = Lists.newArrayList();
-                int count = 0;
-                for (Object it : (List)v) {
-                    result.add(resolveValue(it, type, exec, 
-                            (contextMessage!=null ? contextMessage+", " : "") 
+ "list entry "+count, forceDeep));
-                    count++;
-                }
-                return (T) result;
-                
-            } else if (v instanceof Set) {
-                Set result = Sets.newLinkedHashSet();
-                int count = 0;
-                for (Object it : (Set)v) {
-                    result.add(resolveValue(it, type, exec, 
-                            (contextMessage!=null ? contextMessage+", " : "") 
+ "list entry "+count, forceDeep));
-                    count++;
-                }
-                return (T) result;
-                
-            } else if (v instanceof Iterable) {
-                List result = Lists.newArrayList();
-                int count = 0;
-                for (Object it : (Iterable)v) {
-                    result.add(resolveValue(it, type, exec, 
-                            (contextMessage!=null ? contextMessage+", " : "") 
+ "list entry "+count, forceDeep));
-                    count++;
-                }
-                return (T) result;
-                
-            } else {
-                return TypeCoercions.coerce(v, type);
-            }
-            
-        } catch (Exception e) {
-            throw new IllegalArgumentException("Error resolving 
"+(contextMessage!=null ? contextMessage+", " : "")+v+", in "+exec+": "+e, e);
-        }
-        return resolveValue(v, type, exec, contextMessage, forceDeep);
-    }
-
     /**
      * @see #resolveDeepValue(Object, Class, ExecutionContext, String)
      */
@@ -238,8 +150,8 @@ public class Tasks {
      * e.g. if the requested type is an Object, {@link #resolveValue(Object, 
Class, ExecutionContext, String)}
      * will decide that it matches a Map and not recurse on it, whereas this 
will recurse on it.
      */
-    public static Object resolveDeepValue(Object v, Class<?> type, 
ExecutionContext exec, String contextMessage) throws ExecutionException, 
InterruptedException {
-        return resolveValue(v, type, exec, contextMessage, true);
+    public static <T> T resolveDeepValue(Object v, Class<T> type, 
ExecutionContext exec, String contextMessage) throws ExecutionException, 
InterruptedException {
+        return new ValueResolver<T>(v, 
type).context(exec).deep(true).description(contextMessage).get();
     }
 
     /** sets extra status details on the current task, if possible (otherwise 
does nothing).

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/05a39488/core/src/main/java/brooklyn/util/task/ValueResolver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/task/ValueResolver.java 
b/core/src/main/java/brooklyn/util/task/ValueResolver.java
new file mode 100644
index 0000000..5508b0d
--- /dev/null
+++ b/core/src/main/java/brooklyn/util/task/ValueResolver.java
@@ -0,0 +1,284 @@
+/*
+ * 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 brooklyn.util.task;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import brooklyn.management.ExecutionContext;
+import brooklyn.management.Task;
+import brooklyn.management.TaskAdaptable;
+import brooklyn.util.flags.TypeCoercions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.time.CountdownTimer;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Durations;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.reflect.TypeToken;
+
+/** 
+ * Resolves a given object, as follows:
+ * <li> If it is a {@link Tasks} or a {@link DeferredSupplier} then get its 
contents
+ * <li> If it's a map and {@link #deep(boolean)} is requested, it applies 
resolution to contents
+ * <li> It applies coercion
+ * <p>
+ * Fluent-style API exposes a number of other options.
+ */
+public class ValueResolver<T> {
+    
+    final Object value;
+    final Class<T> type;
+    ExecutionContext exec;
+    String description;
+    boolean forceDeep;
+    /** null means do it if you can; true means always, false means never */
+    Boolean embedResolutionInTask;
+    /** timeout on execution, if possible, or if embedResolutionInTask is true 
*/
+    Duration timeout;
+    
+    // internal fields
+    final Object parentOriginalValue;
+    final CountdownTimer parentTimer;
+    AtomicBoolean started = new AtomicBoolean(false);
+    boolean expired;
+    
+    ValueResolver(Object v, Class<T> type) {
+        this.value = v;
+        this.type = type;
+        checkTypeNotNull();
+        parentOriginalValue = null;
+        parentTimer = null;
+    }
+    
+    ValueResolver(Object v, Class<T> type, ValueResolver<?> parent) {
+        this.value = v;
+        this.type = type;
+        checkTypeNotNull();
+        
+        exec = parent.exec;
+        description = parent.description;
+        forceDeep = parent.forceDeep;
+        embedResolutionInTask = parent.embedResolutionInTask;
+
+        parentOriginalValue = parent.getOriginalValue();
+
+        timeout = parent.timeout;
+        parentTimer = parent.parentTimer;
+        if (parentTimer!=null && parentTimer.isExpired())
+            expired = true;
+    }
+
+    public static class ResolverBuilderPretype {
+        final Object v;
+        public ResolverBuilderPretype(Object v) {
+            this.v = v;
+        }
+        public <T> ValueResolver<T> as(Class<T> type) {
+            return new ValueResolver<T>(v, type);
+        }
+    }
+    
+    /** execution context to use when resolving; required if resolving 
unsubmitted tasks or running with a time limit */
+    public ValueResolver<T> context(ExecutionContext exec) {
+        this.exec = exec;
+        return this;
+    }
+    
+    /** sets a message which will be displayed in status reports while it 
waits (e.g. the name of the config key being looked up) */
+    public ValueResolver<T> description(String description) {
+        this.description = description;
+        return this;
+    }
+    
+    /** causes nested structures (maps, lists) to be descended and nested 
unresolved values resolved */
+    public ValueResolver<T> deep(boolean forceDeep) {
+        this.forceDeep = forceDeep;
+        return this;
+    }
+
+    /** if true, forces execution of a deferred supplier to be run in a task;
+     * if false, it prevents it (meaning time limits may not be applied);
+     * if null, the default, it runs in a task if a time limit is applied.
+     * <p>
+     * running inside a task is required for some {@link DeferredSupplier}
+     * instances which look up a task {@link ExecutionContext}. */
+    public ValueResolver<T> embedResolutionInTask(Boolean 
embedResolutionInTask) {
+        this.embedResolutionInTask = embedResolutionInTask;
+        return this;
+    }
+    
+    /** sets a time limit on executions
+     * <p>
+     * used for {@link Task} and {@link DeferredSupplier} instances.
+     * may require an execution context at runtime. */
+    public ValueResolver<T> timeout(Duration timeout) {
+        this.timeout = timeout;
+        return this;
+    }
+    
+    protected void checkTypeNotNull() {
+        if (type==null) 
+            throw new NullPointerException("type must be set to resolve, for 
'"+value+"'"+(description!=null ? ", "+description : ""));
+    }
+
+    public T get() {
+        return getMaybe().get();
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public Maybe<T> getMaybe() {
+        if (started.getAndSet(true))
+            throw new IllegalStateException("ValueResolver can only be used 
once");
+        
+        if (expired) return Maybe.absent("Nested resolution of 
"+getOriginalValue()+" did not complete within "+timeout);
+        
+        CountdownTimer timerU = parentTimer;
+        if (timerU==null && timeout!=null)
+            timerU = timeout.countdownTimer();
+        final CountdownTimer timer = timerU;
+        if (timer!=null && !timer.isRunning())
+            timer.start();
+        
+        checkTypeNotNull();
+        Object v = this.value;
+        
+        //if the expected type is a closure or map and that's what we have, 
we're done (or if it's null);
+        //but not allowed to return a future or DeferredSupplier as the 
resolved value
+        if (v==null || (!forceDeep && type.isInstance(v) && 
!Future.class.isInstance(v) && !DeferredSupplier.class.isInstance(v)))
+            return Maybe.of((T) v);
+        
+        try {
+            //if it's a task or a future, we wait for the task to complete
+            if (v instanceof TaskAdaptable<?>) {
+                //if it's a task, we make sure it is submitted
+                if (!((TaskAdaptable<?>) v).asTask().isSubmitted() ) {
+                    // TODO could try to get exec context from Tasks.current() 
... should we?
+                    if (exec==null)
+                        return Maybe.absent("Value for unsubmitted task 
'"+getDescription()+"' requested but no execution context available");
+                    exec.submit(((TaskAdaptable<?>) v).asTask());
+                }
+            }
+
+            if (v instanceof Future) {
+                final Future<?> vfuture = (Future<?>) v;
+
+                //including tasks, above
+                if (!vfuture.isDone()) {
+                    Callable<Maybe> callable = new Callable<Maybe>() {
+                        public Maybe call() throws Exception {
+                            return Durations.get(vfuture, timer);
+                        } };
+
+                    String description = getDescription();
+                    Maybe vm = Tasks.withBlockingDetails("Waiting for 
"+description, callable);
+                    if (vm.isAbsent()) return vm;
+                    v = vm.get();
+
+                } else {
+                    v = vfuture.get();
+                    
+                }
+
+            } else if (v instanceof DeferredSupplier<?>) {
+                final Object vf = v;
+                Callable<Object> callable = new Callable<Object>() {
+                    public Object call() throws Exception {
+                        return ((DeferredSupplier<?>) vf).get();
+                    } };
+                    
+                if (Boolean.TRUE.equals(embedResolutionInTask) || 
timeout!=null) {
+                    if (exec==null)
+                        return Maybe.absent("Embedding in task needed for 
'"+getDescription()+"' but no execution context available");
+                        
+                    String description = getDescription();
+                    Task<Object> vt = 
exec.submit(Tasks.<Object>builder().body(callable).name("Resolving dependent 
value").description(description).build());
+                    Maybe<Object> vm = Durations.get(vt, timer);
+                    vt.cancel(true);
+                    if (vm.isAbsent()) return (Maybe<T>)vm;
+                    v = vm.get();
+                    
+                } else {
+                    v = callable.call();
+                    
+                }
+
+            } else if (v instanceof Map) {
+                //and if a map or list we look inside
+                Map result = Maps.newLinkedHashMap();
+                for (Map.Entry<?,?> entry : ((Map<?,?>)v).entrySet()) {
+                    Maybe<?> vv = new ValueResolver(entry.getValue(), type, 
this)
+                        .description( (description!=null ? description+", " : 
"") + "map entry "+entry.getKey() )
+                        .getMaybe();
+                    if (vv.isAbsent()) return (Maybe<T>)vv;
+                    result.put(entry.getKey(), vv.get());
+                }
+                return Maybe.of((T) result);
+
+            } else if (v instanceof Set) {
+                Set result = Sets.newLinkedHashSet();
+                int count = 0;
+                for (Object it : (Set)v) {
+                    Maybe<?> vv = new ValueResolver(it, type, this)
+                        .description( (description!=null ? description+", " : 
"") + "entry "+count )
+                        .getMaybe();
+                    if (vv.isAbsent()) return (Maybe<T>)vv;
+                    result.add(vv.get());
+                    count++;
+                }
+                return Maybe.of((T) result);
+
+            } else if (v instanceof Iterable) {
+                List result = Lists.newArrayList();
+                int count = 0;
+                for (Object it : (Iterable)v) {
+                    Maybe<?> vv = new ValueResolver(it, type, this)
+                        .description( (description!=null ? description+", " : 
"") + "entry "+count )
+                        .getMaybe();
+                    if (vv.isAbsent()) return (Maybe<T>)vv;
+                    result.add(vv.get());
+                    count++;
+                }
+                return Maybe.of((T) result);
+
+            } else {
+                return TypeCoercions.tryCoerce(v, TypeToken.of(type));
+            }
+
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Error resolving 
"+(description!=null ? description+", " : "")+v+", in "+exec+": "+e, e);
+        }
+        
+        return new ValueResolver(v, type, this).getMaybe();
+    }
+
+    protected String getDescription() {
+        return description!=null ? description : ""+value;
+    }
+    protected Object getOriginalValue() {
+        if (parentOriginalValue!=null) return parentOriginalValue;
+        return value;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/05a39488/core/src/test/java/brooklyn/util/task/TasksTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/util/task/TasksTest.java 
b/core/src/test/java/brooklyn/util/task/TasksTest.java
index 2d53a1e..8ba61e0 100644
--- a/core/src/test/java/brooklyn/util/task/TasksTest.java
+++ b/core/src/test/java/brooklyn/util/task/TasksTest.java
@@ -88,6 +88,7 @@ public class TasksTest extends BrooklynAppUnitTestSupport {
         assertResolvesValue(orig, String.class, expected);
     }
     
+    @SuppressWarnings("unchecked")
     @Test
     public void testResolvesIterableOfMapsWithAttributeWhenReady() throws 
Exception {
         app.setAttribute(TestApplication.MY_ATTRIBUTE, "myval");

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/05a39488/core/src/test/java/brooklyn/util/task/ValueResolverTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/util/task/ValueResolverTest.java 
b/core/src/test/java/brooklyn/util/task/ValueResolverTest.java
new file mode 100644
index 0000000..43c156c
--- /dev/null
+++ b/core/src/test/java/brooklyn/util/task/ValueResolverTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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 brooklyn.util.task;
+
+import java.util.concurrent.Callable;
+
+import org.junit.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.BrooklynAppUnitTestSupport;
+import brooklyn.management.ExecutionContext;
+import brooklyn.management.Task;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+
+/**
+ * see also {@link TasksTest} for more tests
+ */
+@Test
+public class ValueResolverTest extends BrooklynAppUnitTestSupport {
+
+    private ExecutionContext executionContext;
+
+    @BeforeMethod(alwaysRun=true)
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        executionContext = app.getExecutionContext();
+    }
+    
+    public static final Task<String> newSleepTask(final Duration timeout, 
final String result) {
+        return Tasks.<String>builder().body(new Callable<String>() { 
+            public String call() { 
+                Time.sleep(timeout); 
+                return result; 
+            }}
+        ).build();
+    }
+    
+    public void testTimeoutZero() {
+        Maybe<String> result = 
Tasks.resolving(newSleepTask(Duration.TEN_SECONDS, 
"foo")).as(String.class).context(executionContext).timeout(Duration.ZERO).getMaybe();
+        Assert.assertFalse(result.isPresent());
+    }
+    
+    public void testTimeoutBig() {
+        Maybe<String> result = Tasks.resolving(newSleepTask(Duration.ZERO, 
"foo")).as(String.class).context(executionContext).timeout(Duration.TEN_SECONDS).getMaybe();
+        Assert.assertEquals(result.get(), "foo");
+    }
+
+    public void testNoExecutionContextOnCompleted() {
+        Task<String> t = newSleepTask(Duration.ZERO, "foo");
+        executionContext.submit(t).getUnchecked();
+        Maybe<String> result = 
Tasks.resolving(t).as(String.class).timeout(Duration.ZERO).getMaybe();
+        Assert.assertEquals(result.get(), "foo");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/05a39488/usage/rest-server/src/main/java/brooklyn/rest/security/BrooklynPropertiesSecurityFilter.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/main/java/brooklyn/rest/security/BrooklynPropertiesSecurityFilter.java
 
b/usage/rest-server/src/main/java/brooklyn/rest/security/BrooklynPropertiesSecurityFilter.java
index bffa6a3..e5894f6 100644
--- 
a/usage/rest-server/src/main/java/brooklyn/rest/security/BrooklynPropertiesSecurityFilter.java
+++ 
b/usage/rest-server/src/main/java/brooklyn/rest/security/BrooklynPropertiesSecurityFilter.java
@@ -111,11 +111,10 @@ public class BrooklynPropertiesSecurityFilter implements 
Filter {
                     }
                     return;
                 } catch (Throwable e) {
-                    // NB errors are typically already caught at this point
-                    if (log.isDebugEnabled()) {
-                        log.debug("REST failed processing request " + uri + " 
with " + entitlementContext + ": " + e, e);
-                    }
-                    throw Exceptions.propagate(e);
+                    // errors are typically already caught at this point, 
except for serialization errors
+                    log.warn("REST failed processing request " + uri + " with 
" + entitlementContext + ": " + e, e);
+                    ((HttpServletResponse) 
response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                    return;
                 } finally {
                     originalRequest.remove();
                     Entitlements.clearEntitlementContext();

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/05a39488/usage/rest-server/src/main/java/brooklyn/rest/transform/EffectorTransformer.java
----------------------------------------------------------------------
diff --git 
a/usage/rest-server/src/main/java/brooklyn/rest/transform/EffectorTransformer.java
 
b/usage/rest-server/src/main/java/brooklyn/rest/transform/EffectorTransformer.java
index 89f3f02..1d15abe 100644
--- 
a/usage/rest-server/src/main/java/brooklyn/rest/transform/EffectorTransformer.java
+++ 
b/usage/rest-server/src/main/java/brooklyn/rest/transform/EffectorTransformer.java
@@ -24,10 +24,17 @@ import java.util.Set;
 import javax.annotation.Nullable;
 
 import brooklyn.entity.Effector;
+import brooklyn.entity.Entity;
 import brooklyn.entity.ParameterType;
+import brooklyn.entity.basic.EntityInternal;
 import brooklyn.entity.basic.EntityLocal;
 import brooklyn.rest.domain.EffectorSummary;
 import brooklyn.rest.domain.EffectorSummary.ParameterSummary;
+import brooklyn.rest.util.WebResourceUtils;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.task.Tasks;
+import brooklyn.util.time.Duration;
 
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableMap;
@@ -36,7 +43,7 @@ import com.google.common.collect.Iterables;
 
 public class EffectorTransformer {
 
-    public static EffectorSummary effectorSummary(EntityLocal entity, 
Effector<?> effector) {
+    public static EffectorSummary effectorSummary(final EntityLocal entity, 
Effector<?> effector) {
         String applicationUri = "/v1/applications/" + 
entity.getApplicationId();
         String entityUri = applicationUri + "/entities/" + entity.getId();
         return new EffectorSummary(effector.getName(), 
effector.getReturnTypeName(),
@@ -44,7 +51,7 @@ public class EffectorTransformer {
                 new Function<ParameterType<?>, 
EffectorSummary.ParameterSummary<?>>() {
                     @Override
                     public EffectorSummary.ParameterSummary<?> apply(@Nullable 
ParameterType<?> parameterType) {
-                        return parameterSummary(parameterType);
+                        return parameterSummary(entity, parameterType);
                     }
                 })), effector.getDescription(), ImmutableMap.of(
                 "self", URI.create(entityUri + "/effectors/" + 
effector.getName()),
@@ -58,16 +65,23 @@ public class EffectorTransformer {
                 new Function<ParameterType<?>, 
EffectorSummary.ParameterSummary<?>>() {
                     @Override
                     public EffectorSummary.ParameterSummary<?> 
apply(ParameterType<?> parameterType) {
-                        return parameterSummary(parameterType);
+                        return parameterSummary(null, parameterType);
                     }
                 }));
         return new EffectorSummary(effector.getName(),
                 effector.getReturnTypeName(), parameters, 
effector.getDescription(), null);
     }
     
-    @SuppressWarnings("unchecked")
-    protected static EffectorSummary.ParameterSummary<?> 
parameterSummary(ParameterType<?> parameterType) {
-        return new ParameterSummary(parameterType.getName(), 
parameterType.getParameterClassName(), 
-                parameterType.getDescription(), 
parameterType.getDefaultValue());
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected static EffectorSummary.ParameterSummary<?> 
parameterSummary(Entity entity, ParameterType<?> parameterType) {
+        try {
+            Maybe<?> defaultValue = 
Tasks.resolving(parameterType.getDefaultValue()).as(parameterType.getParameterClass())
+                .context(entity!=null ? 
((EntityInternal)entity).getExecutionContext() : 
null).timeout(Duration.millis(50)).getMaybe();
+            return new ParameterSummary(parameterType.getName(), 
parameterType.getParameterClassName(), 
+                parameterType.getDescription(), 
+                WebResourceUtils.getValueForDisplay(defaultValue.orNull(), 
true, false));
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/05a39488/utils/common/src/main/java/brooklyn/util/time/Durations.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/time/Durations.java 
b/utils/common/src/main/java/brooklyn/util/time/Durations.java
index b1fb6d2..60e77f4 100644
--- a/utils/common/src/main/java/brooklyn/util/time/Durations.java
+++ b/utils/common/src/main/java/brooklyn/util/time/Durations.java
@@ -19,7 +19,12 @@
 package brooklyn.util.time;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
 
 public class Durations {
 
@@ -30,5 +35,26 @@ public class Durations {
     public static void join(Thread thread, Duration time) throws 
InterruptedException {
         thread.join(time.toMillisecondsRoundingUp());
     }
+
+    public static <T> Maybe<T> get(Future<T> t, Duration timeout) {
+        try {
+            if (timeout==null || timeout.toMilliseconds()<0 || 
Duration.PRACTICALLY_FOREVER.equals(timeout))
+                return Maybe.of(t.get());
+            if (timeout.toMilliseconds()==0 && !t.isDone())
+                return Maybe.absent("Task "+t+" not completed when immediate 
completion requested");
+            return Maybe.of(t.get(timeout.toMilliseconds(), 
TimeUnit.MILLISECONDS));
+        } catch (TimeoutException e) {
+            return Maybe.absent("Task "+t+" did not complete within "+timeout);
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    public static <T> Maybe<T> get(Future<T> t, CountdownTimer timer) {
+        if (timer==null) return get(t, (Duration)null);
+        Duration remaining = timer.getDurationRemaining();
+        if (remaining.isPositive()) return get(t, remaining);
+        return get(t, Duration.ZERO);
+    }
     
 }

Reply via email to