Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,12 @@
import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode;
import org.apache.brooklyn.camp.spi.resolve.interpret.PlanInterpretationNode.Role;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.javalang.Reflections;
import org.apache.brooklyn.util.text.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;

/**
* {@link PlanInterpreter} which understands the $brooklyn DSL
*/
Expand Down Expand Up @@ -157,15 +156,6 @@ public Object evaluateOn(Object o, FunctionWithArgs f, boolean deepEvaluation) {
if (f.getArgs()==null)
throw new IllegalStateException("Invalid function-only expression '"+f.getFunction()+"'");

Class<?> clazz;
if (o instanceof Class) {
clazz = (Class<?>)o;
} else {
clazz = o.getClass();
}
if (!(clazz.getPackage().getName().startsWith(BrooklynDslCommon.class.getPackage().getName())))
throw new IllegalArgumentException("Not permitted to invoke function on '"+clazz+"' (outside allowed package scope)");

String fn = f.getFunction();
fn = Strings.removeFromStart(fn, "$brooklyn:");
if (fn.startsWith("function.")) {
Expand All @@ -175,19 +165,22 @@ public Object evaluateOn(Object o, FunctionWithArgs f, boolean deepEvaluation) {
o = BrooklynDslCommon.Functions.class;
fn = Strings.removeFromStart(fn, "function.");
}
List<Object> args = new ArrayList<>();
for (Object arg: f.getArgs()) {
args.add( deepEvaluation ? evaluate(arg, true) : arg );
}
try {
List<Object> args = new ArrayList<>();
for (Object arg: f.getArgs()) {
args.add( deepEvaluation ? evaluate(arg, true) : arg );
if (o instanceof BrooklynDslDeferredSupplier && !(o instanceof DslCallable)) {
return new DslDeferredFunctionCall((BrooklynDslDeferredSupplier<?>) o, fn, args);
} else {
// Would prefer to keep the invocation logic encapsulated in DslDeferredFunctionCall, but
// for backwards compatibility will evaluate as much as possible eagerly (though it shouldn't matter in theory).
return DslDeferredFunctionCall.invokeOn(o, fn, args);
}
Optional<Object> v = Reflections.invokeMethodWithArgs(o, fn, args);
if (v.isPresent()) return v.get();
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
throw Exceptions.propagate(new InvocationTargetException(e, "Error invoking '"+fn+"' on '"+o+"'"));
throw Exceptions.propagate(new InvocationTargetException(e, "Error invoking '"+fn+"' on '"+o+"' with arguments "+args+""));
}

throw new IllegalArgumentException("No such function '"+fn+"' on "+o);
}

}
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,191 @@
/*
* 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.lang.reflect.Method;
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;
import com.google.common.collect.ImmutableList;

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)
.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) {
Object instance = obj;
List<?> instanceArgs = args;
Maybe<Method> method = Reflections.getMethodFromArgs(instance, fnName, instanceArgs);

if (method.isAbsent()) {
instance = BrooklynDslCommon.class;
instanceArgs = ImmutableList.builder().add(obj).addAll(args).build();
method = Reflections.getMethodFromArgs(instance, fnName, instanceArgs);
}

if (method.isAbsent()) {
Maybe<?> facade;
try {
facade = Reflections.invokeMethodFromArgs(BrooklynDslCommon.DslFacades.class, "wrap", ImmutableList.of(obj));
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
facade = Maybe.absent();
}

if (facade.isPresent()) {
instance = facade.get();
instanceArgs = args;
method = Reflections.getMethodFromArgs(instance, fnName, instanceArgs);
}
}

if (method.isPresent()) {
Method m = method.get();

checkCallAllowed(m);

try {
// Value is most likely another BrooklynDslDeferredSupplier - let the caller handle it,
return Reflections.invokeMethodFromArgs(instance, m, instanceArgs);
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
throw Exceptions.propagate(new InvocationTargetException(e, "Error invoking '"+fnName+"("+toString(instanceArgs)+")' on '"+instance+"'"));
}
} else {
throw new IllegalArgumentException("No such function '"+fnName+"("+toString(args)+")' on "+obj);
}
}

private static void checkCallAllowed(Method m) {
Class<?> clazz = m.getDeclaringClass();
if (clazz.getPackage() == null || // Proxy objects don't have a package
!(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);
}
}
Loading