Skip to content
Merged
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,11 @@
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.javalang.Reflections;
import org.apache.brooklyn.util.guava.Maybe;
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 +155,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 +164,24 @@ 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 );
// TODO Could move argument resolve in DslDeferredFunctionCall freeing each Deffered implementation
// having to handle it separately. The shortcoming is that will lose the eager evaluation we have here.
if (o instanceof BrooklynDslDeferredSupplier && !(o instanceof DslFunctionSource)) {
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).get();
}
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,28 @@
/*
* 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 static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(METHOD)
public @interface DslAccessible {
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to add some javadoc around this to explain what it's for.


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*
* 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.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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 Logger log = LoggerFactory.getLogger(DslDeferredFunctionCall.class);
private static final Set<Method> DEPRECATED_ACCESS_WARNINGS = Collections.newSetFromMap(new ConcurrentHashMap<Method, Boolean>());

private static final long serialVersionUID = 3243262633795112155L;

private Object object;
private String fnName;
private List<?> args;

public DslDeferredFunctionCall(Object o, String fn, List<Object> args) {
this.object = o;
this.fnName = fn;
this.args = args;
}

@Override
public Maybe<Object> getImmediately() {
return invokeOnDeferred(object, true);
}

@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 {
return invokeOnDeferred(object, false).get();
}

}).build();
}

protected Maybe<Object> invokeOnDeferred(Object obj, boolean immediate) {
Maybe<?> resolvedMaybe = resolve(obj, immediate);
if (resolvedMaybe.isPresent()) {
Object instance = resolvedMaybe.get();

if (instance == null) {
throw new IllegalArgumentException("Deferred function call, " + object +
" evaluates to null (when calling " + fnName + "(" + toString(args) + "))");
}

return invokeOn(instance);
} else {
if (immediate) {
return Maybe.absent("Could not evaluate immediately " + obj);
} else {
return Maybe.absent(Maybe.getException(resolvedMaybe));
}
}
}

protected Maybe<Object> invokeOn(Object obj) {
return invokeOn(obj, fnName, args);
}

protected static Maybe<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 Maybe.of(Reflections.invokeMethodFromArgs(instance, m, instanceArgs));
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
// If the method is there but not executable for whatever reason fail with a fatal error, don't return an absent.
throw Exceptions.propagate(new InvocationTargetException(e, "Error invoking '"+fnName+"("+toString(instanceArgs)+")' on '"+instance+"'"));
}
} else {
return Maybe.absent(new IllegalArgumentException("No such function '"+fnName+"("+toString(args)+")' on "+obj));
}
}

protected Maybe<?> resolve(Object object, boolean immediate) {
if (object instanceof DslFunctionSource || object == null) {
return Maybe.of(object);
}

Maybe<?> resultMaybe = Tasks.resolving(object, Object.class)
Copy link
Contributor

Choose a reason for hiding this comment

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

How is this code reachable? This class is only constructed when object is DslCallable in which case the above branch applies, isn't it?

Copy link
Contributor

Choose a reason for hiding this comment

The 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.isPresent()) {
// 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);
}
} else {
return resultMaybe;
}
}

private static void checkCallAllowed(Method m) {
DslAccessible dslAccessible = m.getAnnotation(DslAccessible.class);
boolean isAnnotationAllowed = dslAccessible != null;
if (isAnnotationAllowed) return;

// TODO white-list using brooklyn.properties (at runtime)

Class<?> clazz = m.getDeclaringClass();
Package whiteListPackage = BrooklynDslCommon.class.getPackage();
boolean isPackageAllowed = (clazz.getPackage() != null && // Proxy objects don't have a package
clazz.getPackage().getName().startsWith(whiteListPackage.getName()));
if (isPackageAllowed) {
if (DEPRECATED_ACCESS_WARNINGS.add(m)) {
log.warn("Deprecated since 0.11.0. The method '" + m.toString() + "' called by DSL should be white listed using the " + DslAccessible.class.getSimpleName() + " annotation. Support for DSL callable methods under the " + whiteListPackage + " will be fremoved in a future release.");
Copy link
Contributor

Choose a reason for hiding this comment

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

typo "fremoved". Would be nice to break this line up over several (currently 297 characters wide!)

}
return;
}

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);
}
}
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 DslFunctionSource {

}
Loading