All Downloads are FREE. Search and download functionalities are using the official Maven repository.

brooklyn.management.internal.AbstractManagementContext Maven / Gradle / Ivy

There is a newer version: 0.7.0-M1
Show newest version
package brooklyn.management.internal;

import static java.lang.String.format;

import java.io.FileNotFoundException;
import java.net.URL;
import java.util.Collection;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import brooklyn.catalog.BrooklynCatalog;
import brooklyn.catalog.internal.BasicBrooklynCatalog;
import brooklyn.catalog.internal.CatalogClasspathDo.CatalogScanningModes;
import brooklyn.catalog.internal.CatalogDtoUtils;
import brooklyn.config.BrooklynProperties;
import brooklyn.config.StringConfigMap;
import brooklyn.entity.Effector;
import brooklyn.entity.Entity;
import brooklyn.entity.basic.BrooklynTasks;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.drivers.BasicEntityDriverManager;
import brooklyn.entity.drivers.EntityDriverManager;
import brooklyn.entity.drivers.downloads.BasicDownloadsManager;
import brooklyn.entity.drivers.downloads.DownloadResolverManager;
import brooklyn.entity.rebind.RebindManager;
import brooklyn.entity.rebind.RebindManagerImpl;
import brooklyn.internal.storage.BrooklynStorage;
import brooklyn.internal.storage.BrooklynStorageFactory;
import brooklyn.internal.storage.impl.inmemory.InMemoryBrooklynStorageFactory;
import brooklyn.location.LocationRegistry;
import brooklyn.location.basic.BasicLocationRegistry;
import brooklyn.management.ExecutionContext;
import brooklyn.management.SubscriptionContext;
import brooklyn.management.Task;
import brooklyn.util.GroovyJavaMethods;
import brooklyn.util.ResourceUtils;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.task.BasicExecutionContext;
import brooklyn.util.task.Tasks;
import brooklyn.util.text.Strings;

import com.google.common.base.Function;
import com.google.common.base.Throwables;

public abstract class AbstractManagementContext implements ManagementContextInternal {
    private static final Logger log = LoggerFactory.getLogger(AbstractManagementContext.class);

    private static BrooklynStorageFactory loadBrooklynStorageFactory(BrooklynProperties properties){
        String clazzName = properties.getFirst(BrooklynStorageFactory.class.getName());
        if(clazzName == null){
            clazzName = InMemoryBrooklynStorageFactory.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 BrooklynStorageFactory)){
            throw new IllegalStateException(format("Class [%s] not an instantiate of class [%s]",clazzName, BrooklynStorageFactory .class.getName()));
        }

