/* * 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.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.effector.EffectorTasks.EffectorBodyTaskFactory; import org.apache.brooklyn.core.effector.EffectorTasks.EffectorMarkingTaskFactory; import org.apache.brooklyn.core.effector.EffectorTasks.EffectorTaskFactory; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.EntityInternal; 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()); } /** convenience for {@link #invocationParallel(Effector, Map, Iterable)} */ public static TaskAdaptable<List<?>> invocation(Effector<?> eff, Map<?,?> params, Iterable<? extends Entity> entities) { return invocationParallel(eff, params, entities); } /** returns an unsubmitted task which will invoke the given effector on the given entities in parallel; * 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<?>> invocationParallel(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()])); } /** as {@link #invocationParallel(Effector, Map, Iterable)} but executing sequentially */ public static TaskAdaptable<List<?>> invocationSequential(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.sequential("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; } }