http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/effector/core/Effectors.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/effector/core/Effectors.java b/core/src/main/java/org/apache/brooklyn/effector/core/Effectors.java deleted file mode 100644 index 7f7bed4..0000000 --- a/core/src/main/java/org/apache/brooklyn/effector/core/Effectors.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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 org.apache.brooklyn.effector.core; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nullable; - -import org.apache.brooklyn.api.effector.Effector; -import org.apache.brooklyn.api.effector.ParameterType; -import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.entity.EntityLocal; -import org.apache.brooklyn.api.mgmt.TaskAdaptable; -import org.apache.brooklyn.config.ConfigKey; -import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.core.entity.Entities; -import org.apache.brooklyn.core.entity.EntityInternal; -import org.apache.brooklyn.effector.core.EffectorTasks.EffectorBodyTaskFactory; -import org.apache.brooklyn.effector.core.EffectorTasks.EffectorMarkingTaskFactory; -import org.apache.brooklyn.effector.core.EffectorTasks.EffectorTaskFactory; -import org.apache.brooklyn.util.collections.MutableMap; -import org.apache.brooklyn.util.core.config.ConfigBag; -import org.apache.brooklyn.util.core.task.Tasks; -import org.apache.brooklyn.util.text.Strings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Objects; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; - -public class Effectors { - - private static final Logger log = LoggerFactory.getLogger(Effectors.class); - - public static class EffectorBuilder<T> { - private Class<T> returnType; - private String effectorName; - private String description; - private Map<String,ParameterType<?>> parameters = new LinkedHashMap<String,ParameterType<?>>(); - private EffectorTaskFactory<T> impl; - - private EffectorBuilder(Class<T> returnType, String effectorName) { - this.returnType = returnType; - this.effectorName = effectorName; - } - public EffectorBuilder<T> description(String description) { - this.description = description; - return this; - } - public EffectorBuilder<T> parameter(Class<?> paramType, String paramName) { - return parameter(paramType, paramName, null, null); - } - public EffectorBuilder<T> parameter(Class<?> paramType, String paramName, String paramDescription) { - return parameter(paramType, paramName, paramDescription, null); - } - public <V> EffectorBuilder<T> parameter(Class<V> paramType, String paramName, String paramDescription, V defaultValue) { - return parameter(new BasicParameterType<V>(paramName, paramType, paramDescription, defaultValue)); - } - public <V> EffectorBuilder<T> parameter(ConfigKey<V> key) { - return parameter(asParameterType(key)); - } - public EffectorBuilder<T> parameter(ParameterType<?> p) { - // allow redeclaring, e.g. for the case where we are overriding an existing effector - parameters.put(p.getName(), p); - return this; - } - public EffectorBuilder<T> impl(EffectorTaskFactory<T> taskFactory) { - this.impl = new EffectorMarkingTaskFactory<T>(taskFactory); - return this; - } - public EffectorBuilder<T> impl(EffectorBody<T> effectorBody) { - this.impl = new EffectorBodyTaskFactory<T>(effectorBody); - return this; - } - /** returns the effector, with an implementation (required); @see {@link #buildAbstract()} */ - public Effector<T> build() { - Preconditions.checkNotNull(impl, "Cannot create effector %s with no impl (did you forget impl? or did you mean to buildAbstract?)", effectorName); - return new EffectorAndBody<T>(effectorName, returnType, ImmutableList.copyOf(parameters.values()), description, impl); - } - - /** returns an abstract effector, where the body will be defined later/elsewhere - * (impl must not be set) */ - public Effector<T> buildAbstract() { - Preconditions.checkArgument(impl==null, "Cannot create abstract effector {} as an impl is defined", effectorName); - return new EffectorBase<T>(effectorName, returnType, ImmutableList.copyOf(parameters.values()), description); - } - } - - /** creates a new effector builder with the given name and return type */ - public static <T> EffectorBuilder<T> effector(Class<T> returnType, String effectorName) { - return new EffectorBuilder<T>(returnType, effectorName); - } - - /** creates a new effector builder to _override_ the given effector */ - public static <T> EffectorBuilder<T> effector(Effector<T> base) { - EffectorBuilder<T> builder = new EffectorBuilder<T>(base.getReturnType(), base.getName()); - for (ParameterType<?> p: base.getParameters()) - builder.parameter(p); - builder.description(base.getDescription()); - if (base instanceof EffectorWithBody) - builder.impl(((EffectorWithBody<T>) base).getBody()); - return builder; - } - - /** as {@link #invocation(Entity, Effector, Map)} but convenience for passing a {@link ConfigBag} */ - public static <T> TaskAdaptable<T> invocation(Entity entity, Effector<T> eff, ConfigBag parameters) { - return invocation(entity, eff, parameters==null ? ImmutableMap.of() : parameters.getAllConfig()); - } - - /** returns an unsubmitted task which invokes the given effector; use {@link Entities#invokeEffector(EntityLocal, Entity, Effector, Map)} for a submitted variant */ - public static <T> TaskAdaptable<T> invocation(Entity entity, Effector<T> eff, @Nullable Map<?,?> parameters) { - @SuppressWarnings("unchecked") - Effector<T> eff2 = (Effector<T>) ((EntityInternal)entity).getEffector(eff.getName()); - if (log.isTraceEnabled()) { - Object eff1Body = (eff instanceof EffectorWithBody<?> ? ((EffectorWithBody<?>) eff).getBody() : "bodyless"); - String message = String.format("Invoking %s/%s on entity %s", eff, eff1Body, entity); - if (eff != eff2) { - Object eff2Body = (eff2 instanceof EffectorWithBody<?> ? ((EffectorWithBody<?>) eff2).getBody() : "bodyless"); - message += String.format(" (actually %s/%s)", eff2, eff2Body); - } - log.trace(message); - } - if (eff2 != null) { - if (eff2 != eff) { - if (eff2 instanceof EffectorWithBody) { - log.debug("Replacing invocation of {} on {} with {} which is the impl defined at that entity", new Object[] { eff, entity, eff2 }); - return ((EffectorWithBody<T>)eff2).getBody().newTask(entity, eff2, ConfigBag.newInstance().putAll(parameters)); - } else { - log.warn("Effector {} defined on {} has no body; invoking caller-supplied {} instead", new Object[] { eff2, entity, eff }); - } - } - } else { - log.debug("Effector {} does not exist on {}; attempting to invoke anyway", new Object[] { eff, entity }); - } - - if (eff instanceof EffectorWithBody) { - return ((EffectorWithBody<T>)eff).getBody().newTask(entity, eff, ConfigBag.newInstance().putAll(parameters)); - } - - throw new UnsupportedOperationException("No implementation registered for effector "+eff+" on "+entity); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static <V> ParameterType<V> asParameterType(ConfigKey<V> key) { - return key.hasDefaultValue() - ? new BasicParameterType<V>(key.getName(), (Class)key.getType(), key.getDescription(), key.getDefaultValue()) - : new BasicParameterType<V>(key.getName(), (Class)key.getType(), key.getDescription()); - } - - public static <V> ConfigKey<V> asConfigKey(ParameterType<V> paramType) { - return ConfigKeys.newConfigKey(paramType.getParameterClass(), paramType.getName(), paramType.getDescription(), paramType.getDefaultValue()); - } - - /** returns an unsubmitted task which will invoke the given effector on the given entities; - * return type is Task<List<T>> (but haven't put in the blood sweat toil and tears to make the generics work) */ - public static TaskAdaptable<List<?>> invocation(Effector<?> eff, Map<?,?> params, Iterable<? extends Entity> entities) { - List<TaskAdaptable<?>> tasks = new ArrayList<TaskAdaptable<?>>(); - for (Entity e: entities) tasks.add(invocation(e, eff, params)); - return Tasks.parallel("invoking "+eff+" on "+tasks.size()+" node"+(Strings.s(tasks.size())), tasks.toArray(new TaskAdaptable[tasks.size()])); - } - - /** returns an unsubmitted task which will invoke the given effector on the given entities - * (this form of method is a convenience for {@link #invocation(Effector, Map, Iterable)}) */ - public static TaskAdaptable<List<?>> invocation(Effector<?> eff, MutableMap<?, ?> params, Entity ...entities) { - return invocation(eff, params, Arrays.asList(entities)); - } - - public static boolean sameSignature(Effector<?> e1, Effector<?> e2) { - return Objects.equal(e1.getName(), e2.getName()) && - Objects.equal(e1.getParameters(), e2.getParameters()) && - Objects.equal(e1.getReturnType(), e2.getReturnType()); - } - - // TODO sameSignatureAndBody - - public static boolean sameInstance(Effector<?> e1, Effector<?> e2) { - return e1 == e2; - } - -}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/effector/core/ExplicitEffector.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/effector/core/ExplicitEffector.java b/core/src/main/java/org/apache/brooklyn/effector/core/ExplicitEffector.java deleted file mode 100644 index 46b19ea..0000000 --- a/core/src/main/java/org/apache/brooklyn/effector/core/ExplicitEffector.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 org.apache.brooklyn.effector.core; - -import groovy.lang.Closure; - -import java.util.List; -import java.util.Map; - -import org.apache.brooklyn.api.effector.ParameterType; -import org.apache.brooklyn.api.entity.Entity; - -import com.google.common.base.Objects; -import com.google.common.collect.ImmutableList; - -public abstract class ExplicitEffector<I,T> extends AbstractEffector<T> { - public ExplicitEffector(String name, Class<T> type, String description) { - this(name, type, ImmutableList.<ParameterType<?>>of(), description); - } - public ExplicitEffector(String name, Class<T> type, List<ParameterType<?>> parameters, String description) { - super(name, type, parameters, description); - } - - public T call(Entity entity, Map parameters) { - return invokeEffector((I) entity, (Map<String,?>)parameters ); - } - - public abstract T invokeEffector(I trait, Map<String,?> parameters); - - /** convenience to create an effector supplying a closure; annotations are preferred, - * and subclass here would be failback, but this is offered as - * workaround for bug GROOVY-5122, as discussed in test class CanSayHi - */ - public static <I,T> ExplicitEffector<I,T> create(String name, Class<T> type, List<ParameterType<?>> parameters, String description, Closure body) { - return new ExplicitEffectorFromClosure<I,T>(name, type, parameters, description, body); - } - - private static class ExplicitEffectorFromClosure<I,T> extends ExplicitEffector<I,T> { - private static final long serialVersionUID = -5771188171702382236L; - final Closure<T> body; - public ExplicitEffectorFromClosure(String name, Class<T> type, List<ParameterType<?>> parameters, String description, Closure<T> body) { - super(name, type, parameters, description); - this.body = body; - } - public T invokeEffector(I trait, Map<String,?> parameters) { return body.call(trait, parameters); } - - @Override - public int hashCode() { - return Objects.hashCode(super.hashCode(), body); - } - - @Override - public boolean equals(Object other) { - return super.equals(other) && Objects.equal(body, ((ExplicitEffectorFromClosure<?,?>)other).body); - } - - } -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/effector/core/MethodEffector.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/effector/core/MethodEffector.java b/core/src/main/java/org/apache/brooklyn/effector/core/MethodEffector.java deleted file mode 100644 index 7dd0828..0000000 --- a/core/src/main/java/org/apache/brooklyn/effector/core/MethodEffector.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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 org.apache.brooklyn.effector.core; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.apache.brooklyn.api.effector.Effector; -import org.apache.brooklyn.api.effector.ParameterType; -import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.core.annotation.EffectorParam; -import org.apache.brooklyn.core.entity.AbstractEntity; -import org.apache.brooklyn.core.mgmt.internal.EffectorUtils; -import org.apache.brooklyn.util.core.flags.TypeCoercions; -import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.groovy.GroovyJavaMethods; -import org.codehaus.groovy.runtime.MethodClosure; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.Lists; - -/** concrete class for providing an Effector implementation that gets its information from annotations on a method; - * see Effector*Test for usage example. - * <p> - * note that the method must be on an interface in order for it to be remoted, with the current implementation. - * see comments in {@link #call(Entity, Map)} for more details. - */ -public class MethodEffector<T> extends AbstractEffector<T> { - - private static final long serialVersionUID = 6989688364011965968L; - private static final Logger log = LoggerFactory.getLogger(MethodEffector.class); - - @SuppressWarnings("rawtypes") - public static Effector<?> create(Method m) { - return new MethodEffector(m); - } - - protected static class AnnotationsOnMethod { - final Class<?> clazz; - final String name; - final String description; - final Class<?> returnType; - final List<ParameterType<?>> parameters; - - public AnnotationsOnMethod(Class<?> clazz, String methodName) { - this(clazz, inferBestMethod(clazz, methodName)); - } - - public AnnotationsOnMethod(Class<?> clazz, Method method) { - this.clazz = clazz; - this.name = method.getName(); - this.returnType = method.getReturnType(); - - // Get the description - org.apache.brooklyn.core.annotation.Effector effectorAnnotation = method.getAnnotation(org.apache.brooklyn.core.annotation.Effector.class); - description = (effectorAnnotation != null) ? effectorAnnotation.description() : null; - - // Get the parameters - parameters = Lists.newArrayList(); - int numParameters = method.getParameterTypes().length; - for (int i = 0; i < numParameters; i++) { - parameters.add(toParameterType(method, i)); - } - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - protected static ParameterType<?> toParameterType(Method method, int paramIndex) { - Annotation[] anns = method.getParameterAnnotations()[paramIndex]; - Class<?> type = method.getParameterTypes()[paramIndex]; - EffectorParam paramAnnotation = findAnnotation(anns, EffectorParam.class); - - // TODO if blank, could do "param"+(i+1); would that be better? - // TODO this will now give "" if name is blank, rather than previously null. Is that ok?! - String name = (paramAnnotation != null) ? paramAnnotation.name() : null; - - String paramDescription = (paramAnnotation == null || EffectorParam.MAGIC_STRING_MEANING_NULL.equals(paramAnnotation.description())) ? null : paramAnnotation.description(); - String description = (paramDescription != null) ? paramDescription : null; - - String paramDefaultValue = (paramAnnotation == null || EffectorParam.MAGIC_STRING_MEANING_NULL.equals(paramAnnotation.defaultValue())) ? null : paramAnnotation.defaultValue(); - Object defaultValue = (paramDefaultValue != null) ? TypeCoercions.coerce(paramDefaultValue, type) : null; - - return new BasicParameterType(name, type, description, defaultValue); - } - - @SuppressWarnings("unchecked") - protected static <T extends Annotation> T findAnnotation(Annotation[] anns, Class<T> type) { - for (Annotation ann : anns) { - if (type.isInstance(ann)) return (T) ann; - } - return null; - } - - protected static Method inferBestMethod(Class<?> clazz, String methodName) { - Method best = null; - for (Method it : clazz.getMethods()) { - if (it.getName().equals(methodName)) { - if (best==null || best.getParameterTypes().length < it.getParameterTypes().length) best=it; - } - } - if (best==null) { - throw new IllegalStateException("Cannot find method "+methodName+" on "+clazz.getCanonicalName()); - } - return best; - } - } - - /** Defines a new effector whose details are supplied as annotations on the given type and method name */ - public MethodEffector(Class<?> whereEffectorDefined, String methodName) { - this(new AnnotationsOnMethod(whereEffectorDefined, methodName), null); - } - - public MethodEffector(Method method) { - this(new AnnotationsOnMethod(method.getDeclaringClass(), method), null); - } - - public MethodEffector(MethodClosure mc) { - this(new AnnotationsOnMethod((Class<?>)mc.getDelegate(), mc.getMethod()), null); - } - - @SuppressWarnings("unchecked") - protected MethodEffector(AnnotationsOnMethod anns, String description) { - super(anns.name, (Class<T>)anns.returnType, anns.parameters, GroovyJavaMethods.<String>elvis(description, anns.description)); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public T call(Entity entity, Map parameters) { - Object[] parametersArray = EffectorUtils.prepareArgsForEffector(this, parameters); - if (entity instanceof AbstractEntity) { - return EffectorUtils.invokeMethodEffector(entity, this, parametersArray); - } else { - // we are dealing with a proxy here - // this implementation invokes the method on the proxy - // (requiring it to be on the interface) - // and letting the proxy deal with the remoting / runAtEntity; - // alternatively we could create the task here and pass it to runAtEntity; - // the latter may allow us to simplify/remove a lot of the stuff from - // EffectorUtils and possibly Effectors and Entities - - // TODO Should really find method with right signature, rather than just the right args. - // TODO prepareArgs can miss things out that have "default values"! Code below will probably fail if that happens. - Method[] methods = entity.getClass().getMethods(); - for (Method method : methods) { - if (method.getName().equals(getName())) { - if (parametersArray.length == method.getParameterTypes().length) { - try { - return (T) method.invoke(entity, parametersArray); - } catch (Exception e) { - // exception handled by the proxy invocation (which leads to EffectorUtils.invokeEffectorMethod...) - throw Exceptions.propagate(e); - } - } - } - } - String msg = "Could not find method for effector "+getName()+" with "+parametersArray.length+" parameters on "+entity; - log.warn(msg+" (throwing); available methods are: "+Arrays.toString(methods)); - throw new IllegalStateException(msg); - } - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/effector/core/ssh/SshEffectorTasks.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/effector/core/ssh/SshEffectorTasks.java b/core/src/main/java/org/apache/brooklyn/effector/core/ssh/SshEffectorTasks.java deleted file mode 100644 index b904ba7..0000000 --- a/core/src/main/java/org/apache/brooklyn/effector/core/ssh/SshEffectorTasks.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * 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 org.apache.brooklyn.effector.core.ssh; - -import java.util.List; -import java.util.Map; - -import javax.annotation.Nullable; - -import org.apache.brooklyn.api.effector.Effector; -import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.api.location.Location; -import org.apache.brooklyn.api.mgmt.Task; -import org.apache.brooklyn.config.ConfigKey; -import org.apache.brooklyn.config.StringConfigMap; -import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.core.config.ConfigUtils; -import org.apache.brooklyn.core.entity.EntityInternal; -import org.apache.brooklyn.core.location.internal.LocationInternal; -import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; -import org.apache.brooklyn.effector.core.EffectorBody; -import org.apache.brooklyn.effector.core.EffectorTasks; -import org.apache.brooklyn.effector.core.EffectorTasks.EffectorTaskFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.brooklyn.location.ssh.SshMachineLocation; -import org.apache.brooklyn.util.core.config.ConfigBag; -import org.apache.brooklyn.util.core.internal.ssh.SshTool; -import org.apache.brooklyn.util.core.task.Tasks; -import org.apache.brooklyn.util.core.task.ssh.SshFetchTaskFactory; -import org.apache.brooklyn.util.core.task.ssh.SshFetchTaskWrapper; -import org.apache.brooklyn.util.core.task.ssh.SshPutTaskFactory; -import org.apache.brooklyn.util.core.task.ssh.SshPutTaskWrapper; -import org.apache.brooklyn.util.core.task.ssh.SshTasks; -import org.apache.brooklyn.util.core.task.ssh.internal.AbstractSshExecTaskFactory; -import org.apache.brooklyn.util.core.task.ssh.internal.PlainSshExecTaskFactory; -import org.apache.brooklyn.util.core.task.system.ProcessTaskFactory; -import org.apache.brooklyn.util.core.task.system.ProcessTaskWrapper; -import org.apache.brooklyn.util.ssh.BashCommands; - -import com.google.common.annotations.Beta; -import com.google.common.base.Function; -import com.google.common.collect.Maps; - -/** - * Conveniences for generating {@link Task} instances to perform SSH activities. - * <p> - * If the {@link SshMachineLocation machine} is not specified directly it - * will be inferred from the {@link Entity} context of either the {@link Effector} - * or the current {@link Task}. - * - * @see SshTasks - * @since 0.6.0 - */ -@Beta -public class SshEffectorTasks { - - private static final Logger log = LoggerFactory.getLogger(SshEffectorTasks.class); - - public static final ConfigKey<Boolean> IGNORE_ENTITY_SSH_FLAGS = ConfigKeys.newBooleanConfigKey("ignoreEntitySshFlags", - "Whether to ignore any ssh flags (behaviour constraints) set on the entity or location " + - "where this is running, using only flags explicitly specified", false); - - /** - * Like {@link EffectorBody} but providing conveniences when in an entity with a single machine location. - */ - public abstract static class SshEffectorBody<T> extends EffectorBody<T> { - - /** convenience for accessing the machine */ - public SshMachineLocation machine() { - return EffectorTasks.getSshMachine(entity()); - } - - /** convenience for generating an {@link PlainSshExecTaskFactory} which can be further customised if desired, and then (it must be explicitly) queued */ - public ProcessTaskFactory<Integer> ssh(String ...commands) { - return new SshEffectorTaskFactory<Integer>(commands).machine(machine()); - } - } - - /** variant of {@link PlainSshExecTaskFactory} which fulfills the {@link EffectorTaskFactory} signature so can be used directly as an impl for an effector, - * also injects the machine automatically; can also be used outwith effector contexts, and machine is still injected if it is - * run from inside a task at an entity with a single SshMachineLocation */ - public static class SshEffectorTaskFactory<RET> extends AbstractSshExecTaskFactory<SshEffectorTaskFactory<RET>,RET> implements EffectorTaskFactory<RET> { - - public SshEffectorTaskFactory(String ...commands) { - super(commands); - } - public SshEffectorTaskFactory(SshMachineLocation machine, String ...commands) { - super(machine, commands); - } - @Override - public ProcessTaskWrapper<RET> newTask(Entity entity, Effector<RET> effector, ConfigBag parameters) { - markDirty(); - if (summary==null) summary(effector.getName()+" (ssh)"); - machine(EffectorTasks.getSshMachine(entity)); - return newTask(); - } - @Override - public synchronized ProcessTaskWrapper<RET> newTask() { - Entity entity = BrooklynTaskTags.getTargetOrContextEntity(Tasks.current()); - if (machine==null) { - if (log.isDebugEnabled()) - log.debug("Using an ssh task not in an effector without any machine; will attempt to infer the machine: "+this); - if (entity!=null) - machine(EffectorTasks.getSshMachine(entity)); - } - applySshFlags(getConfig(), entity, getMachine()); - return super.newTask(); - } - - @Override - public <T2> SshEffectorTaskFactory<T2> returning(ScriptReturnType type) { - return (SshEffectorTaskFactory<T2>) super.<T2>returning(type); - } - - @Override - public SshEffectorTaskFactory<Boolean> returningIsExitCodeZero() { - return (SshEffectorTaskFactory<Boolean>) super.returningIsExitCodeZero(); - } - - public SshEffectorTaskFactory<String> requiringZeroAndReturningStdout() { - return (SshEffectorTaskFactory<String>) super.requiringZeroAndReturningStdout(); - } - - public <RET2> SshEffectorTaskFactory<RET2> returning(Function<ProcessTaskWrapper<?>, RET2> resultTransformation) { - return (SshEffectorTaskFactory<RET2>) super.returning(resultTransformation); - } - } - - public static class SshPutEffectorTaskFactory extends SshPutTaskFactory implements EffectorTaskFactory<Void> { - public SshPutEffectorTaskFactory(String remoteFile) { - super(remoteFile); - } - public SshPutEffectorTaskFactory(SshMachineLocation machine, String remoteFile) { - super(machine, remoteFile); - } - @Override - public SshPutTaskWrapper newTask(Entity entity, Effector<Void> effector, ConfigBag parameters) { - machine(EffectorTasks.getSshMachine(entity)); - applySshFlags(getConfig(), entity, getMachine()); - return super.newTask(); - } - @Override - public SshPutTaskWrapper newTask() { - Entity entity = BrooklynTaskTags.getTargetOrContextEntity(Tasks.current()); - if (machine==null) { - if (log.isDebugEnabled()) - log.debug("Using an ssh put task not in an effector without any machine; will attempt to infer the machine: "+this); - if (entity!=null) { - machine(EffectorTasks.getSshMachine(entity)); - } - - } - applySshFlags(getConfig(), entity, getMachine()); - return super.newTask(); - } - } - - public static class SshFetchEffectorTaskFactory extends SshFetchTaskFactory implements EffectorTaskFactory<String> { - public SshFetchEffectorTaskFactory(String remoteFile) { - super(remoteFile); - } - public SshFetchEffectorTaskFactory(SshMachineLocation machine, String remoteFile) { - super(machine, remoteFile); - } - @Override - public SshFetchTaskWrapper newTask(Entity entity, Effector<String> effector, ConfigBag parameters) { - machine(EffectorTasks.getSshMachine(entity)); - applySshFlags(getConfig(), entity, getMachine()); - return super.newTask(); - } - @Override - public SshFetchTaskWrapper newTask() { - Entity entity = BrooklynTaskTags.getTargetOrContextEntity(Tasks.current()); - if (machine==null) { - if (log.isDebugEnabled()) - log.debug("Using an ssh fetch task not in an effector without any machine; will attempt to infer the machine: "+this); - if (entity!=null) - machine(EffectorTasks.getSshMachine(entity)); - } - applySshFlags(getConfig(), entity, getMachine()); - return super.newTask(); - } - } - - public static SshEffectorTaskFactory<Integer> ssh(String ...commands) { - return new SshEffectorTaskFactory<Integer>(commands); - } - - public static SshEffectorTaskFactory<Integer> ssh(List<String> commands) { - return ssh(commands.toArray(new String[commands.size()])); - } - - public static SshPutTaskFactory put(String remoteFile) { - return new SshPutEffectorTaskFactory(remoteFile); - } - - public static SshFetchEffectorTaskFactory fetch(String remoteFile) { - return new SshFetchEffectorTaskFactory(remoteFile); - } - - /** task which returns 0 if pid is running */ - public static SshEffectorTaskFactory<Integer> codePidRunning(Integer pid) { - return ssh("ps -p "+pid).summary("PID "+pid+" is-running check (exit code)").allowingNonZeroExitCode(); - } - - /** task which fails if the given PID is not running */ - public static SshEffectorTaskFactory<?> requirePidRunning(Integer pid) { - return codePidRunning(pid).summary("PID "+pid+" is-running check (required)").requiringExitCodeZero("Process with PID "+pid+" is required to be running"); - } - - /** as {@link #codePidRunning(Integer)} but returning boolean */ - public static SshEffectorTaskFactory<Boolean> isPidRunning(Integer pid) { - return codePidRunning(pid).summary("PID "+pid+" is-running check (boolean)").returning(new Function<ProcessTaskWrapper<?>, Boolean>() { - public Boolean apply(@Nullable ProcessTaskWrapper<?> input) { return Integer.valueOf(0).equals(input.getExitCode()); } - }); - } - - - /** task which returns 0 if pid in the given file is running; - * method accepts wildcards so long as they match a single file on the remote end - * <p> - * returns 1 if no matching file, - * 1 if matching file but no matching process, - * and 2 if 2+ matching files */ - public static SshEffectorTaskFactory<Integer> codePidFromFileRunning(final String pidFile) { - return ssh(BashCommands.chain( - // this fails, but isn't an error - BashCommands.requireTest("-f "+pidFile, "The PID file "+pidFile+" does not exist."), - // this fails and logs an error picked up later - BashCommands.requireTest("`ls "+pidFile+" | wc -w` -eq 1", "ERROR: there are multiple matching PID files"), - // this fails and logs an error picked up later - BashCommands.require("cat "+pidFile, "ERROR: the PID file "+pidFile+" cannot be read (permissions?)."), - // finally check the process - "ps -p `cat "+pidFile+"`")).summary("PID file "+pidFile+" is-running check (exit code)") - .allowingNonZeroExitCode() - .addCompletionListener(new Function<ProcessTaskWrapper<?>,Void>() { - public Void apply(ProcessTaskWrapper<?> input) { - if (input.getStderr().contains("ERROR:")) - throw new IllegalStateException("Invalid or inaccessible PID filespec: "+pidFile); - return null; - } - }); - } - - /** task which fails if the pid in the given file is not running (or if there is no such PID file); - * method accepts wildcards so long as they match a single file on the remote end (fails if 0 or 2+ matching files) */ - public static SshEffectorTaskFactory<?> requirePidFromFileRunning(String pidFile) { - return codePidFromFileRunning(pidFile) - .summary("PID file "+pidFile+" is-running check (required)") - .requiringExitCodeZero("Process with PID from file "+pidFile+" is required to be running"); - } - - /** as {@link #codePidFromFileRunning(String)} but returning boolean */ - public static SshEffectorTaskFactory<Boolean> isPidFromFileRunning(String pidFile) { - return codePidFromFileRunning(pidFile).summary("PID file "+pidFile+" is-running check (boolean)"). - returning(new Function<ProcessTaskWrapper<?>, Boolean>() { - public Boolean apply(@Nullable ProcessTaskWrapper<?> input) { return ((Integer)0).equals(input.getExitCode()); } - }); - } - - /** extracts the values for the main brooklyn.ssh.config.* config keys (i.e. those declared in ConfigKeys) - * as declared on the entity, and inserts them in a map using the unprefixed state, for ssh. - * <p> - * currently this is computed for each call, which may be wasteful, but it is reliable in the face of config changes. - * we could cache the Map. note that we do _not_ cache (or even own) the SshTool; - * the SshTool is created or re-used by the SshMachineLocation making use of these properties */ - @Beta - public static Map<String, Object> getSshFlags(Entity entity, Location optionalLocation) { - ConfigBag allConfig = ConfigBag.newInstance(); - - StringConfigMap globalConfig = ((EntityInternal)entity).getManagementContext().getConfig(); - allConfig.putAll(globalConfig.getAllConfig()); - - if (optionalLocation!=null) - allConfig.putAll(((LocationInternal)optionalLocation).config().getBag()); - - allConfig.putAll(((EntityInternal)entity).getAllConfig()); - - Map<String, Object> result = Maps.newLinkedHashMap(); - for (String keyS : allConfig.getAllConfig().keySet()) { - if (keyS.startsWith(SshTool.BROOKLYN_CONFIG_KEY_PREFIX)) { - ConfigKey<?> key = ConfigKeys.newConfigKey(Object.class, keyS); - - Object val = allConfig.getStringKey(keyS); - - /* - * NOV 2013 changing this to rely on config above being inserted in the right order, - * so entity config will be preferred over location, and location over global. - * If that is consistent then remove the lines below. - * (We can also accept null entity and so combine with SshTasks.getSshFlags.) - */ - -// // have to use raw config to test whether the config is set -// Object val = ((EntityInternal)entity).getConfigMap().getRawConfig(key); -// if (val!=null) { -// val = entity.getConfig(key); -// } else { -// val = globalConfig.getRawConfig(key); -// if (val!=null) val = globalConfig.getConfig(key); -// } -// if (val!=null) { - result.put(ConfigUtils.unprefixedKey(SshTool.BROOKLYN_CONFIG_KEY_PREFIX, key).getName(), val); -// } - } - } - return result; - } - - private static void applySshFlags(ConfigBag config, Entity entity, Location machine) { - if (entity!=null) { - if (!config.get(IGNORE_ENTITY_SSH_FLAGS)) { - config.putIfAbsent(getSshFlags(entity, machine)); - } - } - } - -} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java index 1b16369..bf39663 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicCluster.java @@ -33,11 +33,11 @@ import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.annotation.Effector; import org.apache.brooklyn.core.annotation.EffectorParam; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.MethodEffector; import org.apache.brooklyn.core.entity.Attributes; import org.apache.brooklyn.core.entity.factory.EntityFactory; import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; import org.apache.brooklyn.core.entity.trait.MemberReplaceable; -import org.apache.brooklyn.effector.core.MethodEffector; import org.apache.brooklyn.entity.group.zoneaware.BalancingNodePlacementStrategy; import org.apache.brooklyn.entity.group.zoneaware.ProportionalZoneFailureDetector; import org.apache.brooklyn.sensor.core.BasicAttributeSensor; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java index 7a0c31b..a8c63ef 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicClusterImpl.java @@ -39,6 +39,7 @@ import org.apache.brooklyn.api.location.MachineProvisioningLocation; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.policy.Policy; import org.apache.brooklyn.core.config.render.RendererHints; +import org.apache.brooklyn.core.effector.Effectors; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.factory.EntityFactory; import org.apache.brooklyn.core.entity.factory.EntityFactoryForLocation; @@ -50,7 +51,6 @@ import org.apache.brooklyn.core.entity.trait.Startable; import org.apache.brooklyn.core.entity.trait.StartableMethods; import org.apache.brooklyn.core.location.Locations; import org.apache.brooklyn.core.location.cloud.AvailabilityZoneExtension; -import org.apache.brooklyn.effector.core.Effectors; import org.apache.brooklyn.entity.stock.DelegateEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabricImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabricImpl.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabricImpl.java index 7611ba8..daedc39 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabricImpl.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicFabricImpl.java @@ -32,6 +32,7 @@ import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.core.effector.Effectors; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.entity.factory.EntityFactory; @@ -40,7 +41,6 @@ import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic; import org.apache.brooklyn.core.entity.trait.Changeable; import org.apache.brooklyn.core.entity.trait.Startable; -import org.apache.brooklyn.effector.core.Effectors; import org.apache.brooklyn.sensor.enricher.Enrichers; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.exceptions.Exceptions; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicGroup.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicGroup.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicGroup.java index 36ac0f9..bb43d3b 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicGroup.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicGroup.java @@ -28,8 +28,8 @@ import org.apache.brooklyn.api.sensor.SensorEvent; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.annotation.Effector; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.MethodEffector; import org.apache.brooklyn.core.entity.trait.Startable; -import org.apache.brooklyn.effector.core.MethodEffector; import org.apache.brooklyn.sensor.core.Sensors; import org.apache.brooklyn.util.core.flags.SetFromFlag; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicRegionsFabric.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicRegionsFabric.java b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicRegionsFabric.java index fc11e9b..7c46d14 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/DynamicRegionsFabric.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/DynamicRegionsFabric.java @@ -21,7 +21,7 @@ package org.apache.brooklyn.entity.group; import org.apache.brooklyn.api.entity.ImplementedBy; import org.apache.brooklyn.core.annotation.Effector; import org.apache.brooklyn.core.annotation.EffectorParam; -import org.apache.brooklyn.effector.core.MethodEffector; +import org.apache.brooklyn.core.effector.MethodEffector; @ImplementedBy(DynamicRegionsFabricImpl.class) public interface DynamicRegionsFabric extends DynamicFabric { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/entity/group/QuarantineGroupImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/entity/group/QuarantineGroupImpl.java b/core/src/main/java/org/apache/brooklyn/entity/group/QuarantineGroupImpl.java index dfddf5f..886862a 100644 --- a/core/src/main/java/org/apache/brooklyn/entity/group/QuarantineGroupImpl.java +++ b/core/src/main/java/org/apache/brooklyn/entity/group/QuarantineGroupImpl.java @@ -23,10 +23,10 @@ import java.util.Set; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.core.effector.Effectors; import org.apache.brooklyn.core.entity.AbstractEntity; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.trait.Startable; -import org.apache.brooklyn.effector.core.Effectors; import org.apache.brooklyn.util.core.task.DynamicTasks; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/sensor/core/HttpRequestSensor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/sensor/core/HttpRequestSensor.java b/core/src/main/java/org/apache/brooklyn/sensor/core/HttpRequestSensor.java index 7a5fecf..6b9c780 100644 --- a/core/src/main/java/org/apache/brooklyn/sensor/core/HttpRequestSensor.java +++ b/core/src/main/java/org/apache/brooklyn/sensor/core/HttpRequestSensor.java @@ -25,7 +25,7 @@ import net.minidev.json.JSONObject; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.effector.core.AddSensor; +import org.apache.brooklyn.core.effector.AddSensor; import org.apache.brooklyn.sensor.feed.http.HttpFeed; import org.apache.brooklyn.sensor.feed.http.HttpPollConfig; import org.apache.brooklyn.sensor.feed.http.HttpValueFunctions; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/sensor/core/StaticSensor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/sensor/core/StaticSensor.java b/core/src/main/java/org/apache/brooklyn/sensor/core/StaticSensor.java index 7bc10bf..0c3a00f 100644 --- a/core/src/main/java/org/apache/brooklyn/sensor/core/StaticSensor.java +++ b/core/src/main/java/org/apache/brooklyn/sensor/core/StaticSensor.java @@ -22,7 +22,7 @@ import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; -import org.apache.brooklyn.effector.core.AddSensor; +import org.apache.brooklyn.core.effector.AddSensor; import org.apache.brooklyn.sensor.enricher.Propagator; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.task.Tasks; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/sensor/feed/windows/WindowsPerformanceCounterFeed.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/sensor/feed/windows/WindowsPerformanceCounterFeed.java b/core/src/main/java/org/apache/brooklyn/sensor/feed/windows/WindowsPerformanceCounterFeed.java index 33b284c..95aba9f 100644 --- a/core/src/main/java/org/apache/brooklyn/sensor/feed/windows/WindowsPerformanceCounterFeed.java +++ b/core/src/main/java/org/apache/brooklyn/sensor/feed/windows/WindowsPerformanceCounterFeed.java @@ -40,8 +40,8 @@ import org.apache.brooklyn.api.mgmt.ExecutionContext; import org.apache.brooklyn.api.sensor.AttributeSensor; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.effector.EffectorTasks; import org.apache.brooklyn.core.entity.EntityInternal; -import org.apache.brooklyn.effector.core.EffectorTasks; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.brooklyn.location.winrm.WinRmMachineLocation; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java index 10eea13..448cd61 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ssh/SshTasks.java @@ -32,10 +32,10 @@ import org.apache.brooklyn.api.mgmt.TaskQueueingContext; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.ConfigUtils; +import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks; import org.apache.brooklyn.core.location.AbstractLocation; import org.apache.brooklyn.core.location.internal.LocationInternal; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; -import org.apache.brooklyn.effector.core.ssh.SshEffectorTasks; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.brooklyn.location.ssh.SshMachineLocation; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorBasicTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/EffectorBasicTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorBasicTest.java new file mode 100644 index 0000000..b05a397 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorBasicTest.java @@ -0,0 +1,183 @@ +/* + * 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 org.apache.brooklyn.core.effector; + +import java.util.List; +import java.util.concurrent.Callable; + +import org.apache.brooklyn.api.entity.EntityLocal; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.mgmt.HasTaskChildren; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.trait.FailingEntity; +import org.apache.brooklyn.core.entity.trait.Startable; +import org.apache.brooklyn.core.location.SimulatedLocation; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.apache.brooklyn.core.test.entity.TestEntity; +import org.apache.brooklyn.test.TestUtils; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.task.Tasks; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +public class EffectorBasicTest extends BrooklynAppUnitTestSupport { + + private static final Logger log = LoggerFactory.getLogger(EffectorBasicTest.class); + + // NB: more tests of effectors in EffectorSayHiTest and EffectorConcatenateTest + // as well as EntityConfigMapUsageTest and others + + private List<SimulatedLocation> locs; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + locs = ImmutableList.of(new SimulatedLocation()); + } + + @Test + public void testInvokeEffectorStart() { + app.start(locs); + TestUtils.assertSetsEqual(locs, app.getLocations()); + // TODO above does not get registered as a task + } + + @Test + public void testInvokeEffectorStartWithMap() { + app.invoke(Startable.START, MutableMap.of("locations", locs)).getUnchecked(); + TestUtils.assertSetsEqual(locs, app.getLocations()); + } + + @Test + public void testInvokeEffectorStartWithArgs() { + Entities.invokeEffectorWithArgs((EntityLocal)app, app, Startable.START, locs).getUnchecked(); + TestUtils.assertSetsEqual(locs, app.getLocations()); + } + + @Test + public void testInvokeEffectorStartWithTwoEntities() { + TestEntity entity = app.createAndManageChild(EntitySpec.create(TestEntity.class)); + TestEntity entity2 = app.createAndManageChild(EntitySpec.create(TestEntity.class)); + app.start(locs); + TestUtils.assertSetsEqual(locs, app.getLocations()); + TestUtils.assertSetsEqual(locs, entity.getLocations()); + TestUtils.assertSetsEqual(locs, entity2.getLocations()); + } + + @Test + public void testInvokeEffectorTaskHasTag() { + Task<Void> starting = app.invoke(Startable.START, MutableMap.of("locations", locs)); +// log.info("TAGS: "+starting.getTags()); + Assert.assertTrue(starting.getTags().contains(ManagementContextInternal.EFFECTOR_TAG)); + } + + // check various failure situations + + private FailingEntity createFailingEntity() { + FailingEntity entity = app.createAndManageChild(EntitySpec.create(FailingEntity.class) + .configure(FailingEntity.FAIL_ON_START, true)); + return entity; + } + + // uncaught failures are propagates + + @Test + public void testInvokeEffectorStartFailing_Method() { + FailingEntity entity = createFailingEntity(); + assertStartMethodFails(entity); + } + + @Test + public void testInvokeEffectorStartFailing_EntityInvoke() { + FailingEntity entity = createFailingEntity(); + assertTaskFails( entity.invoke(Startable.START, MutableMap.of("locations", locs)) ); + } + + @Test + public void testInvokeEffectorStartFailing_EntitiesInvoke() { + FailingEntity entity = createFailingEntity(); + + assertTaskFails( Entities.invokeEffectorWithArgs(entity, entity, Startable.START, locs) ); + } + + // caught failures are NOT propagated! + + @Test + public void testInvokeEffectorStartFailing_MethodInDynamicTask() { + Task<Void> task = app.getExecutionContext().submit(Tasks.<Void>builder().dynamic(true).body(new Callable<Void>() { + @Override public Void call() throws Exception { + testInvokeEffectorStartFailing_Method(); + return null; + } + }).build()); + + assertTaskSucceeds(task); + assertTaskHasFailedChild(task); + } + + @Test + public void testInvokeEffectorStartFailing_MethodInTask() { + Task<Void> task = app.getExecutionContext().submit(Tasks.<Void>builder().dynamic(false).body(new Callable<Void>() { + @Override public Void call() throws Exception { + testInvokeEffectorStartFailing_Method(); + return null; + } + }).build()); + + assertTaskSucceeds(task); + } + + private void assertTaskSucceeds(Task<Void> task) { + task.getUnchecked(); + Assert.assertFalse(task.isError()); + } + + private void assertTaskHasFailedChild(Task<Void> task) { + Assert.assertTrue(Tasks.failed( ((HasTaskChildren)task).getChildren() ).iterator().hasNext()); + } + + private void assertStartMethodFails(FailingEntity entity) { + try { + entity.start(locs); + Assert.fail("Should have failed"); + } catch (Exception e) { + // expected + } + } + + protected void assertTaskFails(Task<?> t) { + try { + t.get(); + Assert.fail("Should have failed"); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + // expected + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorConcatenateTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/EffectorConcatenateTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorConcatenateTest.java new file mode 100644 index 0000000..c909a9d --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorConcatenateTest.java @@ -0,0 +1,241 @@ +/* + * 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 org.apache.brooklyn.core.effector; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.fail; + +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.mgmt.ExecutionManager; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.core.annotation.Effector; +import org.apache.brooklyn.core.annotation.EffectorParam; +import org.apache.brooklyn.core.effector.MethodEffector; +import org.apache.brooklyn.core.entity.AbstractEntity; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; +import org.apache.brooklyn.core.test.entity.TestApplication; +import org.apache.brooklyn.core.test.entity.TestApplicationImpl; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.task.BasicExecutionContext; +import org.apache.brooklyn.util.core.task.Tasks; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; + +public class EffectorConcatenateTest { + + + private static final Logger log = LoggerFactory.getLogger(EffectorConcatenateTest.class); + private static final long TIMEOUT = 10*1000; + + public static class MyEntityImpl extends AbstractEntity { + + public static MethodEffector<String> CONCATENATE = new MethodEffector<String>(MyEntityImpl.class, "concatenate"); + public static MethodEffector<Void> WAIT_A_BIT = new MethodEffector<Void>(MyEntityImpl.class, "waitabit"); + public static MethodEffector<Void> SPAWN_CHILD = new MethodEffector<Void>(MyEntityImpl.class, "spawnchild"); + + public MyEntityImpl() { + super(); + } + public MyEntityImpl(Entity parent) { + super(parent); + } + + /** The "current task" representing the effector currently executing */ + AtomicReference<Task<?>> waitingTask = new AtomicReference<Task<?>>(); + + /** latch is .countDown'ed by the effector at the beginning of the "waiting" point */ + CountDownLatch nowWaitingLatch = new CountDownLatch(1); + + /** latch is await'ed on by the effector when it is in the "waiting" point */ + CountDownLatch continueFromWaitingLatch = new CountDownLatch(1); + + @Effector(description="sample effector concatenating strings") + public String concatenate(@EffectorParam(name="first", description="first argument") String first, + @EffectorParam(name="second", description="2nd arg") String second) throws Exception { + return first+second; + } + + @Effector(description="sample effector doing some waiting") + public void waitabit() throws Exception { + waitingTask.set(Tasks.current()); + + Tasks.setExtraStatusDetails("waitabit extra status details"); + + Tasks.withBlockingDetails("waitabit.blocking", new Callable<Void>() { + public Void call() throws Exception { + nowWaitingLatch.countDown(); + if (!continueFromWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { + fail("took too long to be told to continue"); + } + return null; + }}); + } + + @Effector(description="sample effector that spawns a child task that waits a bit") + public void spawnchild() throws Exception { + // spawn a child, then wait + BasicExecutionContext.getCurrentExecutionContext().submit( + MutableMap.of("displayName", "SpawnedChildName"), + new Callable<Void>() { + public Void call() throws Exception { + log.info("beginning spawned child response "+Tasks.current()+", with tags "+Tasks.current().getTags()); + Tasks.setBlockingDetails("spawned child blocking details"); + nowWaitingLatch.countDown(); + if (!continueFromWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { + fail("took too long to be told to continue"); + } + return null; + }}); + } + } + + private TestApplication app; + private MyEntityImpl e; + + @BeforeMethod(alwaysRun=true) + public void setUp() { + app = new TestApplicationImpl(); + e = new MyEntityImpl(app); + Entities.startManagement(app); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() { + if (app != null) Entities.destroyAll(app.getManagementContext()); + } + + @Test + public void testCanInvokeEffector() throws Exception { + // invocation map syntax + Task<String> task = e.invoke(MyEntityImpl.CONCATENATE, ImmutableMap.of("first", "a", "second", "b")); + assertEquals(task.get(TIMEOUT, TimeUnit.MILLISECONDS), "ab"); + + // method syntax + assertEquals("xy", e.concatenate("x", "y")); + } + + @Test + public void testReportsTaskDetails() throws Exception { + final AtomicReference<String> result = new AtomicReference<String>(); + + Thread bg = new Thread(new Runnable() { + public void run() { + try { + // Expect "wait a bit" to tell us it's blocking + if (!e.nowWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { + result.set("took too long for waitabit to be waiting"); + return; + } + + // Expect "wait a bit" to have retrieved and set its task + try { + Task<?> t = e.waitingTask.get(); + String status = t.getStatusDetail(true); + log.info("waitabit task says:\n"+status); + if (!status.contains("waitabit extra status details")) { + result.set("Status not in expected format: doesn't contain extra status details phrase 'My extra status details'\n"+status); + return; + } + if (!status.startsWith("waitabit.blocking")) { + result.set("Status not in expected format: doesn't start with blocking details 'waitabit.blocking'\n"+status); + return; + } + } finally { + e.continueFromWaitingLatch.countDown(); + } + } catch (Throwable t) { + log.warn("Failure: "+t, t); + result.set("Failure: "+t); + } + }}); + bg.start(); + + e.invoke(MyEntityImpl.WAIT_A_BIT, ImmutableMap.<String,Object>of()) + .get(TIMEOUT, TimeUnit.MILLISECONDS); + + bg.join(TIMEOUT*2); + assertFalse(bg.isAlive()); + + String problem = result.get(); + if (problem!=null) fail(problem); + } + + @Test + public void testReportsSpawnedTaskDetails() throws Exception { + final AtomicReference<String> result = new AtomicReference<String>(); + + Thread bg = new Thread(new Runnable() { + public void run() { + try { + // Expect "spawned child" to tell us it's blocking + if (!e.nowWaitingLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { + result.set("took too long for spawnchild's sub-task to be waiting"); + return; + } + + // Expect spawned task to be have been tagged with entity + ExecutionManager em = e.getManagementContext().getExecutionManager(); + Task<?> subtask = Iterables.find(BrooklynTaskTags.getTasksInEntityContext(em, e), new Predicate<Task<?>>() { + public boolean apply(Task<?> input) { + return "SpawnedChildName".equals(input.getDisplayName()); + } + }); + + // Expect spawned task to haev correct "blocking details" + try { + String status = subtask.getStatusDetail(true); + log.info("subtask task says:\n"+status); + if (!status.contains("spawned child blocking details")) { + result.set("Status not in expected format: doesn't contain blocking details phrase 'spawned child blocking details'\n"+status); + return; + } + } finally { + e.continueFromWaitingLatch.countDown(); + } + } catch (Throwable t) { + log.warn("Failure: "+t, t); + result.set("Failure: "+t); + } + }}); + bg.start(); + + e.invoke(MyEntityImpl.SPAWN_CHILD, ImmutableMap.<String,Object>of()) + .get(TIMEOUT, TimeUnit.MILLISECONDS); + + bg.join(TIMEOUT*2); + assertFalse(bg.isAlive()); + + String problem = result.get(); + if (problem!=null) fail(problem); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorMetadataTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/EffectorMetadataTest.java b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorMetadataTest.java new file mode 100644 index 0000000..1f1be85 --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorMetadataTest.java @@ -0,0 +1,166 @@ +/* + * 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 org.apache.brooklyn.core.effector; + +import static org.testng.Assert.assertEquals; + +import java.util.Collection; +import java.util.List; + +import org.apache.brooklyn.api.effector.Effector; +import org.apache.brooklyn.api.effector.ParameterType; +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.entity.EntitySpec; +import org.apache.brooklyn.api.entity.ImplementedBy; +import org.apache.brooklyn.api.location.Location; +import org.apache.brooklyn.core.annotation.EffectorParam; +import org.apache.brooklyn.core.effector.BasicParameterType; +import org.apache.brooklyn.core.effector.Effectors; +import org.apache.brooklyn.core.effector.MethodEffector; +import org.apache.brooklyn.core.entity.AbstractEntity; +import org.apache.brooklyn.core.entity.trait.Startable; +import org.apache.brooklyn.core.mgmt.internal.EffectorUtils; +import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +/** + * Test the operation of the {@link Effector} implementations. + * + * TODO clarify test purpose + */ +public class EffectorMetadataTest extends BrooklynAppUnitTestSupport { + + private MyAnnotatedEntity e1; + private MyOverridingEntity e2; + + @BeforeMethod(alwaysRun=true) + @Override + public void setUp() throws Exception { + super.setUp(); + e1 = app.createAndManageChild(EntitySpec.create(MyAnnotatedEntity.class)); + e2 = app.createAndManageChild(EntitySpec.create(MyOverridingEntity.class)); + } + + @Test + public void testEffectorMetaDataFromAnnotationsWithConstant() { + Effector<?> effector = EffectorUtils.findEffectorDeclared(e1, "effWithNewAnnotation").get(); + Assert.assertTrue(Effectors.sameSignature(effector, MyAnnotatedEntity.EFF_WITH_NEW_ANNOTATION)); + assertEquals(effector.getName(), "effWithNewAnnotation"); + assertEquals(effector.getDescription(), "my effector description"); + assertEquals(effector.getReturnType(), String.class); + assertParametersEqual( + effector.getParameters(), + ImmutableList.<ParameterType<?>>of( + new BasicParameterType<String>("param1", String.class, "my param description", "my default val"))); + } + + @Test + public void testEffectorMetaDataFromAnnotationsWithoutConstant() { + Effector<?> effector = EffectorUtils.findEffectorDeclared(e1, "effWithAnnotationButNoConstant").get(); + assertEquals(effector.getName(), "effWithAnnotationButNoConstant"); + assertEquals(effector.getDescription(), "my effector description"); + assertEquals(effector.getReturnType(), String.class); + assertParametersEqual( + effector.getParameters(), + ImmutableList.<ParameterType<?>>of( + new BasicParameterType<String>("param1", String.class, "my param description", "my default val"))); + } + + @SuppressWarnings("rawtypes") + @Test + public void testEffectorMetaDataFromOverriddenMethod() { + // Overridden with new annotations + Effector<?> startEffector = EffectorUtils.findEffectorDeclared(e2, "start").get(); + assertEquals(startEffector.getName(), "start"); + assertEquals(startEffector.getDescription(), "My overridden start description"); + assertEquals(startEffector.getReturnType(), void.class); + assertParametersEqual( + startEffector.getParameters(), + ImmutableList.<ParameterType<?>>of( + new BasicParameterType<Collection>("locations", Collection.class, "my overridden param description", null))); + } + + private void assertParametersEqual(List<ParameterType<?>> actuals, List<ParameterType<?>> expecteds) { + assertEquals(actuals.size(), expecteds.size(), "actual="+actuals); + for (int i = 0; i < actuals.size(); i++) { + ParameterType<?> actual = actuals.get(i); + ParameterType<?> expected = expecteds.get(i); + assertParameterEqual(actual, expected); + } + } + + private void assertParameterEqual(ParameterType<?> actual, ParameterType<?> expected) { + assertEquals(actual.getName(), expected.getName(), "actual="+actual); + assertEquals(actual.getDescription(), expected.getDescription(), "actual="+actual); + assertEquals(actual.getParameterClass(), expected.getParameterClass(), "actual="+actual); + assertEquals(actual.getParameterClassName(), expected.getParameterClassName(), "actual="+actual); + } + + @ImplementedBy(MyAnnotatedEntityImpl.class) + public interface MyAnnotatedEntity extends Entity { + static MethodEffector<String> EFF_WITH_NEW_ANNOTATION = new MethodEffector<String>(MyAnnotatedEntity.class, "effWithNewAnnotation"); + + @org.apache.brooklyn.core.annotation.Effector(description="my effector description") + public String effWithNewAnnotation( + @EffectorParam(name="param1", defaultValue="my default val", description="my param description") String param1); + + @org.apache.brooklyn.core.annotation.Effector(description="my effector description") + public String effWithAnnotationButNoConstant( + @EffectorParam(name="param1", defaultValue="my default val", description="my param description") String param1); + } + + public static class MyAnnotatedEntityImpl extends AbstractEntity implements MyAnnotatedEntity { + @Override + public String effWithNewAnnotation(String param1) { + return param1; + } + + @Override + public String effWithAnnotationButNoConstant(String param1) { + return param1; + } + } + + @ImplementedBy(MyOverridingEntityImpl.class) + public interface MyOverridingEntity extends Entity, Startable { + org.apache.brooklyn.api.effector.Effector<Void> START = Effectors.effector(Startable.START) + .description("My overridden start description") + .parameter(Collection.class, "locations", "my overridden param description") + .build(); + } + + public static class MyOverridingEntityImpl extends AbstractEntity implements MyOverridingEntity { + + @Override + public void restart() { + } + + @Override + public void start(Collection<? extends Location> locations2) { + } + + @Override + public void stop() { + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/8dbb0e4b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorSayHiGroovyTest.groovy ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/core/effector/EffectorSayHiGroovyTest.groovy b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorSayHiGroovyTest.groovy new file mode 100644 index 0000000..5dd776e --- /dev/null +++ b/core/src/test/java/org/apache/brooklyn/core/effector/EffectorSayHiGroovyTest.groovy @@ -0,0 +1,182 @@ +/* + * 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 org.apache.brooklyn.core.effector + +import static org.testng.Assert.* + +import org.apache.brooklyn.api.effector.Effector +import org.apache.brooklyn.api.entity.Entity +import org.apache.brooklyn.api.entity.EntitySpec +import org.apache.brooklyn.api.entity.ImplementedBy +import org.apache.brooklyn.api.mgmt.ManagementContext +import org.apache.brooklyn.api.mgmt.Task +import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; +import org.apache.brooklyn.core.mgmt.internal.EffectorUtils +import org.apache.brooklyn.core.test.entity.TestApplication +import org.apache.brooklyn.core.annotation.EffectorParam +import org.apache.brooklyn.core.effector.BasicParameterType; +import org.apache.brooklyn.core.effector.ExplicitEffector; +import org.apache.brooklyn.core.effector.MethodEffector; +import org.apache.brooklyn.core.entity.AbstractEntity +import org.apache.brooklyn.core.entity.Entities +import org.apache.brooklyn.core.entity.trait.Startable +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.testng.annotations.AfterMethod +import org.testng.annotations.BeforeMethod +import org.testng.annotations.Test + +/** + * Test the operation of the {@link Effector} implementations. + * + * TODO clarify test purpose + */ +public class EffectorSayHiGroovyTest { + private static final Logger log = LoggerFactory.getLogger(EffectorSayHiTest.class); + + private TestApplication app; + private MyEntity e; + + @BeforeMethod(alwaysRun=true) + public void setUp() { + app = TestApplication.Factory.newManagedInstanceForTests(); + e = app.createAndManageChild(EntitySpec.create(MyEntity.class)); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() { + if (app != null) Entities.destroyAll(app.getManagementContext()); + } + + @Test + public void testFindEffectors() { + assertEquals("sayHi1", e.SAY_HI_1.getName()); + assertEquals(["name", "greeting"], e.SAY_HI_1.getParameters()[0..1]*.getName()); + assertEquals("says hello", e.SAY_HI_1.getDescription()); + + assertEquals("sayHi1", e.SAY_HI_1_ALT.getName()); + assertEquals(["name", "greeting"], e.SAY_HI_1_ALT.getParameters()[0..1]*.getName()); + assertEquals("says hello", e.SAY_HI_1_ALT.getDescription()); + + assertEquals("sayHi2", e.SAY_HI_2.getName()); + assertEquals(["name", "greeting"], e.SAY_HI_2.getParameters()[0..1]*.getName()); + assertEquals("says hello", e.SAY_HI_2.getDescription()); + } + + @Test + public void testFindTraitEffectors() { + assertEquals("locations", Startable.START.getParameters()[0].getName()); + } + + @Test + public void testInvokeEffectorMethod1BypassInterception() { + String name = "sayHi1" + def args = ["Bob", "hello"] as Object[] + + //try the alt syntax recommended from web + def metaMethod = e.metaClass.getMetaMethod(name, args) + if (metaMethod==null) + throw new IllegalArgumentException("Invalid arguments (no method found) for method $name: "+args); + assertEquals("hello Bob", metaMethod.invoke(e, args)) + } + + @Test + public void testInvokeEffectorMethod2BypassInterception() { + String name = "sayHi2" + def args = ["Bob", "hello"] as Object[] + assertEquals("hello Bob", e.metaClass.invokeMethod(e, name, args)) + } + + @Test + public void testInvokeEffectors1() { + assertEquals("hi Bob", e.sayHi1("Bob", "hi")) + + assertEquals("hello Bob", e.SAY_HI_1.call(e, [name:"Bob"]) ) + assertEquals("hello Bob", e.invoke(e.SAY_HI_1, [name:"Bob"]).get() ); + + assertEquals("hello Bob", e.SAY_HI_1_ALT.call(e, [name:"Bob"]) ) + } + + @Test + public void testInvokeEffectors2() { + assertEquals("hi Bob", e.sayHi2("Bob", "hi")) + + assertEquals("hello Bob", e.SAY_HI_2.call(e, [name:"Bob"]) ) + assertEquals("hello Bob", e.invoke(e.SAY_HI_2, [name:"Bob"]).get() ); + + } + + @Test + public void testCanRetrieveTaskForEffector() { + e.sayHi2("Bob", "hi") + + ManagementContext managementContext = e.getManagementContext() + + Set<Task> tasks = managementContext.getExecutionManager().getTasksWithAllTags([ + BrooklynTaskTags.tagForContextEntity(e),"EFFECTOR"]) + assertEquals(tasks.size(), 1) + assertTrue(tasks.iterator().next().getDescription().contains("sayHi2")) + } +} +public interface CanSayHi { + //prefer following simple groovy syntax + static Effector<String> SAY_HI_1 = new MethodEffector<String>(CanSayHi.&sayHi1); + //slightly longer-winded pojo also supported + static Effector<String> SAY_HI_1_ALT = new MethodEffector<String>(CanSayHi.class, "sayHi1"); + + @org.apache.brooklyn.core.annotation.Effector(description="says hello") + public String sayHi1( + @EffectorParam(name="name") String name, + @EffectorParam(name="greeting", defaultValue="hello", description="what to say") String greeting); + + //finally there is a way to provide a class/closure if needed or preferred for some odd reason + static Effector<String> SAY_HI_2 = + + //groovy 1.8.2 balks at runtime during getCallSiteArray (bug 5122) if we use anonymous inner class +// new ExplicitEffector<CanSayHi,String>( +// "sayHi2", String.class, [ +// [ "name", String.class, "person to say hi to" ] as BasicParameterType<String>, +// [ "greeting", String.class, "what to say as greeting", "hello" ] as BasicParameterType<String> +// ], +// "says hello to a person") { +// public String invokeEffector(CanSayHi e, Map m) { +// e.sayHi2(m) +// } +// }; + //following is a workaround, not greatly enamoured of it... but MethodEffector is generally preferred anyway + ExplicitEffector.create("sayHi2", String.class, [ + new BasicParameterType<String>("name", String.class, "person to say hi to"), + new BasicParameterType<String>("greeting", String.class, "what to say as greeting", "hello") + ], + "says hello", { e, m -> + def args = EffectorUtils.prepareArgsForEffector(SAY_HI_2, m); + e.sayHi2(args[0], args[1]) }) + + public String sayHi2(String name, String greeting); + +} + +@ImplementedBy(MyEntityImpl.class) +public interface MyEntity extends Entity, CanSayHi { +} + +public class MyEntityImpl extends AbstractEntity implements MyEntity { + public String sayHi1(String name, String greeting) { "$greeting $name" } + public String sayHi2(String name, String greeting) { "$greeting $name" } +}
