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

brooklyn.entity.rebind.RebindManagerImpl Maven / Gradle / Ivy

package brooklyn.entity.rebind;

import static com.google.common.base.Preconditions.checkNotNull;

import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import brooklyn.entity.Application;
import brooklyn.entity.Entity;
import brooklyn.entity.basic.AbstractApplication;
import brooklyn.entity.basic.Entities;
import brooklyn.entity.proxying.EntitySpec;
import brooklyn.entity.proxying.EntitySpecs;
import brooklyn.entity.proxying.InternalEntityFactory;
import brooklyn.location.Location;
import brooklyn.management.ManagementContext;
import brooklyn.mementos.BrooklynMemento;
import brooklyn.mementos.BrooklynMementoPersister;
import brooklyn.mementos.BrooklynMementoPersister.Delta;
import brooklyn.mementos.EntityMemento;
import brooklyn.mementos.LocationMemento;
import brooklyn.mementos.PolicyMemento;
import brooklyn.policy.Policy;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.javalang.Reflections;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

public class RebindManagerImpl implements RebindManager {

    // TODO Use ImmediateDeltaChangeListener if the period is set to 0?
    // But for MultiFile persister, that is still async
    
    public static final Logger LOG = LoggerFactory.getLogger(RebindManagerImpl.class);

    private volatile long periodicPersistPeriod = 1000;
    
    private volatile boolean running = true;
    
    private final ManagementContext managementContext;

    private volatile PeriodicDeltaChangeListener realChangeListener;
    private volatile ChangeListener changeListener;
    
    private volatile BrooklynMementoPersister persister;

    public RebindManagerImpl(ManagementContext managementContext) {
        this.managementContext = managementContext;
        this.changeListener = ChangeListener.NOOP;
    }

    /**
     * Must be called before setPerister()
     */
    public void setPeriodicPersistPeriod(long periodMillis) {
        this.periodicPersistPeriod = periodMillis;
    }

    @Override
    public void setPersister(BrooklynMementoPersister val) {
        if (persister != null && persister != val) {
            throw new IllegalStateException("Dynamically changing persister is not supported: old="+persister+"; new="+val);
        }
        this.persister = checkNotNull(val, "persister");
        
        if (running) {
            this.realChangeListener = new PeriodicDeltaChangeListener(managementContext.getExecutionManager(), persister, periodicPersistPeriod);
            this.changeListener = new SafeChangeListener(realChangeListener);
        }
    }

    @Override
    public BrooklynMementoPersister getPersister() {
        return persister;
    }
    
    @Override
    public void stop() {
        running = false;
        if (realChangeListener != null) realChangeListener.stop();
        if (persister != null) persister.stop();
    }
    
    @Override
    @VisibleForTesting
     public void waitForPendingComplete(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
        if (persister == null || !running) return;
        realChangeListener.waitForPendingComplete(timeout, unit);
        persister.waitForWritesCompleted(timeout, unit);
    }
    
    @Override
    public ChangeListener getChangeListener() {
        return changeListener;
    }
    
    @Override
    public List rebind(final BrooklynMemento memento) {
        return rebind(memento, getClass().getClassLoader());
    }
    
