-
Notifications
You must be signed in to change notification settings - Fork 52
DSL deferred execution #482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
2e0ee18
d71141e
498a58c
ddfe733
42bc7c1
e831d75
6607ccb
eb4ac4c
b75b45c
60c5c6f
f564aa8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| /* | ||
| * Copyright 2016 The Apache Software Foundation. | ||
| * | ||
| * Licensed 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.camp.brooklyn.spi.dsl; | ||
|
|
||
| import org.apache.brooklyn.util.core.task.DeferredSupplier; | ||
|
|
||
| /** | ||
| * Marker interface so the evaluator can tell apart objects which are {@link DeferredSupplier} | ||
| * but which expect DSL methods called on them instead of the value they supply. | ||
| */ | ||
| public interface DslCallable { | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| /* | ||
| * Copyright 2016 The Apache Software Foundation. | ||
| * | ||
| * Licensed 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.camp.brooklyn.spi.dsl; | ||
|
|
||
| import java.lang.reflect.InvocationTargetException; | ||
| import java.util.List; | ||
| import java.util.concurrent.Callable; | ||
|
|
||
| import org.apache.brooklyn.api.mgmt.Task; | ||
| import org.apache.brooklyn.camp.brooklyn.spi.dsl.methods.BrooklynDslCommon; | ||
| import org.apache.brooklyn.core.entity.EntityInternal; | ||
| import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; | ||
| import org.apache.brooklyn.util.core.task.Tasks; | ||
| import org.apache.brooklyn.util.exceptions.Exceptions; | ||
| import org.apache.brooklyn.util.guava.Maybe; | ||
| import org.apache.brooklyn.util.javalang.Reflections; | ||
|
|
||
| import com.google.common.base.Joiner; | ||
| import com.google.common.base.Objects; | ||
|
|
||
| public class DslDeferredFunctionCall extends BrooklynDslDeferredSupplier<Object> { | ||
|
|
||
| private static final long serialVersionUID = 3243262633795112155L; | ||
|
|
||
| // TODO should this be some of the super types? | ||
| private BrooklynDslDeferredSupplier<?> object; | ||
| private String fnName; | ||
| private List<?> args; | ||
|
|
||
| public DslDeferredFunctionCall(BrooklynDslDeferredSupplier<?> o, String fn, List<Object> args) { | ||
| this.object = o; | ||
| this.fnName = fn; | ||
| this.args = args; | ||
| } | ||
|
|
||
| @Override | ||
| public Maybe<Object> getImmediately() { | ||
| Maybe<?> obj = resolveImmediate(object); | ||
| if (obj.isPresent()) { | ||
| if (obj.isNull()) { | ||
| throw new IllegalArgumentException("Deferred function call, " + object + | ||
| " evaluates to null (when calling " + fnName + "(" + toString(args) + "))"); | ||
| } | ||
| return Maybe.of(invokeOn(obj.get())); | ||
| } | ||
| return Maybe.absent("Could not evaluate immediately " + object); | ||
| } | ||
|
|
||
| @Override | ||
| public Task<Object> newTask() { | ||
| return Tasks.builder() | ||
| .displayName("Deferred function call " + object + "." + fnName + "(" + toString(args) + ")") | ||
| .tag(BrooklynTaskTags.TRANSIENT_TASK_TAG) | ||
| .dynamic(false) | ||
| .body(new Callable<Object>() { | ||
| @Override | ||
| public Object call() throws Exception { | ||
| Object obj = DslDeferredFunctionCall.this.resolveBlocking(object).orNull(); | ||
| if (obj == null) { | ||
| throw new IllegalArgumentException("Deferred function call, " + object + | ||
| " evaluates to null (when calling " + fnName + "(" + DslDeferredFunctionCall.toString(args) + "))"); | ||
| } | ||
| return invokeOn(obj); | ||
| } | ||
|
|
||
| }).build(); | ||
| } | ||
|
|
||
| protected Maybe<?> resolveImmediate(Object object) { | ||
| return resolve(object, true); | ||
| } | ||
| protected Maybe<?> resolveBlocking(Object object) { | ||
| return resolve(object, false); | ||
| } | ||
| protected Maybe<?> resolve(Object object, boolean immediate) { | ||
| if (object instanceof DslCallable || object == null) { | ||
| return Maybe.of(object); | ||
| } | ||
| Maybe<?> resultMaybe = Tasks.resolving(object, Object.class) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is this code reachable? This class is only constructed when
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nvm - missed the |
||
| .context(((EntityInternal)entity()).getExecutionContext()) | ||
| .deep(true) | ||
| .immediately(immediate) | ||
| .recursive(false) | ||
| .getMaybe(); | ||
| if (resultMaybe.isAbsent()) { | ||
| return resultMaybe; | ||
| } else { | ||
| // No nice way to figure out whether the object is deferred. Try to resolve it | ||
| // until it matches the input value as a poor man's replacement. | ||
| Object result = resultMaybe.get(); | ||
| if (result == object) { | ||
| return resultMaybe; | ||
| } else { | ||
| return resolve(result, immediate); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| protected Object invokeOn(Object obj) { | ||
| return invokeOn(obj, fnName, args); | ||
| } | ||
|
|
||
| protected static Object invokeOn(Object obj, String fnName, List<?> args) { | ||
| checkCallAllowed(obj, fnName, args); | ||
|
|
||
| Maybe<Object> v; | ||
| try { | ||
| v = Reflections.invokeMethodFromArgs(obj, fnName, args); | ||
| } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { | ||
| Exceptions.propagateIfFatal(e); | ||
| throw Exceptions.propagate(new InvocationTargetException(e, "Error invoking '"+fnName+"("+toString(args)+")' on '"+obj+"'")); | ||
| } | ||
| if (v.isPresent()) { | ||
| // Value is most likely another BrooklynDslDeferredSupplier - let the caller handle it, | ||
| return v.get(); | ||
| } else { | ||
| throw new IllegalArgumentException("No such function '"+fnName+"("+toString(args)+")' on "+obj); | ||
| } | ||
| } | ||
|
|
||
| private static void checkCallAllowed(Object obj, String fnName2, List<?> args2) { | ||
| Class<?> clazz; | ||
| if (obj instanceof Class) { | ||
| clazz = (Class<?>)obj; | ||
| } else { | ||
| clazz = obj.getClass(); | ||
| } | ||
| if (!(clazz.getPackage().getName().startsWith(BrooklynDslCommon.class.getPackage().getName()))) | ||
| throw new IllegalArgumentException("Not permitted to invoke function on '"+clazz+"' (outside allowed package scope)"); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hashCode(object, fnName, args); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object obj) { | ||
| if (this == obj) return true; | ||
| if (obj == null || getClass() != obj.getClass()) return false; | ||
| DslDeferredFunctionCall that = DslDeferredFunctionCall.class.cast(obj); | ||
| return Objects.equal(this.object, that.object) && | ||
| Objects.equal(this.fnName, that.fnName) && | ||
| Objects.equal(this.args, that.args); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return object + "." + fnName + "(" + toString(args) + ")"; | ||
| } | ||
|
|
||
| private static String toString(List<?> args) { | ||
| if (args == null) return ""; | ||
| return Joiner.on(", ").join(args); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm considering changing this to an annotation. When applied on:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea. Do you want to make it part of this PR or do it subsequently?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one. Don't want to introduce APIs that will go away in master.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After spiking the changes decided to keep
DslCallableinterface for first case and introduce aDslAccessibleannotation for second. They have different use-cases, better not to mix them in the same annotation.Will send an email to dev@brooklyn.apache.org discussing the
DslAccessiblechange and implement it in a separate PR.