/* * 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.entity; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.brooklyn.api.effector.Effector; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntityType; import org.apache.brooklyn.api.sensor.Sensor; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.config.ConfigKey.HasConfigKey; import org.apache.brooklyn.core.effector.EffectorAndBody; import org.apache.brooklyn.core.effector.EffectorBody; import org.apache.brooklyn.core.effector.EffectorWithBody; import org.apache.brooklyn.core.effector.Effectors; import org.apache.brooklyn.core.effector.MethodEffector; import org.apache.brooklyn.core.effector.EffectorTasks.EffectorBodyTaskFactory; import org.apache.brooklyn.core.effector.EffectorTasks.EffectorTaskFactory; import org.apache.brooklyn.core.objs.BrooklynDynamicType; import org.apache.brooklyn.util.javalang.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.google.common.base.Joiner; import com.google.common.base.Throwables; import com.google.common.collect.Maps; /** This is the actual type of an entity instance at runtime, * which can change from the static {@link EntityType}, and can change over time; * for this reason it does *not* implement EntityType, but * callers can call {@link #getSnapshot()} to get a snapshot such instance */ public class EntityDynamicType extends BrooklynDynamicType<Entity, AbstractEntity> { private static final Logger LOG = LoggerFactory.getLogger(EntityDynamicType.class); /** * Effectors on this entity, by name. */ // TODO support overloading; requires not using a map keyed off method name. private final Map<String, Effector<?>> effectors = new ConcurrentHashMap<String, Effector<?>>(); /** * Map of sensors on this entity, by name. */ private final ConcurrentMap<String,Sensor<?>> sensors = new ConcurrentHashMap<String, Sensor<?>>(); public EntityDynamicType(AbstractEntity entity) { this(entity.getClass(), entity); } public EntityDynamicType(Class<? extends Entity> clazz) { this(clazz, null); } private EntityDynamicType(Class<? extends Entity> clazz, AbstractEntity entity) { super(clazz, entity); String id = entity==null ? clazz.getName() : entity.getId(); effectors.putAll(findEffectors(clazz, null)); if (LOG.isTraceEnabled()) LOG.trace("Entity {} effectors: {}", id, Joiner.on(", ").join(effectors.keySet())); sensors.putAll(findSensors(clazz, null)); if (LOG.isTraceEnabled()) LOG.trace("Entity {} sensors: {}", id, Joiner.on(", ").join(sensors.keySet())); refreshSnapshot(); } /** * @deprecated since 0.7; unused code; instead use {@link #getBrooklynClass()} */ @Deprecated public Class<? extends Entity> getEntityClass() { return super.getBrooklynClass(); } @Override public EntityType getSnapshot() { return (EntityType) super.getSnapshot(); } // -------------------------------------------------- /** * @return the effector with the given name, or null if not found */ public Effector<?> getEffector(String name) { return effectors.get(name); } /** * Effectors available on this entity. */ public Map<String,Effector<?>> getEffectors() { return Collections.unmodifiableMap(effectors); } /** * Adds the given {@link Effector} to this entity. */ @Beta public void addEffector(Effector<?> newEffector) { Effector<?> oldEffector = effectors.put(newEffector.getName(), newEffector); invalidateSnapshot(); if (oldEffector!=null) instance.sensors().emit(AbstractEntity.EFFECTOR_CHANGED, newEffector.getName()); else instance.sensors().emit(AbstractEntity.EFFECTOR_ADDED, newEffector.getName()); } /** * Adds an effector with an explicit body to this entity. */ @Beta public <T> void addEffector(Effector<T> effector, EffectorTaskFactory<T> body) { addEffector(new EffectorAndBody<T>(effector, body)); } /** * Adds an effector with an explicit body to this entity. */ @Beta public <T> void addEffector(Effector<T> effector, EffectorBody<T> body) { addEffector(effector, new EffectorBodyTaskFactory<T>(body)); } /** * Removes the given {@link Effector} from this entity. * <p> * Note that if the argument is an instance of {@link EffectorWithBody} it will * still be possible to invoke the effector on the entity by calling * <code>entity.invoke(effector, argumentsMap)</code>. */ @Beta public void removeEffector(Effector<?> effector) { Effector<?> removed = effectors.remove(effector.getName()); invalidateSnapshot(); if (removed != null) { instance.sensors().emit(AbstractEntity.EFFECTOR_REMOVED, removed.getName()); } } // -------------------------------------------------- /** * Sensors available on this entity. */ public Map<String,Sensor<?>> getSensors() { return Collections.unmodifiableMap(sensors); } /** * Convenience for finding named sensor. */ public Sensor<?> getSensor(String sensorName) { return sensors.get(sensorName); } /** * Adds the given {@link Sensor} to this entity. */ public void addSensor(Sensor<?> newSensor) { sensors.put(newSensor.getName(), newSensor); invalidateSnapshot(); instance.sensors().emit(AbstractEntity.SENSOR_ADDED, newSensor); } /** * Adds the given {@link Sensor}s to this entity. */ public void addSensors(Iterable<? extends Sensor<?>> newSensors) { for (Sensor<?> sensor : newSensors) { addSensor(sensor); } } public void addSensorIfAbsent(Sensor<?> newSensor) { Sensor<?> prev = addSensorIfAbsentWithoutPublishing(newSensor); if (prev == null) { instance.sensors().emit(AbstractEntity.SENSOR_ADDED, newSensor); } } public Sensor<?> addSensorIfAbsentWithoutPublishing(Sensor<?> newSensor) { Sensor<?> prev = sensors.putIfAbsent(newSensor.getName(), newSensor); if (prev == null) { invalidateSnapshot(); } return prev; } /** * Removes the named {@link Sensor} from this entity. */ public Sensor<?> removeSensor(String sensorName) { Sensor<?> result = sensors.remove(sensorName); if (result != null) { invalidateSnapshot(); instance.sensors().emit(AbstractEntity.SENSOR_REMOVED, result); } return result; } /** * Removes the named {@link Sensor} from this entity. */ public boolean removeSensor(Sensor<?> sensor) { return (removeSensor(sensor.getName()) != null); } // -------------------------------------------------- /** * Adds the given {@link ConfigKey} to this entity. */ public void addConfigKey(ConfigKey<?> newKey) { configKeys.put(newKey.getName(), new FieldAndValue<ConfigKey<?>>(null, newKey)); invalidateSnapshot(); instance.sensors().emit(AbstractEntity.CONFIG_KEY_ADDED, newKey); } /** * Adds the given {@link ConfigKey} to this entity. */ public void addConfigKeys(Iterable<ConfigKey<?>> newKeys) { for (ConfigKey<?> newKey : newKeys) { addConfigKey(newKey); } } /** * Removes the named {@link ConfigKey} from this entity. */ public boolean removeConfigKey(ConfigKey<?> key) { FieldAndValue<ConfigKey<?>> result = configKeys.remove(key.getName()); if (result != null) { invalidateSnapshot(); ConfigKey<?> removedKey = result.value; instance.sensors().emit(AbstractEntity.CONFIG_KEY_REMOVED, removedKey); return true; } else { return false; } } // -------------------------------------------------- @Override protected EntityTypeSnapshot newSnapshot() { return new EntityTypeSnapshot(name, value(configKeys), sensors, effectors.values()); } /** * Finds the effectors defined on the entity's class, statics and optionally any non-static (discouraged). */ public static Map<String,Effector<?>> findEffectors(Class<? extends Entity> clazz, Entity optionalEntity) { try { Map<String,Effector<?>> result = Maps.newLinkedHashMap(); Map<String,Field> fieldSources = Maps.newLinkedHashMap(); Map<String,Method> methodSources = Maps.newLinkedHashMap(); for (Field f : Reflections.findPublicFieldsOrderedBySuper(clazz)) { if (Effector.class.isAssignableFrom(f.getType())) { if (!Modifier.isStatic(f.getModifiers())) { // require it to be static or we have an instance LOG.warn("Discouraged/deprecated use of non-static effector field "+f+" defined in " + (optionalEntity!=null ? optionalEntity : clazz)); if (optionalEntity==null) continue; } Effector<?> eff = (Effector<?>) f.get(optionalEntity); if (eff==null) { LOG.warn("Effector "+f+" undefined for "+clazz+" ("+optionalEntity+")"); continue; } Effector<?> overwritten = result.put(eff.getName(), eff); Field overwrittenFieldSource = fieldSources.put(eff.getName(), f); if (overwritten!=null && !Effectors.sameInstance(overwritten, eff)) { LOG.trace("multiple definitions for effector {} on {}; preferring {} from {} to {} from {}", new Object[] { eff.getName(), (optionalEntity != null ? optionalEntity : clazz), eff, f, overwritten, overwrittenFieldSource}); } } } for (Method m : Reflections.findPublicMethodsOrderedBySuper(clazz)) { org.apache.brooklyn.core.annotation.Effector effectorAnnotation = m.getAnnotation(org.apache.brooklyn.core.annotation.Effector.class); if (effectorAnnotation != null) { if (Modifier.isStatic(m.getModifiers())) { // require it to be static or we have an instance LOG.warn("Discouraged/deprecated use of static annotated effector method "+m+" defined in " + (optionalEntity!=null ? optionalEntity : clazz)); if (optionalEntity==null) continue; } Effector<?> eff = MethodEffector.create(m); Effector<?> overwritten = result.get(eff.getName()); if ((overwritten instanceof EffectorWithBody) && !(overwritten instanceof MethodEffector<?>)) { // don't let annotations on methods override a static, unless that static is a MethodEffector // TODO not perfect, but approx right; we should clarify whether we prefer statics or methods } else { result.put(eff.getName(), eff); Method overwrittenMethodSource = methodSources.put(eff.getName(), m); Field overwrittenFieldSource = fieldSources.remove(eff.getName()); LOG.trace("multiple definitions for effector {} on {}; preferring {} from {} to {} from {}", new Object[] { eff.getName(), (optionalEntity != null ? optionalEntity : clazz), eff, m, overwritten, (overwrittenMethodSource != null ? overwrittenMethodSource : overwrittenFieldSource)}); } } } return result; } catch (IllegalAccessException e) { throw Throwables.propagate(e); } } /** * Finds the sensors defined on the entity's class, statics and optionally any non-static (discouraged). */ public static Map<String,Sensor<?>> findSensors(Class<? extends Entity> clazz, Entity optionalEntity) { try { Map<String,Sensor<?>> result = Maps.newLinkedHashMap(); Map<String,Field> sources = Maps.newLinkedHashMap(); for (Field f : Reflections.findPublicFieldsOrderedBySuper((clazz))) { if (Sensor.class.isAssignableFrom(f.getType())) { if (!Modifier.isStatic(f.getModifiers())) { // require it to be static or we have an instance LOG.warn("Discouraged use of non-static sensor "+f+" defined in " + (optionalEntity!=null ? optionalEntity : clazz)); if (optionalEntity==null) continue; } Sensor<?> sens = (Sensor<?>) f.get(optionalEntity); Sensor<?> overwritten = result.put(sens.getName(), sens); Field source = sources.put(sens.getName(), f); if (overwritten!=null && overwritten != sens) { if (sens instanceof HasConfigKey) { // probably overriding defaults, just log low level (there will be add'l logging in config key section) LOG.trace("multiple definitions for config sensor {} on {}; preferring {} from {} to {} from {}", new Object[] { sens.getName(), optionalEntity!=null ? optionalEntity : clazz, sens, f, overwritten, source}); } else { LOG.warn("multiple definitions for sensor {} on {}; preferring {} from {} to {} from {}", new Object[] { sens.getName(), optionalEntity!=null ? optionalEntity : clazz, sens, f, overwritten, source}); } } } } return result; } catch (IllegalAccessException e) { throw Throwables.propagate(e); } } }