    @Override
    public List rebind(final BrooklynMemento memento, ClassLoader classLoader) {
        checkNotNull(memento, "memento");
        checkNotNull(classLoader, "classLoader");
        
        Reflections reflections = new Reflections(classLoader);
        Map entities = Maps.newLinkedHashMap();
        Map locations = Maps.newLinkedHashMap();
        Map policies = Maps.newLinkedHashMap();
        
        final RebindContextImpl rebindContext = new RebindContextImpl(classLoader);

        // Instantiate locations
        LOG.info("RebindManager instantiating locations: {}", memento.getLocationIds());
        for (LocationMemento locMemento : memento.getLocationMementos().values()) {
            if (LOG.isTraceEnabled()) LOG.trace("RebindManager instantiating location {}", locMemento);
            
            Location location = newLocation(locMemento, reflections);
            locations.put(locMemento.getId(), location);
            rebindContext.registerLocation(locMemento.getId(), location);
        }
        
        // Instantiate entities
        LOG.info("RebindManager instantiating entities: {}", memento.getEntityIds());
        for (EntityMemento entityMemento : memento.getEntityMementos().values()) {
            if (LOG.isDebugEnabled()) LOG.debug("RebindManager instantiating entity {}", entityMemento);
            
            Entity entity = newEntity(entityMemento, reflections);
            entities.put(entityMemento.getId(), entity);
            rebindContext.registerEntity(entityMemento.getId(), entity);
        }
        
        // Instantiate policies
        LOG.info("RebindManager instantiating policies: {}", memento.getPolicyIds());
        for (PolicyMemento policyMemento : memento.getPolicyMementos().values()) {
            if (LOG.isDebugEnabled()) LOG.debug("RebindManager instantiating policy {}", policyMemento);
            
            Policy policy = newPolicy(policyMemento, reflections);
            policies.put(policyMemento.getId(), policy);
            rebindContext.registerPolicy(policyMemento.getId(), policy);
        }
        
        // Reconstruct locations
        LOG.info("RebindManager reconstructing locations");
        for (LocationMemento locMemento : memento.getLocationMementos().values()) {
            Location location = rebindContext.getLocation(locMemento.getId());
            if (LOG.isDebugEnabled()) LOG.debug("RebindManager reconstructing location {}", locMemento);

            location.getRebindSupport().reconstruct(rebindContext, locMemento);
        }

        // Reconstruct policies
        LOG.info("RebindManager reconstructing policies");
        for (PolicyMemento policyMemento : memento.getPolicyMementos().values()) {
            Policy policy = rebindContext.getPolicy(policyMemento.getId());
            if (LOG.isDebugEnabled()) LOG.debug("RebindManager reconstructing policy {}", policyMemento);

            policy.getRebindSupport().reconstruct(rebindContext, policyMemento);
        }

        // Reconstruct entities
        LOG.info("RebindManager reconstructing entities");
        for (EntityMemento entityMemento : memento.getEntityMementos().values()) {
            Entity entity = rebindContext.getEntity(entityMemento.getId());
            if (LOG.isDebugEnabled()) LOG.debug("RebindManager reconstructing entity {}", entityMemento);

            entity.getRebindSupport().reconstruct(rebindContext, entityMemento);
        }
        
        LOG.info("RebindManager managing locations");
        for (Location location: locations.values()) {
            if (location.getParent()==null) {
                // manage all root locations
                // LocationManager.manage perhaps should not be deprecated, as we need to do this I think?
                managementContext.getLocationManager().manage(location);
            }
        }
        
        // Manage the top-level apps (causing everything under them to become managed)
        LOG.info("RebindManager managing entities");
        for (String appId : memento.getApplicationIds()) {
            Entities.startManagement((Application)rebindContext.getEntity(appId), managementContext);
        }
        
        // Return the top-level applications
        List apps = Lists.newArrayList();
        for (String appId : memento.getApplicationIds()) {
            apps.add((Application)rebindContext.getEntity(appId));
        }
        
        LOG.info("RebindManager complete; return apps: {}", memento.getApplicationIds());
        return apps;
    }
    
    private Entity newEntity(EntityMemento memento, Reflections reflections) {
        String entityId = memento.getId();
        String entityType = checkNotNull(memento.getType(), "entityType of "+entityId);
        Class entityClazz = reflections.loadClass(entityType);
        
        Map flags = Maps.newLinkedHashMap();
        flags.put("id", entityId);
        if (AbstractApplication.class.isAssignableFrom(entityClazz)) flags.put("mgmt", managementContext);
  
        if (InternalEntityFactory.isNewStyleEntity(managementContext, entityClazz)) {
            Class entityInterface = managementContext.getEntityManager().getEntityTypeRegistry().getEntityTypeOf((Class)entityClazz);
            EntitySpec entitySpec = EntitySpecs.spec((Class)entityInterface).impl((Class)entityClazz).configure("id", entityId);
            return managementContext.getEntityManager().createEntity(entitySpec);
        } else {
            // There are several possibilities for the constructor; find one that works.
            // Prefer passing in the flags because required for Application to set the management context
            // TODO Feels very hacky!
            return (Entity) invokeConstructor(reflections, entityClazz, new Object[] {flags}, new Object[] {flags, null}, new Object[] {null}, new Object[0]);
        }
    }
    
    /**
     * Constructs a new location, passing to its constructor the location id and all of memento.getFlags().
     */
    private Location newLocation(LocationMemento memento, Reflections reflections) {
        String locationId = memento.getId();
        String locationType = checkNotNull(memento.getType(), "locationType of "+locationId);
        Class locationClazz = reflections.loadClass(locationType);

        Map flags = MutableMap.builder()
        		.put("id", locationId)
        		.putAll(memento.getLocationConfig())
        		.removeAll(memento.getLocationConfigReferenceKeys())
        		.build();

        return (Location) invokeConstructor(reflections, locationClazz, new Object[] {flags});
        // 'used' config keys get marked in BasicLocationRebindSupport
    }

