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); + } }
