Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.BrooklynDslCommon;
import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.DslToStringHelpers;
import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
import org.apache.brooklyn.util.core.task.ImmediateSupplier;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
Expand Down Expand Up @@ -94,7 +95,7 @@ protected Maybe<Object> invokeOnDeferred(Object obj, boolean immediate) {
return invokeOn(instance);
} else {
if (immediate) {
return Maybe.absent("Could not evaluate immediately: " + obj);
return Maybe.absent(new ImmediateSupplier.ImmediateValueNotAvailableException("Could not evaluate immediately: " + obj));
} else {
return Maybe.absent(Maybe.getException(resolvedMaybe));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -589,18 +589,11 @@ public Maybe<Object> getImmediately() {
final Class<?> clazz = getOrLoadType();
final ExecutionContext executionContext = entity().getExecutionContext();

// Marker exception that one of our component-parts cannot yet be resolved -
// throwing and catching this allows us to abort fast.
// A bit messy to use exceptions in normal control flow, but this allows the Maps util methods to be used.
@SuppressWarnings("serial")
class UnavailableException extends RuntimeException {
}

final Function<Object, Object> resolver = new Function<Object, Object>() {
@Override public Object apply(Object value) {
Maybe<Object> result = Tasks.resolving(value, Object.class).context(executionContext).deep(true).immediately(true).getMaybe();
if (result.isAbsent()) {
throw new UnavailableException();
throw new ImmediateValueNotAvailableException();
} else {
return result.get();
}
Expand All @@ -620,8 +613,8 @@ class UnavailableException extends RuntimeException {
result = create(clazz, factoryMethodName, resolvedFactoryMethodArgs, resolvedFields, resolvedConfig);
}
return Maybe.of(result);
} catch (UnavailableException e) {
return Maybe.absent();
} catch (ImmediateValueNotAvailableException e) {
return ImmediateValueNotAvailableException.newAbsentWithExceptionSupplier();
}
}

Expand Down Expand Up @@ -873,7 +866,7 @@ public EntitySupplier(String entityId) {
public Maybe<Entity> getImmediately() {
EntityInternal entity = entity();
if (entity == null) {
return Maybe.absent();
return Maybe.absent("No entity available");
}
Entity targetEntity = entity.getManagementContext().getEntityManager().getEntity(entityId);
return Maybe.of(targetEntity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ protected Maybe<Entity> callImpl(boolean immediate) throws Exception {

if (immediate) {
if (maybeComponentId.isAbsent()) {
return Maybe.absent(Maybe.getException(maybeComponentId));
return ImmediateValueNotAvailableException.newAbsentWrapping("Cannot find component ID", maybeComponentId);
}
}

Expand Down Expand Up @@ -418,7 +418,7 @@ public EntityId(DslComponent component) {
@Override
public Maybe<Object> getImmediately() {
Maybe<Entity> targetEntityMaybe = component.getImmediately();
if (targetEntityMaybe.isAbsent()) return Maybe.absent("Target entity not available");
if (targetEntityMaybe.isAbsent()) return ImmediateValueNotAvailableException.newAbsentWrapping("Target entity is not available: "+component, targetEntityMaybe);
Entity targetEntity = targetEntityMaybe.get();

return Maybe.<Object>of(targetEntity.getId());
Expand Down Expand Up @@ -477,7 +477,7 @@ protected String resolveSensorName(boolean immediately) {
@Override
public final Maybe<Object> getImmediately() {
Maybe<Entity> targetEntityMaybe = component.getImmediately();
if (targetEntityMaybe.isAbsent()) return Maybe.absent("Target entity not available");
if (targetEntityMaybe.isAbsent()) return ImmediateValueNotAvailableException.newAbsentWrapping("Target entity not available: "+component, targetEntityMaybe);
Entity targetEntity = targetEntityMaybe.get();

String sensorNameS = resolveSensorName(true);
Expand All @@ -486,7 +486,7 @@ public final Maybe<Object> getImmediately() {
targetSensor = Sensors.newSensor(Object.class, sensorNameS);
}
Object result = targetEntity.sensors().get(targetSensor);
return GroovyJavaMethods.truth(result) ? Maybe.of(result) : Maybe.absent();
return GroovyJavaMethods.truth(result) ? Maybe.of(result) : ImmediateValueNotAvailableException.newAbsentWithExceptionSupplier();
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -660,7 +660,7 @@ protected Maybe<Sensor<?>> getImmediately(Object si, boolean resolved) {
return Maybe.<Sensor<?>>of((Sensor<?>)si);
} else if (si instanceof String) {
Maybe<Entity> targetEntityMaybe = component.getImmediately();
if (targetEntityMaybe.isAbsent()) return Maybe.absent("Target entity not available");
if (targetEntityMaybe.isAbsent()) return ImmediateValueNotAvailableException.newAbsentWrapping("Target entity is not available: "+component, targetEntityMaybe);
Entity targetEntity = targetEntityMaybe.get();

Sensor<?> result = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,16 +307,7 @@ public void testDeferredSupplierToConfig() throws Exception {
assertEquals(entity.config().get(TestEntity.CONF_SET_PLAIN), ImmutableSet.of("myOther"));
}

/**
* TODO The {@code entity.config().getNonBlocking()} can return absent. When it's called with
* a deferred supplier value, it will kick off a task and then wait just a few millis for that
* task to execute deferredSupplier.get(). If it times out, then it returns Maybe.absent.
* However, on apache jenkins the machine is often slow so the task doesn't complete in the
* given number of millis (even though deferredSupplier.get() doesn't need to block for anything).
* Same for {@link #testDeferredSupplierToAttributeWhenReadyInSpecialTypes()}.
* See https://issues.apache.org/jira/browse/BROOKLYN-272.
*/
@Test(groups="Broken")
@Test
public void testDeferredSupplierToAttributeWhenReady() throws Exception {
String yaml = Joiner.on("\n").join(
"services:",
Expand Down Expand Up @@ -344,17 +335,9 @@ public Object call() {

/**
* This tests config keys of type {@link org.apache.brooklyn.core.config.MapConfigKey}, etc.
* For plain maps, see {@link #testDeferredSupplierToAttributeWhenReadyInPlainCollections()}.
*
* TODO The {@code entity.config().getNonBlocking()} can return absent. When it's called with
* a deferred supplier value, it will kick off a task and then wait just a few millis for that
* task to execute deferredSupplier.get(). If it times out, then it returns Maybe.absent.
* However, on apache jenkins the machine is often slow so the task doesn't complete in the
* given number of millis (even though deferredSupplier.get() doesn't need to block for anything).
* Same for {@link #testDeferredSupplierToAttributeWhenReady()}.
* See https://issues.apache.org/jira/browse/BROOKLYN-272.
* For plain maps, see {@link #testDeferredSupplierToAttributeWhenReadyInPlainCollections()}
*/
@Test(groups="Broken")
@Test
public void testDeferredSupplierToAttributeWhenReadyInSpecialTypes() throws Exception {
String yaml = Joiner.on("\n").join(
"services:",
Expand All @@ -372,10 +355,10 @@ public void testDeferredSupplierToAttributeWhenReadyInSpecialTypes() throws Exce
final TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren());

// Attribute not yet set; non-blocking will return promptly without the value
assertTrue(entity.config().getNonBlocking(TestEntity.CONF_NAME).isAbsent());
assertTrue(entity.config().getNonBlocking(TestEntity.CONF_MAP_THING).isAbsent());
assertTrue(entity.config().getNonBlocking(TestEntity.CONF_LIST_THING).isAbsent());
assertTrue(entity.config().getNonBlocking(TestEntity.CONF_SET_THING).isAbsent());
Asserts.assertNotPresent(entity.config().getNonBlocking(TestEntity.CONF_NAME));
Asserts.assertNotPresent(entity.config().getNonBlocking(TestEntity.CONF_MAP_THING));
Asserts.assertNotPresent(entity.config().getNonBlocking(TestEntity.CONF_LIST_THING));
Asserts.assertNotPresent(entity.config().getNonBlocking(TestEntity.CONF_SET_THING));

// Now set the attribute: get will return once that has happened
executor.submit(new Callable<Object>() {
Expand All @@ -399,15 +382,8 @@ public Object call() {
* This tests config keys of type {@link java.util.Map}, etc.
* For special types (e.g. {@link org.apache.brooklyn.core.config.MapConfigKey}), see
* {@link #testDeferredSupplierToAttributeWhenReadyInPlainCollections()}.
*
* TODO test doesn't work because getNonBlocking returns even when no value.
* For example, we get back: Present[value={mykey=attributeWhenReady("myOtherSensor")}].
* However, the `config().get()` does behave as desired.
*
* Including the "WIP" group because this test would presumably have never worked!
* Added to demonstrate the short-coming.
*/
@Test(groups={"Broken", "WIP"})
@Test
public void testDeferredSupplierToAttributeWhenReadyInPlainCollections() throws Exception {
String yaml = Joiner.on("\n").join(
"services:",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@ protected Maybe<Object> resolveRawValueFromContainer(TContainer container, Confi
// wasteful to make a copy to look up; maybe try once opportunistically?
ownCopy = MutableMap.copyOf(oc);
}
// would be cleaner here to have an extractValueMaybe but semantics can get confusing whether absent
// means no value can be extracted (getRaw semantics) and immediate mode is on but blocking is needed (ImmediateSupplier semantics);
// simpler not to support maybe, in which case here null means the former, and the latter throws something (which the caller catches)
Maybe<Object> result = Maybe.of((Object) ((ConfigKeySelfExtracting<?>) key).extractValue(ownCopy, getExecutionContext(container)) );
postLocalEvaluate(key, bo, value, result);
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

import javax.annotation.Nullable;

Expand All @@ -41,8 +39,9 @@
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.core.task.ImmediateSupplier.ImmediateUnsupportedException;
import org.apache.brooklyn.util.core.task.ImmediateSupplier.ImmediateValueNotAvailableException;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.core.task.ValueResolver;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.exceptions.RuntimeInterruptedException;
import org.apache.brooklyn.util.guava.Maybe;
Expand All @@ -53,6 +52,7 @@

public abstract class AbstractConfigurationSupportInternal implements BrooklynObjectInternal.ConfigurationSupportInternal {

@SuppressWarnings("unused")
private static final Logger LOG = LoggerFactory.getLogger(AbstractConfigurationSupportInternal.class);

@Override
Expand All @@ -77,10 +77,16 @@ public <T> Maybe<T> getNonBlocking(HasConfigKey<T> key) {

@Override
public <T> Maybe<T> getNonBlocking(final ConfigKey<T> key) {
if (key instanceof StructuredConfigKey || key instanceof SubElementConfigKey) {
return getNonBlockingResolvingStructuredKey(key);
} else {
return getNonBlockingResolvingSimple(key);
try {
if (key instanceof StructuredConfigKey || key instanceof SubElementConfigKey) {
return getNonBlockingResolvingStructuredKey(key);
} else {
return getNonBlockingResolvingSimple(key);
}
} catch (ImmediateValueNotAvailableException e) {
return Maybe.absent(e);
} catch (ImmediateUnsupportedException e) {
return Maybe.absent(e);
}
}

Expand All @@ -89,12 +95,6 @@ public <T> Maybe<T> getNonBlocking(final ConfigKey<T> key) {
* execute the custom logic, as is done by {@link #get(ConfigKey)}, but non-blocking!
*/
protected <T> Maybe<T> getNonBlockingResolvingStructuredKey(final ConfigKey<T> key) {
// TODO This is a poor implementation. We risk timing out when it's just doing its
// normal work (e.g. because job's thread was starved), rather than when it's truly
// blocked. Really we'd need to dig into the implementation of get(key), so that the
// underlying work can be configured with a timeout, for when it finally calls
// ValueResolver.

Callable<T> job = new Callable<T>() {
@Override
public T call() {
Expand All @@ -106,22 +106,15 @@ public T call() {
}
};

Task<T> t = getContext().submit(Tasks.<T>builder().body(job)
Task<T> t = Tasks.<T>builder().body(job)
.displayName("Resolving dependent value")
.description("Resolving "+key.getName())
.tag(BrooklynTaskTags.TRANSIENT_TASK_TAG)
.build());
.build();
try {
T result = t.get(ValueResolver.NON_BLOCKING_WAIT);
return Maybe.of(result);
} catch (TimeoutException e) {
t.cancel(true);
return Maybe.<T>absent();
} catch (ExecutionException e) {
LOG.debug("Problem resolving "+key.getName()+", returning <absent>", e);
return Maybe.<T>absent();
} catch (InterruptedException e) {
throw Exceptions.propagate(e);
return getContext().getImmediately(t);
} catch (ImmediateUnsupportedException e) {
return Maybe.absent();
}
}

Expand All @@ -138,18 +131,14 @@ protected <T> Maybe<T> getNonBlockingResolvingSimple(ConfigKey<T> key) {
// getRaw returns Maybe(val) if the key was explicitly set (where val can be null)
// or Absent if the config key was unset.
Object unresolved = getRaw(key).or(key.getDefaultValue());
final Object marker = new Object();
// Give tasks a short grace period to resolve.
Object resolved = Tasks.resolving(unresolved)
Maybe<Object> resolved = Tasks.resolving(unresolved)
.as(Object.class)
.defaultValue(marker)
.immediately(true)
.deep(true)
.context(getContext())
.get();
return (resolved != marker)
? TypeCoercions.tryCoerce(resolved, key.getTypeToken())
: Maybe.<T>absent();
.getMaybe();
if (resolved.isAbsent()) return Maybe.Absent.<T>castAbsent(resolved);
return TypeCoercions.tryCoerce(resolved.get(), key.getTypeToken());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that there's no marker object you can rely on ValueResolver's coercion directly (by passing in the expected type).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could, but not 100% sure. There may be some corner cases and some lossiness between as(Class<T>) and tryCoerce(..., TypeToken<T>). Have added a comment.

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
import org.apache.brooklyn.config.ConfigMap.ConfigMapWithInheritance;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.task.ImmediateSupplier;
import org.apache.brooklyn.util.core.task.ImmediateSupplier.ImmediateUnsupportedException;
import org.apache.brooklyn.util.guava.Maybe;

import com.google.common.annotations.Beta;
Expand Down Expand Up @@ -113,13 +115,20 @@ public interface ConfigurationSupportInternal extends Configurable.Configuration

/**
* Attempts to coerce the value for this config key, if available,
* taking a default and {@link Maybe#absent absent} if the uncoerced
* cannot be resolved within a short timeframe.
* including returning a default if the config key is unset,
* returning a {@link Maybe#absent absent} if the uncoerced
* does not support immediate resolution.
* <p>
* Note: if no value for the key is available, not even as a default,
* this returns a {@link Maybe#isPresent()} containing <code>null</code>
* (following the semantics of {@link #get(ConfigKey)}
* rather than {@link #getRaw(ConfigKey)}).
* Thus a {@link Maybe#absent()} definitively indicates that
* the absence is due to the request to evaluate immediately.
* <p>
* This will include catching {@link ImmediateUnsupportedException}
* and returning it as an absence, thus making the semantics here slightly
* "safer" than that of {@link ImmediateSupplier#getImmediately()}.
*/
@Beta
<T> Maybe<T> getNonBlocking(ConfigKey<T> key);
Expand Down
Loading