        return (BrooklynStorageFactory)instance;
    }

    static {
        // ensure that if ResourceUtils is given an entity as context,
        // we use the catalog class loader (e.g. to resolve classpath URLs)
        ResourceUtils.addClassLoaderProvider(new Function() {
            @Override
            public ClassLoader apply(@Nullable Object input) {
                if (input instanceof EntityInternal)
                    return apply(((EntityInternal)input).getManagementSupport());
                if (input instanceof EntityManagementSupport)
                    return apply(((EntityManagementSupport)input).getManagementContext());
                if (input instanceof AbstractManagementContext)
                    return ((AbstractManagementContext)input).getCatalog().getRootClassLoader();
                return null;
            }
        });
    }

    private final AtomicLong totalEffectorInvocationCount = new AtomicLong();

    protected BrooklynProperties configMap;
    protected BasicLocationRegistry locationRegistry;
    protected volatile BasicBrooklynCatalog catalog;
    protected ClassLoader baseClassLoader;
    protected Iterable baseClassPathForScanning;

    // TODO leaking "this" reference; yuck
    private final RebindManager rebindManager = new RebindManagerImpl(this);
    
    protected volatile BrooklynGarbageCollector gc;

    private final EntityDriverManager entityDriverManager;
    
    private final DownloadResolverManager downloadsManager;

    private final BrooklynStorage storage;

    private volatile boolean running = true;

    public   AbstractManagementContext(BrooklynProperties brooklynProperties){
        this(brooklynProperties, null);
    }

    public AbstractManagementContext(BrooklynProperties brooklynProperties, BrooklynStorageFactory storageFactory) {
        this.configMap = brooklynProperties;
        this.entityDriverManager = new BasicEntityDriverManager();
        this.downloadsManager = BasicDownloadsManager.newDefault(configMap);
        if (storageFactory == null) {
            storageFactory = loadBrooklynStorageFactory(brooklynProperties);
        }
        this.storage = storageFactory.newStorage(this);
    }

    public void terminate() {
        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).
    }
    
    public boolean isRunning() {
        return running;
    }

    @Override
    public BrooklynStorage getStorage() {
        return storage;
    }
    
    @Override
    public RebindManager getRebindManager() {
        return rebindManager;
    }

    public long getTotalEffectorInvocations() {
        return totalEffectorInvocationCount.get();
    }
    
    public ExecutionContext getExecutionContext(Entity e) {
        // BEC is a thin wrapper around EM so fine to create a new one here
        return new BasicExecutionContext(MutableMap.of("tag", BrooklynTasks.tagForContextEntity(e)), getExecutionManager());
    }
    
    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 EntityDriverManager getEntityDriverFactory() {
        return getEntityDriverManager();
    }

    @Override
    public EntityDriverManager getEntityDriverManager() {
        return entityDriverManager;
    }

    @Override
    public DownloadResolverManager getEntityDownloadsManager() {
        return downloadsManager;
    }
    
    @Deprecated
    @Override
    public boolean isManaged(Entity e) {
        return getEntityManager().isManaged(e);
    }
    
    @Deprecated
    @Override
    public void manage(Entity e) {
        getEntityManager().manage(e);
    }
    
    @Deprecated
    @Override
    public void unmanage(Entity e) {
        getEntityManager().unmanage(e);
    }

    @Deprecated
    @Override
    public synchronized Collection getEntities() {
        return getEntityManager().getEntities();
    }
    
    @Deprecated
    @Override
    public Entity getEntity(String id) {
        return getEntityManager().getEntity(id);
    }

    protected abstract void manageIfNecessary(Entity entity, Object context);

    public  Task invokeEffector(final Entity entity, final Effector eff, @SuppressWarnings("rawtypes") final Map parameters) {
        return runAtEntity(entity, eff, parameters);
    }
    
    protected  T invokeEffectorMethodLocal(Entity entity, Effector 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 
     */
    public  T invokeEffectorMethodSync(final Entity entity, final Effector eff, final Object args) throws ExecutionException {
        try {
            Task current = Tasks.current();
            if (current == null || !entity.equals(BrooklynTasks.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 task = runAtEntity( EffectorUtils.getTaskFlagsForEffectorInvocation(entity, eff),
                        entity, 
                        new Callable() {
                            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 {@link #runAtEntity(Entity, Task)} if something else is needed);
     * (Callable with Map flags is too open-ended, bothersome to support, and not used much) 
     */
    @Deprecated
    public abstract  Task runAtEntity(@SuppressWarnings("rawtypes") Map flags, Entity entity, Callable c);

    protected abstract  Task runAtEntity(final Entity entity, final Effector eff, @SuppressWarnings("rawtypes") final Map parameters);

    @Override
    public abstract void addEntitySetListener(CollectionChangeListener listener);

    @Override
    public abstract void removeEntitySetListener(CollectionChangeListener listener);
    
    @Override
    public StringConfigMap getConfig() {
        return configMap;
    }

    @Override
    public BrooklynProperties getBrooklynProperties() {
        return configMap;
    }

    @Override
    public synchronized LocationRegistry getLocationRegistry() {
        if (locationRegistry==null) locationRegistry = new BasicLocationRegistry(this);
        return locationRegistry;
    }

    @Override
    public BrooklynCatalog getCatalog() {
        if (catalog==null) loadCatalog();
        return catalog;
    }

    protected synchronized void loadCatalog() {
        if (catalog!=null) return;
        
        BasicBrooklynCatalog catalog = null;
        String catalogUrl = getConfig().getConfig(BROOKLYN_CATALOG_URL);
        
        try {
            if (!Strings.isEmpty(catalogUrl)) {
                catalog = new BasicBrooklynCatalog(this, CatalogDtoUtils.newDtoFromUrl(catalogUrl));
                if (log.isDebugEnabled())
                    log.debug("Loaded catalog from "+catalogUrl+": "+catalog);
            }
        } catch (Exception e) {
            if (Throwables.getRootCause(e) instanceof FileNotFoundException) {
                Object nonDefaultUrl = getConfig().getRawConfig(BROOKLYN_CATALOG_URL);
                if (nonDefaultUrl!=null && !"".equals(nonDefaultUrl)) {
                    log.warn("Could not find catalog XML specified at "+nonDefaultUrl+"; using default (local classpath) catalog. Error was: "+e);
                } else {
                    if (log.isDebugEnabled())
                        log.debug("No default catalog file available; trying again using local classpath to populate catalog. Error was: "+e);
                }
            } else {
                log.warn("Error importing catalog XML at "+catalogUrl+"; using default (local classpath) catalog. Error was: "+e, e);                
            }
        }
        if (catalog==null) {
            // retry, either an error, or was blank
            catalog = new BasicBrooklynCatalog(this, CatalogDtoUtils.newDefaultLocalScanningDto(CatalogScanningModes.ANNOTATIONS));
            if (log.isDebugEnabled())
                log.debug("Loaded default (local classpath) catalog: "+catalog);
        }
        catalog.getCatalog().load(this, null);
        
        this.catalog = catalog;
    }

    /** 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.)
     */
    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.Reflectiosn library can't see in to. 
     * 

* Only settable once, before catalog is loaded. *

* ClasspathHelper.forJavaClassPath() is often a good argument to pass. */ public void setBaseClassPathForScanning(Iterable urls) { if (baseClassPathForScanning == urls) return; if (baseClassPathForScanning != null) throw new IllegalStateException("Cannot change base class path for scanning (in "+this+")"); if (catalog != null) throw new IllegalStateException("Cannot set base class path for scanning after catalog has been loaded (in "+this+")"); this.baseClassPathForScanning = urls; } /** * @see #setBaseClassPathForScanning(Iterable) */ public Iterable getBaseClassPathForScanning() { return baseClassPathForScanning; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy