/*
* 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.objs.proxy;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntityInitializer;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.entity.EntityTypeRegistry;
import org.apache.brooklyn.api.entity.Group;
import org.apache.brooklyn.api.objs.SpecParameter;
import org.apache.brooklyn.api.policy.Policy;
import org.apache.brooklyn.api.policy.PolicySpec;
import org.apache.brooklyn.api.sensor.Enricher;
import org.apache.brooklyn.api.sensor.EnricherSpec;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigConstraints;
import org.apache.brooklyn.core.entity.AbstractApplication;
import org.apache.brooklyn.core.entity.AbstractEntity;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityDynamicType;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal;
import org.apache.brooklyn.core.policy.AbstractPolicy;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.core.flags.FlagUtils;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.javalang.AggregateClassLoader;
import org.apache.brooklyn.util.javalang.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* 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.
*
* Used in three situations:
* <ul>
* <li>Normal entity creation (through entityManager.createEntity)
* <li>rebind (i.e. Brooklyn restart, or promotion of HA standby manager node)
* <li>yaml parsing
* </ul>
*
* @author aled
*/
public class InternalEntityFactory extends InternalFactory {
private static final Logger log = LoggerFactory.getLogger(InternalEntityFactory.class);
private final EntityTypeRegistry entityTypeRegistry;
private final InternalPolicyFactory policyFactory;
public InternalEntityFactory(ManagementContextInternal managementContext, EntityTypeRegistry entityTypeRegistry, InternalPolicyFactory policyFactory) {
super(managementContext);
this.entityTypeRegistry = checkNotNull(entityTypeRegistry, "entityTypeRegistry");
this.policyFactory = checkNotNull(policyFactory, "policyFactory");
}
@VisibleForTesting
public <T extends Entity> T createEntityProxy(EntitySpec<T> spec, T entity) {
Set<Class<?>> interfaces = Sets.newLinkedHashSet();
if (spec.getType().isInterface()) {
interfaces.add(spec.getType());
} else {
log.warn("EntitySpec declared in terms of concrete type "+spec.getType()+"; should be supplied in terms of interface");
interfaces.addAll(Reflections.getAllInterfaces(spec.getType()));
}
interfaces.addAll(spec.getAdditionalInterfaces());
return createEntityProxy(interfaces, entity);
}
@SuppressWarnings("unchecked")
protected <T extends Entity> T createEntityProxy(Iterable<Class<?>> interfaces, T entity) {
// We don't especially want the proxy to have to implement EntityLocal,
// but required by how AbstractEntity.parent is used (e.g. parent.getAllConfig).
// However within EntityProxyImpl we place add'l guards to prevent read-only access to such methods
Set<Class<?>> allInterfaces = MutableSet.<Class<?>>builder()
.add(EntityProxy.class, Entity.class, EntityLocal.class, EntityInternal.class)
.addAll(interfaces)
.build();
// TODO OSGi strangeness! The classloader obtained from the type should be enough.
// If an OSGi class loader, it should delegate to find things like Entity.class etc.
// However, we get errors such as:
// NoClassDefFoundError: org.apache.brooklyn.api.sensor.AttributeSensor not found by ....brooklyn-test-osgi-entities
// Building our own aggregating class loader gets around this.
// But we really should not have to do this! What are the consequences?
//
// The reason for the error is that the proxy tries to load all classes
// referenced from the entity and its interfaces with the single passed loader
// while a normal class loading would nest the class loaders (loading interfaces'
// references with their own class loaders which in our case are different).
Collection<ClassLoader> loaders = Sets.newLinkedHashSet();
addClassLoaders(entity.getClass(), loaders);
for (Class<?> iface : allInterfaces) {
loaders.add(iface.getClassLoader());
}
AggregateClassLoader aggregateClassLoader = AggregateClassLoader.newInstanceWithNoLoaders();
for (ClassLoader cl : loaders) {
aggregateClassLoader.addLast(cl);
}
return (T) java.lang.reflect.Proxy.newProxyInstance(
aggregateClassLoader,
allInterfaces.toArray(new Class[allInterfaces.size()]),
new EntityProxyImpl(entity));
}
private void addClassLoaders(Class<?> type, Collection<ClassLoader> loaders) {
ClassLoader cl = type.getClassLoader();
//java.lang.Object.getClassLoader() = null
if (cl != null) {
loaders.add(cl);
}
Class<?> superType = type.getSuperclass();
if (superType != null) {
addClassLoaders(superType, loaders);
}
for (Class<?> iface : type.getInterfaces()) {
addClassLoaders(iface, loaders);
}
}
/** creates a new entity instance from a spec, with all children, policies, etc,
* fully initialized ({@link AbstractEntity#init()} invoked) and ready for
* management -- commonly the caller will next call
* {@link Entities#manage(Entity)} (if it's in a managed application)
* or {@link Entities#startManagement(org.apache.brooklyn.api.entity.Application, org.apache.brooklyn.api.management.ManagementContext)}
* (if it's an application) */
public <T extends Entity> T createEntity(EntitySpec<T> spec) {
/* Order is important here. Changed Jul 2014 when supporting children in spec.
* (Previously was much simpler, and parent was set right after running initializers; and there were no children.)
* <p>
* It seems we need access to the parent (indeed the root application) when running some initializers (esp children initializers).
* <p>
* Now we do two passes, so that hierarchy is fully populated before initialization and policies.
* (This is needed where some config or initializer might reference another entity by its ID, e.g. yaml $brooklyn:component("id").
* Initialization is done in parent-first order with depth-first children traversal.
*/
// (maps needed because we need the spec, and we need to keep the AbstractEntity to call init, not a proxy)
Map<String,Entity> entitiesByEntityId = MutableMap.of();
Map<String,EntitySpec<?>> specsByEntityId = MutableMap.of();
T entity = createEntityAndDescendantsUninitialized(spec, entitiesByEntityId, specsByEntityId);
initEntityAndDescendants(entity.getId(), entitiesByEntityId, specsByEntityId);
return entity;
}
protected <T extends Entity> T createEntityAndDescendantsUninitialized(EntitySpec<T> spec, Map<String,Entity> entitiesByEntityId, Map<String,EntitySpec<?>> specsByEntityId) {
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);
}
try {
Class<? extends T> clazz = getImplementedBy(spec);
T entity = constructEntity(clazz, spec);
loadUnitializedEntity(entity, spec);
entitiesByEntityId.put(entity.getId(), entity);
specsByEntityId.put(entity.getId(), spec);
for (EntitySpec<?> childSpec : spec.getChildren()) {
if (childSpec.getParent()!=null) {
if (!childSpec.getParent().equals(entity)) {
throw new IllegalStateException("Spec "+childSpec+" has parent "+childSpec.getParent()+" defined, "
+ "but it is defined as a child of "+entity);
}
log.warn("Child spec "+childSpec+" is already set with parent "+entity+"; how did this happen?!");
}
childSpec.parent(entity);
Entity child = createEntityAndDescendantsUninitialized(childSpec, entitiesByEntityId, specsByEntityId);
entity.addChild(child);
}
for (Entity member: spec.getMembers()) {
if (!(entity instanceof Group)) {
throw new IllegalStateException("Entity "+entity+" must be a group to add members "+spec.getMembers());
}
((Group)entity).addMember(member);
}
for (Group group : spec.getGroups()) {
group.addMember(entity);
}
return entity;
} catch (Exception e) {
throw Exceptions.propagate(e);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
protected <T extends Entity> T loadUnitializedEntity(final T entity, final EntitySpec<T> spec) {
try {
if (spec.getDisplayName()!=null)
((AbstractEntity)entity).setDisplayName(spec.getDisplayName());
if (spec.getCatalogItemId()!=null) {
((AbstractEntity)entity).setCatalogItemId(spec.getCatalogItemId());
}
entity.tags().addTags(spec.getTags());
addSpecParameters(spec, ((AbstractEntity)entity).getMutableEntityType());
((AbstractEntity)entity).configure(MutableMap.copyOf(spec.getFlags()));
for (Map.Entry<ConfigKey<?>, Object> entry : spec.getConfig().entrySet()) {
entity.config().set((ConfigKey)entry.getKey(), entry.getValue());
}
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);
}
}
private void addSpecParameters(EntitySpec<?> spec, EntityDynamicType edType) {
for (SpecParameter<?> param : spec.getParameters()) {
edType.addConfigKey(param.getConfigKey());
if (param.getSensor()!=null) edType.addSensor(param.getSensor());
}
}
/**
* Calls {@link ConfigConstraints#assertValid(Entity)} on the given entity and all of
* its descendants.
*/
private void validateDescendantConfig(Entity e) {
Queue<Entity> queue = Lists.newLinkedList();
queue.add(e);
while (!queue.isEmpty()) {
Entity e1 = queue.poll();
ConfigConstraints.assertValid(e1);
queue.addAll(e1.getChildren());
}
}
protected <T extends Entity> void initEntityAndDescendants(String entityId, final Map<String,Entity> entitiesByEntityId, final Map<String,EntitySpec<?>> specsByEntityId) {
final Entity entity = entitiesByEntityId.get(entityId);
final EntitySpec<?> spec = specsByEntityId.get(entityId);
if (entity==null || spec==null) {
log.debug("Skipping initialization of "+entityId+" found as child of entity being initialized, "
+ "but this child is not one we created; likely it was created by an initializer, "
+ "and thus it should be already fully initialized.");
return;
}
// Validate all config before attempting to manage any entity. Do this here rather
// than in manageRecursive so that rebind is unaffected.
validateDescendantConfig(entity);
/* 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 often 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).
* <p>
* Some of these (initializers and enrichers) submit background scheduled tasks,
* which currently show up at the top level once the initializer task completes.
* TODO It would be nice if these schedule tasks were grouped in a bucket!
*/
((EntityInternal)entity).getExecutionContext().submit(Tasks.builder().dynamic(false).displayName("Entity initialization")
.tag(BrooklynTaskTags.tagForContextEntity(entity))
.tag(BrooklynTaskTags.TRANSIENT_TASK_TAG)
.body(new Runnable() {
@Override
public void run() {
((AbstractEntity)entity).init();
((AbstractEntity)entity).addLocations(spec.getLocations());
for (EntityInitializer initializer: spec.getInitializers()) {
initializer.apply((EntityInternal)entity);
}
for (Enricher enricher : spec.getEnrichers()) {
entity.enrichers().add(enricher);
}
for (EnricherSpec<?> enricherSpec : spec.getEnricherSpecs()) {
entity.enrichers().add(policyFactory.createEnricher(enricherSpec));
}
for (Policy policy : spec.getPolicies()) {
entity.policies().add((AbstractPolicy)policy);
}
for (PolicySpec<?> policySpec : spec.getPolicySpecs()) {
entity.policies().add(policyFactory.createPolicy(policySpec));
}
for (Entity child: entity.getChildren()) {
// right now descendants are initialized depth-first (see the getUnchecked() call below)
// they could be done in parallel, but OTOH initializers should be very quick
initEntityAndDescendants(child.getId(), entitiesByEntityId, specsByEntityId);
}
}
}).build()).getUnchecked();
}
/**
* Constructs an entity, i.e. instantiate the actual class given a spec,
* and sets the entity's proxy. Used by this factory to {@link #createEntity(EntitySpec)}
* and also used during rebind.
* <p>
* If {@link EntitySpec#id(String)} was set then uses that to override the entity's id,
* but that behaviour is deprecated.
* <p>
* The new-style no-arg constructor is preferred, and
* configuration from the {@link EntitySpec} is <b>not</b> normally applied,
* although for old-style entities flags from the spec are passed to the constructor.
*/
public <T extends Entity> T constructEntity(Class<? extends T> clazz, EntitySpec<T> spec) {
T entity = constructEntityImpl(clazz, spec.getFlags());
if (((AbstractEntity)entity).getProxy() == null) ((AbstractEntity)entity).setProxy(createEntityProxy(spec, entity));
return entity;
}
/**
* Constructs a new-style entity (fails if no no-arg constructor).
* Sets the entity's id and proxy.
* <p>
* As {@link #constructEntity(Class, EntitySpec)} but when no spec is used.
*/
// TODO would it be cleaner to have callers just create a spec? and deprecate this?
public <T extends Entity> T constructEntity(Class<T> clazz, Iterable<Class<?>> interfaces, String entityId) {
if (!isNewStyle(clazz)) {
throw new IllegalStateException("Cannot construct old-style entity "+clazz);
}
checkNotNull(entityId, "entityId");
checkState(interfaces != null && !Iterables.isEmpty(interfaces), "must have at least one interface for entity %s:%s", clazz, entityId);
T entity = constructEntityImpl(clazz, ImmutableMap.<String, Object>of(), entityId);
if (((AbstractEntity)entity).getProxy() == null) {
Entity proxy = managementContext.getEntityManager().getEntity(entity.getId());
if (proxy==null) {
// normal case, proxy does not exist
proxy = createEntityProxy(interfaces, entity);
} else {
// only if rebinding to existing; don't create a new proxy, then we have proxy explosion
// but callers must be careful that the entity's proxy does not yet point to it
}
((AbstractEntity)entity).setProxy(proxy);
}
return entity;
}
protected <T extends Entity> T constructEntityImpl(Class<? extends T> clazz, Map<String, ?> constructionFlags) {
return constructEntityImpl(clazz, constructionFlags, null);
}
protected <T extends Entity> T constructEntityImpl(Class<? extends T> clazz, Map<String, ?> constructionFlags, String entityId) {
T entity = super.construct(clazz, constructionFlags);
if (entityId != null) {
FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", entityId), entity);
}
if (entity instanceof AbstractApplication) {
FlagUtils.setFieldsFromFlags(ImmutableMap.of("mgmt", managementContext), entity);
}
managementContext.prePreManage(entity);
((AbstractEntity)entity).setManagementContext(managementContext);
return entity;
}
@Override
protected <T> T constructOldStyle(Class<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);
}
return super.constructOldStyle(clazz, flags);
}
private <T extends Entity> Class<? extends T> getImplementedBy(EntitySpec<T> spec) {
if (spec.getImplementation() != null) {
return spec.getImplementation();
} else {
return entityTypeRegistry.getImplementedBy(spec.getType());
}
}
}