/*
* 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.mgmt.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
import org.apache.brooklyn.api.catalog.BrooklynCatalog;
import org.apache.brooklyn.api.effector.Effector;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.drivers.EntityDriverManager;
import org.apache.brooklyn.api.entity.drivers.downloads.DownloadResolverManager;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.location.LocationRegistry;
import org.apache.brooklyn.api.mgmt.ExecutionContext;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.SubscriptionContext;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext;
import org.apache.brooklyn.api.mgmt.entitlement.EntitlementManager;
import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager;
import org.apache.brooklyn.api.mgmt.rebind.RebindManager;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.api.typereg.BrooklynTypeRegistry;
import org.apache.brooklyn.api.typereg.RegisteredType;
import org.apache.brooklyn.config.StringConfigMap;
import org.apache.brooklyn.core.catalog.internal.BasicBrooklynCatalog;
import org.apache.brooklyn.core.catalog.internal.CatalogInitialization;
import org.apache.brooklyn.core.catalog.internal.CatalogUtils;
import org.apache.brooklyn.core.entity.AbstractEntity;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.entity.drivers.BasicEntityDriverManager;
import org.apache.brooklyn.core.entity.drivers.downloads.BasicDownloadsManager;
import org.apache.brooklyn.core.internal.BrooklynProperties;
import org.apache.brooklyn.core.internal.storage.BrooklynStorage;
import org.apache.brooklyn.core.internal.storage.DataGrid;
import org.apache.brooklyn.core.internal.storage.DataGridFactory;
import org.apache.brooklyn.core.internal.storage.impl.BrooklynStorageImpl;
import org.apache.brooklyn.core.internal.storage.impl.inmemory.InMemoryDataGridFactory;
import org.apache.brooklyn.core.location.BasicLocationRegistry;
import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext;
import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
import org.apache.brooklyn.core.mgmt.ha.HighAvailabilityManagerImpl;
import org.apache.brooklyn.core.mgmt.rebind.RebindManagerImpl;
import org.apache.brooklyn.core.typereg.BasicBrooklynTypeRegistry;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.task.BasicExecutionContext;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.groovy.GroovyJavaMethods;
import org.apache.brooklyn.util.guava.Maybe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
public abstract class AbstractManagementContext implements ManagementContextInternal {
private static final Logger log = LoggerFactory.getLogger(AbstractManagementContext.class);
private static DataGridFactory loadDataGridFactory(BrooklynProperties properties) {
String clazzName = properties.getFirst(DataGridFactory.class.getName());
if(clazzName == null){
clazzName = InMemoryDataGridFactory.class.getName();
}
Class<?> clazz;
try{
//todo: which classloader should we use?
clazz = LocalManagementContext.class.getClassLoader().loadClass(clazzName);
}catch(ClassNotFoundException e){
throw new IllegalStateException(format("Could not load class [%s]",clazzName),e);
}
Object instance;
try {
instance = clazz.newInstance();
} catch (InstantiationException e) {
throw new IllegalStateException(format("Could not instantiate class [%s]",clazzName),e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(format("Could not instantiate class [%s]",clazzName),e);
}
if(!(instance instanceof DataGridFactory)){
throw new IllegalStateException(format("Class [%s] not an instantiate of class [%s]",clazzName, DataGridFactory.class.getName()));
}
return (DataGridFactory)instance;
}
static {
ResourceUtils.addClassLoaderProvider(new Function<Object, BrooklynClassLoadingContext>() {
@Override
public BrooklynClassLoadingContext apply(@Nullable Object input) {
if (input instanceof EntityInternal) {
EntityInternal internal = (EntityInternal)input;
if (internal.getCatalogItemId() != null) {
RegisteredType item = internal.getManagementContext().getTypeRegistry().get(internal.getCatalogItemId());
if (item != null) {
return CatalogUtils.newClassLoadingContext(internal.getManagementContext(), item);
} else {
log.error("Can't find catalog item " + internal.getCatalogItemId() +
" used for instantiating entity " + internal +
". Falling back to application classpath.");
}
}
return apply(internal.getManagementSupport());
}
if (input instanceof EntityManagementSupport)
return apply(((EntityManagementSupport)input).getManagementContext());
if (input instanceof ManagementContext)
return JavaBrooklynClassLoadingContext.create((ManagementContext) input);
return null;
}
});
}
private final AtomicLong totalEffectorInvocationCount = new AtomicLong();
protected DeferredBrooklynProperties configMap;
protected BasicLocationRegistry locationRegistry;
protected final BasicBrooklynCatalog catalog;
protected final BrooklynTypeRegistry typeRegistry;
protected ClassLoader baseClassLoader;
protected Iterable<URL> baseClassPathForScanning;
private final RebindManager rebindManager;
private final HighAvailabilityManager highAvailabilityManager;
protected volatile BrooklynGarbageCollector gc;
private final EntityDriverManager entityDriverManager;
protected DownloadResolverManager downloadsManager;
protected EntitlementManager entitlementManager;
private final BrooklynStorage storage;
protected final ExternalConfigSupplierRegistry configSupplierRegistry;
private volatile boolean running = true;
protected boolean startupComplete = false;
protected final List<Throwable> errors = Collections.synchronizedList(MutableList.<Throwable>of());
protected Maybe<URI> uri = Maybe.absent();
protected CatalogInitialization catalogInitialization;
public AbstractManagementContext(BrooklynProperties brooklynProperties){
this(brooklynProperties, null);
}
public AbstractManagementContext(BrooklynProperties brooklynProperties, DataGridFactory datagridFactory) {
this.configMap = new DeferredBrooklynProperties(brooklynProperties, this);
this.entityDriverManager = new BasicEntityDriverManager();
this.downloadsManager = BasicDownloadsManager.newDefault(configMap);
if (datagridFactory == null) {
datagridFactory = loadDataGridFactory(brooklynProperties);
}
DataGrid datagrid = datagridFactory.newDataGrid(this);
this.catalog = new BasicBrooklynCatalog(this);
this.typeRegistry = new BasicBrooklynTypeRegistry(this);
this.storage = new BrooklynStorageImpl(datagrid);
this.rebindManager = new RebindManagerImpl(this); // TODO leaking "this" reference; yuck
this.highAvailabilityManager = new HighAvailabilityManagerImpl(this); // TODO leaking "this" reference; yuck
this.entitlementManager = Entitlements.newManager(this, brooklynProperties);
this.configSupplierRegistry = new BasicExternalConfigSupplierRegistry(this); // TODO leaking "this" reference; yuck
}
@Override
public void terminate() {
highAvailabilityManager.stop();
running = false;
rebindManager.stop();
storage.terminate();
// Don't unmanage everything; different entities get given their events at different times
// so can cause problems (e.g. a group finds out that a member is unmanaged, before the
// group itself has been told that it is unmanaged).
}
@Override
public boolean isRunning() {
return running;
}
@Override
public boolean isStartupComplete() {
return startupComplete;
}
@Override
public BrooklynStorage getStorage() {
return storage;
}
@Override
public RebindManager getRebindManager() {
return rebindManager;
}
@Override
public HighAvailabilityManager getHighAvailabilityManager() {
return highAvailabilityManager;
}
@Override
public long getTotalEffectorInvocations() {
return totalEffectorInvocationCount.get();
}
@Override
public ExecutionContext getExecutionContext(Entity e) {
// BEC is a thin wrapper around EM so fine to create a new one here; but make sure it gets the real entity
if (e instanceof AbstractEntity) {
ImmutableSet<Object> tags = ImmutableSet.<Object>of(
BrooklynTaskTags.tagForContextEntity(e),
this
);
return new BasicExecutionContext(MutableMap.of("tags", tags), getExecutionManager());
} else {
return ((EntityInternal)e).getManagementSupport().getExecutionContext();
}
}
@Override
public ExecutionContext getServerExecutionContext() {
// BEC is a thin wrapper around EM so fine to create a new one here
ImmutableSet<Object> tags = ImmutableSet.<Object>of(
this,
BrooklynTaskTags.BROOKLYN_SERVER_TASK_TAG
);
return new BasicExecutionContext(MutableMap.of("tags", tags), getExecutionManager());
}
@Override
public SubscriptionContext getSubscriptionContext(Entity e) {
// BSC is a thin wrapper around SM so fine to create a new one here
return new BasicSubscriptionContext(getSubscriptionManager(), e);
}
@Override
public SubscriptionContext getSubscriptionContext(Location loc) {
// BSC is a thin wrapper around SM so fine to create a new one here
return new BasicSubscriptionContext(getSubscriptionManager(), loc);
}
@Override
public EntityDriverManager getEntityDriverManager() {
return entityDriverManager;
}
@Override
public DownloadResolverManager getEntityDownloadsManager() {
return downloadsManager;
}
@Override
public EntitlementManager getEntitlementManager() {
return entitlementManager;
}
protected abstract void manageIfNecessary(Entity entity, Object context);
@Override
public <T> Task<T> invokeEffector(final Entity entity, final Effector<T> eff, @SuppressWarnings("rawtypes") final Map parameters) {
return runAtEntity(entity, eff, parameters);
}
protected <T> T invokeEffectorMethodLocal(Entity entity, Effector<T> eff, Object args) {
assert isManagedLocally(entity) : "cannot invoke effector method at "+this+" because it is not managed here";
totalEffectorInvocationCount.incrementAndGet();
Object[] transformedArgs = EffectorUtils.prepareArgsForEffector(eff, args);
return GroovyJavaMethods.invokeMethodOnMetaClass(entity, eff.getName(), transformedArgs);
}
/**
* Method for entity to make effector happen with correct semantics (right place, right task context),
* when a method is called on that entity.
* @throws ExecutionException
*/
@Override
public <T> T invokeEffectorMethodSync(final Entity entity, final Effector<T> eff, final Object args) throws ExecutionException {
try {
Task<?> current = Tasks.current();
if (current == null || !entity.equals(BrooklynTaskTags.getContextEntity(current)) || !isManagedLocally(entity)) {
manageIfNecessary(entity, eff.getName());
// Wrap in a task if we aren't already in a task that is tagged with this entity
Task<T> task = runAtEntity( EffectorUtils.getTaskFlagsForEffectorInvocation(entity, eff,
ConfigBag.newInstance().configureStringKey("args", args)),
entity,
new Callable<T>() {
public T call() {
return invokeEffectorMethodLocal(entity, eff, args);
}});
return task.get();
} else {
return invokeEffectorMethodLocal(entity, eff, args);
}
} catch (Exception e) {
// don't need to attach any message or warning because the Effector impl hierarchy does that (see calls to EffectorUtils.handleException)
throw new ExecutionException(e);
}
}
/**
* Whether the master entity record is local, and sensors and effectors can be properly accessed locally.
*/
public abstract boolean isManagedLocally(Entity e);
/**
* Causes the indicated runnable to be run at the right location for the given entity.
*
* Returns the actual task (if it is local) or a proxy task (if it is remote);
* if management for the entity has not yet started this may start it.
*
* @deprecated since 0.6.0 use effectors (or support {@code runAtEntity(Entity, Effector, Map)} if something else is needed);
* (Callable with Map flags is too open-ended, bothersome to support, and not used much)
*/
@Deprecated
public abstract <T> Task<T> runAtEntity(@SuppressWarnings("rawtypes") Map flags, Entity entity, Callable<T> c);
/** Runs the given effector in the right place for the given entity.
* The task is immediately submitted in the background, but also recorded in the queueing context (if present)
* so it appears as a child, but marked inessential so it does not fail the parent task, who will ordinarily
* call {@link Task#get()} on the object and may do their own failure handling.
*/
protected abstract <T> Task<T> runAtEntity(final Entity entity, final Effector<T> eff, @SuppressWarnings("rawtypes") final Map parameters);
@Override
public StringConfigMap getConfig() {
return configMap;
}
@Override
public BrooklynProperties getBrooklynProperties() {
return configMap;
}
private final Object locationRegistrySemaphore = new Object();
@Override
public LocationRegistry getLocationRegistry() {
// NB: can deadlock if synched on whole LMC
synchronized (locationRegistrySemaphore) {
if (locationRegistry==null) locationRegistry = new BasicLocationRegistry(this);
return locationRegistry;
}
}
@Override
public BrooklynCatalog getCatalog() {
if (!getCatalogInitialization().hasRunAnyInitialization()) {
// catalog init is needed; normally this will be done from start sequence,
// but if accessed early -- and in tests -- we will load it here
getCatalogInitialization().setManagementContext(this);
getCatalogInitialization().populateUnofficial(catalog);
}
return catalog;
}
@Override
public BrooklynTypeRegistry getTypeRegistry() {
return typeRegistry;
}
@Override
public ClassLoader getCatalogClassLoader() {
// catalog does not have to be initialized
return catalog.getRootClassLoader();
}
/**
* Optional class-loader that this management context should use as its base,
* as the first-resort in the catalog, and for scanning (if scanning the default in the catalog).
* In most instances the default classloader (ManagementContext.class.getClassLoader(), assuming
* this was in the JARs used at boot time) is fine, and in those cases this method normally returns null.
* (Surefire does some weird stuff, but the default classloader is fine for loading;
* however it requires a custom base classpath to be set for scanning.)
*/
@Override
public ClassLoader getBaseClassLoader() {
return baseClassLoader;
}
/** See {@link #getBaseClassLoader()}. Only settable once and must be invoked before catalog is loaded. */
public void setBaseClassLoader(ClassLoader cl) {
if (baseClassLoader==cl) return;
if (baseClassLoader!=null) throw new IllegalStateException("Cannot change base class loader (in "+this+")");
if (catalog!=null) throw new IllegalStateException("Cannot set base class after catalog has been loaded (in "+this+")");
this.baseClassLoader = cl;
}
/** Optional mechanism for setting the classpath which should be scanned by the catalog, if the catalog
* is scanning the default classpath. Usually it infers the right thing, but some classloaders
* (e.g. surefire) do funny things which the underlying org.reflections.Reflections library can't see in to.
* <p>
* This should normally be invoked early in the server startup. Setting it after the catalog is loaded will not
* take effect without an explicit internal call to do so. Once set, it can be changed prior to catalog loading
* but it cannot be <i>changed</i> once the catalog is loaded.
* <p>
* ClasspathHelper.forJavaClassPath() is often a good argument to pass, and is used internally in some places
* when no items are found on the catalog. */
@Override
public void setBaseClassPathForScanning(Iterable<URL> urls) {
if (Objects.equal(baseClassPathForScanning, urls)) return;
if (baseClassPathForScanning != null) {
if (catalog==null)
log.warn("Changing scan classpath to "+urls+" from "+baseClassPathForScanning);
else
throw new IllegalStateException("Cannot change base class path for scanning (in "+this+")");
}
this.baseClassPathForScanning = urls;
}
/**
* @see #setBaseClassPathForScanning(Iterable)
*/
@Override
public Iterable<URL> getBaseClassPathForScanning() {
return baseClassPathForScanning;
}
public BrooklynGarbageCollector getGarbageCollector() {
return gc;
}
@Override
public void setManagementNodeUri(URI uri) {
this.uri = Maybe.of(checkNotNull(uri, "uri"));
}
@Override
public Maybe<URI> getManagementNodeUri() {
return uri;
}
private Object catalogInitMutex = new Object();
@Override
public CatalogInitialization getCatalogInitialization() {
synchronized (catalogInitMutex) {
if (catalogInitialization!=null) return catalogInitialization;
CatalogInitialization ci = new CatalogInitialization();
setCatalogInitialization(ci);
return ci;
}
}
@Override
public void setCatalogInitialization(CatalogInitialization catalogInitialization) {
synchronized (catalogInitMutex) {
Preconditions.checkNotNull(catalogInitialization, "initialization must not be null");
if (this.catalogInitialization!=null && this.catalogInitialization != catalogInitialization)
throw new IllegalStateException("Changing catalog init from "+this.catalogInitialization+" to "+catalogInitialization+"; changes not permitted");
catalogInitialization.setManagementContext(this);
this.catalogInitialization = catalogInitialization;
}
}
public BrooklynObject lookup(String id) {
return lookup(id, BrooklynObject.class);
}
@SuppressWarnings("unchecked")
public <T extends BrooklynObject> T lookup(String id, Class<T> type) {
Object result;
result = getEntityManager().getEntity(id);
if (result!=null && type.isInstance(result)) return (T)result;
result = getLocationManager().getLocation(id);
if (result!=null && type.isInstance(result)) return (T)result;
// TODO policies, enrichers, feeds
return null;
}
@Override
public List<Throwable> errors() {
return errors;
}
/** @since 0.8.0 */
@Override
public ExternalConfigSupplierRegistry getExternalConfigProviderRegistry() {
return configSupplierRegistry;
}
}