    /**
     * Constructs a new location, passing to its constructor the location id and all of memento.getFlags().
     */
    private Policy newPolicy(PolicyMemento memento, Reflections reflections) {
        String id = memento.getId();
        String policyType = checkNotNull(memento.getType(), "policyType of "+id);
        Class policyClazz = reflections.loadClass(policyType);

        Map flags = MutableMap.builder()
                .put("id", id)
                .putAll(memento.getFlags())
                .build();

        return (Policy) invokeConstructor(reflections, policyClazz, new Object[] {flags});
    }

    private  T invokeConstructor(Reflections reflections, Class clazz, Object[]... possibleArgs) {
        for (Object[] args : possibleArgs) {
            Constructor constructor = Reflections.findCallabaleConstructor(clazz, args);
            if (constructor != null) {
                constructor.setAccessible(true);
                return reflections.loadInstance(constructor, args);
            }
        }
        throw new IllegalStateException("Cannot instantiate instance of type "+clazz+"; expected constructor signature not found");
    }

    private static class DeltaImpl implements Delta {
        Collection locations = Collections.emptyList();
        Collection entities = Collections.emptyList();
        Collection policies = Collections.emptyList();
        Collection  removedLocationIds = Collections.emptyList();
        Collection  removedEntityIds = Collections.emptyList();
        Collection  removedPolicyIds = Collections.emptyList();
        
        @Override
        public Collection locations() {
            return locations;
        }

        @Override
        public Collection entities() {
            return entities;
        }

        @Override
        public Collection policies() {
            return policies;
        }

        @Override
        public Collection removedLocationIds() {
            return removedLocationIds;
        }

        @Override
        public Collection removedEntityIds() {
            return removedEntityIds;
        }
        
        @Override
        public Collection removedPolicyIds() {
            return removedPolicyIds;
        }
    }
    
    /**
     * Wraps a ChangeListener, to log and never propagate any exceptions that it throws.
     * 
     * Catches Throwable, because really don't want a problem to propagate up to user code,
     * to cause business-level operations to fail. For example, if there is a linkage error
     * due to some problem in the serialization dependencies then just log it. For things
     * more severe (e.g. OutOfMemoryError) then the catch+log means we'll report that we
     * failed to persist, and we'd expect other threads to throw the OutOfMemoryError so
     * we shouldn't lose anything.
     */
    private static class SafeChangeListener implements ChangeListener {
        private final ChangeListener delegate;
        
        public SafeChangeListener(ChangeListener delegate) {
            this.delegate = delegate;
        }
        
        @Override
        public void onManaged(Entity entity) {
            try {
                delegate.onManaged(entity);
            } catch (Throwable t) {
                LOG.error("Error persisting mememento onManaged("+entity+"); continuing.", t);
            }
        }

        @Override
        public void onManaged(Location location) {
            try {
                delegate.onManaged(location);
            } catch (Throwable t) {
                LOG.error("Error persisting mememento onManaged("+location+"); continuing.", t);
            }
        }
        
        @Override
        public void onChanged(Entity entity) {
            try {
                delegate.onChanged(entity);
            } catch (Throwable t) {
                LOG.error("Error persisting mememento onChanged("+entity+"); continuing.", t);
            }
        }
        
        @Override
        public void onUnmanaged(Entity entity) {
            try {
                delegate.onUnmanaged(entity);
            } catch (Throwable t) {
                LOG.error("Error persisting mememento onUnmanaged("+entity+"); continuing.", t);
            }
        }

        @Override
        public void onUnmanaged(Location location) {
            try {
                delegate.onUnmanaged(location);
            } catch (Throwable t) {
                LOG.error("Error persisting mememento onUnmanaged("+location+"); continuing.", t);
            }
        }

        @Override
        public void onChanged(Location location) {
            try {
                delegate.onChanged(location);
            } catch (Throwable t) {
                LOG.error("Error persisting mememento onChanged("+location+"); continuing.", t);
            }
        }
        
        @Override
        public void onChanged(Policy policy) {
            try {
                delegate.onChanged(policy);
            } catch (Throwable t) {
                LOG.error("Error persisting mememento onChanged("+policy+"); continuing.", t);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy