| package brooklyn.entity.proxying; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import brooklyn.config.ConfigKey; |
| import brooklyn.entity.Entity; |
| import brooklyn.entity.basic.AbstractEntity; |
| import brooklyn.entity.basic.BrooklynTaskTags; |
| import brooklyn.entity.basic.EntityInternal; |
| import brooklyn.entity.basic.EntityLocal; |
| import brooklyn.management.ManagementContext; |
| import brooklyn.management.internal.LocalEntityManager; |
| import brooklyn.management.internal.ManagementContextInternal; |
| import brooklyn.policy.Enricher; |
| import brooklyn.policy.EnricherSpec; |
| import brooklyn.policy.Policy; |
| import brooklyn.policy.PolicySpec; |
| import brooklyn.policy.basic.AbstractPolicy; |
| import brooklyn.util.collections.MutableMap; |
| import brooklyn.util.collections.MutableSet; |
| import brooklyn.util.exceptions.Exceptions; |
| import brooklyn.util.flags.FlagUtils; |
| import brooklyn.util.javalang.Reflections; |
| import brooklyn.util.task.Tasks; |
| |
| import com.google.common.base.Optional; |
| import com.google.common.collect.ImmutableMap; |
| |
| /** |
| * Creates entities (and proxies) of required types, given the |
| * |
| * This is an internal class for use by core-brooklyn. End-users are strongly discouraged from |
| * using this class directly. |
| * |
| * @author aled |
| */ |
| public class InternalEntityFactory { |
| |
| private static final Logger log = LoggerFactory.getLogger(InternalEntityFactory.class); |
| |
| private final ManagementContextInternal managementContext; |
| private final EntityTypeRegistry entityTypeRegistry; |
| private final InternalPolicyFactory policyFactory; |
| |
| /** |
| * For tracking if AbstractEntity constructor has been called by framework, or in legacy way (i.e. directly). |
| * |
| * To be deleted once we delete support for constructing entities directly (and expecting configure() to be |
| * called inside the constructor, etc). |
| * |
| * @author aled |
| */ |
| public static class FactoryConstructionTracker { |
| private static ThreadLocal<Boolean> constructing = new ThreadLocal<Boolean>(); |
| |
| public static boolean isConstructing() { |
| return (constructing.get() == Boolean.TRUE); |
| } |
| |
| static void reset() { |
| constructing.set(Boolean.FALSE); |
| } |
| |
| static void setConstructing() { |
| constructing.set(Boolean.TRUE); |
| } |
| } |
| |
| /** |
| * Returns true if this is a "new-style" entity (i.e. where not expected to call the constructor to instantiate it). |
| * That means it is an entity with a no-arg constructor, and where there is a mapped for an entity type interface. |
| * @param managementContext |
| * @param clazz |
| */ |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public static boolean isNewStyleEntity(ManagementContext managementContext, Class<?> clazz) { |
| try { |
| return isNewStyleEntity(clazz) && managementContext.getEntityManager().getEntityTypeRegistry().getEntityTypeOf((Class)clazz) != null; |
| } catch (IllegalArgumentException e) { |
| return false; |
| } |
| } |
| |
| public static boolean isNewStyleEntity(Class<?> clazz) { |
| if (!Entity.class.isAssignableFrom(clazz)) { |
| throw new IllegalArgumentException("Class "+clazz+" is not an entity"); |
| } |
| |
| try { |
| clazz.getConstructor(new Class[0]); |
| return true; |
| } catch (NoSuchMethodException e) { |
| return false; |
| } |
| } |
| |
| public InternalEntityFactory(ManagementContextInternal managementContext, EntityTypeRegistry entityTypeRegistry, InternalPolicyFactory policyFactory) { |
| this.managementContext = checkNotNull(managementContext, "managementContext"); |
| this.entityTypeRegistry = checkNotNull(entityTypeRegistry, "entityTypeRegistry"); |
| this.policyFactory = checkNotNull(policyFactory, "policyFactory"); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <T extends Entity> T createEntityProxy(EntitySpec<T> spec, T entity) { |
| // TODO Don't want the proxy to have to implement EntityLocal, but required by how |
| // AbstractEntity.parent is used (e.g. parent.getAllConfig) |
| ClassLoader classloader = (spec.getImplementation() != null ? spec.getImplementation() : spec.getType()).getClassLoader(); |
| MutableSet.Builder<Class<?>> builder = MutableSet.<Class<?>>builder() |
| .add(EntityProxy.class, Entity.class, EntityLocal.class, EntityInternal.class); |
| if (spec.getType().isInterface()) { |
| builder.add(spec.getType()); |
| } else { |
| log.warn("EntitySpec declared in terms of concrete type "+spec.getType()+"; should be supplied in terms of interface"); |
| } |
| builder.addAll(spec.getAdditionalInterfaces()); |
| Set<Class<?>> interfaces = builder.build(); |
| |
| return (T) java.lang.reflect.Proxy.newProxyInstance( |
| classloader, |
| interfaces.toArray(new Class[interfaces.size()]), |
| new EntityProxyImpl(entity)); |
| } |
| |
| public <T extends Entity> T createEntity(EntitySpec<T> spec) { |
| if (spec.getFlags().containsKey("parent") || spec.getFlags().containsKey("owner")) { |
| throw new IllegalArgumentException("Spec's flags must not contain parent or owner; use spec.parent() instead for "+spec); |
| } |
| if (spec.getFlags().containsKey("id")) { |
| throw new IllegalArgumentException("Spec's flags must not contain id; use spec.id() instead for "+spec); |
| } |
| if (spec.getId() != null && ((LocalEntityManager)managementContext.getEntityManager()).isKnownEntityId(spec.getId())) { |
| throw new IllegalArgumentException("Entity with id "+spec.getId()+" already exists; cannot create new entity with this explicit id from spec "+spec); |
| } |
| |
| try { |
| Class<? extends T> clazz = getImplementedBy(spec); |
| |
| T entity = constructEntity(clazz, spec); |
| |
| // TODO Could move setManagementContext call into constructEntity; would that break rebind? |
| if (spec.getId() != null) { |
| FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", spec.getId()), entity); |
| } |
| ((AbstractEntity)entity).setManagementContext(managementContext); |
| managementContext.prePreManage(entity); |
| |
| initEntity(entity, spec); |
| return entity; |
| |
| } catch (Exception e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public <T extends Entity> T initEntity(final T entity, final EntitySpec<T> spec) { |
| try { |
| if (spec.getDisplayName()!=null) |
| ((AbstractEntity)entity).setDisplayName(spec.getDisplayName()); |
| |
| if (((AbstractEntity)entity).getProxy() == null) ((AbstractEntity)entity).setProxy(createEntityProxy(spec, entity)); |
| ((AbstractEntity)entity).configure(MutableMap.copyOf(spec.getFlags())); |
| |
| for (Map.Entry<ConfigKey<?>, Object> entry : spec.getConfig().entrySet()) { |
| ((EntityLocal)entity).setConfig((ConfigKey)entry.getKey(), entry.getValue()); |
| } |
| |
| /* Marked transient so that the task is not needlessly kept around at the highest level. |
| * Note that the task is not normally visible in the GUI, because |
| * (a) while it is running, the entity is parentless (and so not in the tree); |
| * and (b) when it is completed it is GC'd, as it is transient. |
| * However task info is available via the API if you know its ID, |
| * and if better subtask querying is available it will be picked up as a background task |
| * of the parent entity creating this child entity |
| * (note however such subtasks are currently filtered based on parent entity so is excluded). |
| */ |
| ((EntityInternal)entity).getExecutionContext().submit(Tasks.builder().dynamic(false).name("Entity initialization").tag(BrooklynTaskTags.TRANSIENT_TASK_TAG) |
| .body(new Runnable() { |
| @Override |
| public void run() { |
| ((AbstractEntity)entity).init(); |
| for (EntityInitializer initializer: spec.getInitializers()) |
| initializer.apply((EntityInternal)entity); |
| /* 31 Mar 2014, moved initialization (above) into this task: primarily for consistency and traceability on failure. |
| * TBC whether this is good/bad/indifferent. My (Alex) opinion is that whether it is done in a subtask |
| * should be the same as whether enricher/policy/etc (below) is done subtasks, which is was added recently |
| * in 249c96fbb18bd9d763029475e0a3dc251c01b287. @nakomis can you give exact reason code below is needed in a task |
| * commit message said was to do with wiring up yaml sensors and policies -- which makes sense but specifics would be handy! |
| * and would let me know if there is any reason to do / not_do the initializer code above also here! |
| */ |
| |
| for (Enricher enricher : spec.getEnrichers()) { |
| entity.addEnricher(enricher); |
| } |
| |
| for (EnricherSpec<?> enricherSpec : spec.getEnricherSpecs()) { |
| entity.addEnricher(policyFactory.createEnricher(enricherSpec)); |
| } |
| |
| for (Policy policy : spec.getPolicies()) { |
| entity.addPolicy((AbstractPolicy)policy); |
| } |
| |
| for (PolicySpec<?> policySpec : spec.getPolicySpecs()) { |
| entity.addPolicy(policyFactory.createPolicy(policySpec)); |
| } |
| |
| ((AbstractEntity)entity).addLocations(spec.getLocations()); |
| } |
| }).build()).get(); |
| |
| Entity parent = spec.getParent(); |
| if (parent != null) { |
| parent = (parent instanceof AbstractEntity) ? ((AbstractEntity)parent).getProxyIfAvailable() : parent; |
| entity.setParent(parent); |
| } |
| return entity; |
| |
| } catch (Exception e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| /** |
| * Constructs an entity (if new-style, calls no-arg constructor; if old-style, uses spec to pass in config). |
| */ |
| public <T extends Entity> T constructEntity(Class<? extends T> clazz, EntitySpec<T> spec) { |
| try { |
| FactoryConstructionTracker.setConstructing(); |
| try { |
| if (isNewStyleEntity(clazz)) { |
| return clazz.newInstance(); |
| } else { |
| return constructOldStyle(clazz, MutableMap.copyOf(spec.getFlags())); |
| } |
| } finally { |
| FactoryConstructionTracker.reset(); |
| } |
| } catch (Exception e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| /** |
| * Constructs a new-style entity (fails if no no-arg constructor). |
| */ |
| public <T extends Entity> T constructEntity(Class<T> clazz) { |
| try { |
| FactoryConstructionTracker.setConstructing(); |
| try { |
| if (isNewStyleEntity(clazz)) { |
| return clazz.newInstance(); |
| } else { |
| throw new IllegalStateException("Entity class "+clazz+" must have a no-arg constructor"); |
| } |
| } finally { |
| FactoryConstructionTracker.reset(); |
| } |
| } catch (Exception e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| private <T extends Entity> T constructOldStyle(Class<? extends T> clazz, Map<String,?> flags) throws InstantiationException, IllegalAccessException, InvocationTargetException { |
| if (flags.containsKey("parent") || flags.containsKey("owner")) { |
| throw new IllegalArgumentException("Spec's flags must not contain parent or owner; use spec.parent() instead for "+clazz); |
| } |
| Optional<? extends T> v = Reflections.invokeConstructorWithArgs(clazz, new Object[] {MutableMap.copyOf(flags)}, true); |
| if (v.isPresent()) { |
| return v.get(); |
| } else { |
| throw new IllegalStateException("No valid constructor defined for "+clazz+" (expected no-arg or single java.util.Map argument)"); |
| } |
| } |
| |
| private <T extends Entity> Class<? extends T> getImplementedBy(EntitySpec<T> spec) { |
| if (spec.getImplementation() != null) { |
| return spec.getImplementation(); |
| } else { |
| return entityTypeRegistry.getImplementedBy(spec.getType()); |
| } |
| } |
| } |