Merge pull request #853 from aledsage/feature/storage2
storage: using datagrid for rebind
diff --git a/api/src/main/java/brooklyn/entity/rebind/RebindContext.java b/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
index 264b8b0..27e5e79 100644
--- a/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
+++ b/api/src/main/java/brooklyn/entity/rebind/RebindContext.java
@@ -1,5 +1,7 @@
package brooklyn.entity.rebind;
+import java.util.Set;
+
import brooklyn.entity.Entity;
import brooklyn.location.Location;
import brooklyn.policy.Policy;
@@ -21,5 +23,7 @@
public Policy getPolicy(String id);
+ Set<Entity> getEntities();
+
public Class<?> loadClass(String typeName) throws ClassNotFoundException;
}
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
index d22bbb4..7cafd22 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractEntity.java
@@ -3,7 +3,6 @@
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Collection;
-import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -53,6 +52,7 @@
import brooklyn.util.BrooklynLanguageExtensions;
import brooklyn.util.collections.MutableList;
import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.SetFromLiveMap;
import brooklyn.util.flags.FlagUtils;
import brooklyn.util.flags.SetFromFlag;
import brooklyn.util.task.DeferredSupplier;
@@ -244,7 +244,8 @@
@Override
public boolean equals(Object o) {
- return o != null && (o == this || o == selfProxy);
+ return (o == this || o == selfProxy) ||
+ (o instanceof Entity && Objects.equal(id, ((Entity)o).getId()));
}
protected boolean isLegacyConstruction() {
@@ -376,8 +377,8 @@
String oldDisplayName = displayName.get();
parent = managementContext.getStorage().getReference(id+"-parent");
- groups = Collections.newSetFromMap(managementContext.getStorage().<Group,Boolean>getMap(id+"-groups"));
- children = Collections.newSetFromMap(managementContext.getStorage().<Entity,Boolean>getMap(id+"-children"));
+ groups = SetFromLiveMap.create(managementContext.getStorage().<Group,Boolean>getMap(id+"-groups"));
+ children = SetFromLiveMap.create(managementContext.getStorage().<Entity,Boolean>getMap(id+"-children"));
locations = managementContext.getStorage().getNonConcurrentList(id+"-locations");
creationTimeUtc = managementContext.getStorage().getReference(id+"-creationTime");
displayName = managementContext.getStorage().getReference(id+"-displayName");
@@ -468,6 +469,19 @@
}
/**
+ * Called by framework (in new-style entities) when recreating an entity, on restart.
+ * Called after {@link #setManagementContext(ManagementContextInternal)} but before
+ * {@link #onManagementStarting()}.
+ *
+ * To preserve backwards compatibility, the {@linke #getRebindSupport()}'s
+ * {@link BasicEntityRebindSupport#reconstruct(brooklyn.entity.rebind.RebindContext, EntityMemento)}
+ * will call this method.
+ */
+ public void reconstruct() {
+ // no-op
+ }
+
+ /**
* Adds this as a child of the given entity; registers with application if necessary.
*/
@Override
diff --git a/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java b/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
index 9d7e049..728670f 100644
--- a/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
+++ b/core/src/main/java/brooklyn/entity/basic/AbstractGroupImpl.java
@@ -2,7 +2,6 @@
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -10,8 +9,10 @@
import org.slf4j.LoggerFactory;
import brooklyn.entity.Entity;
+import brooklyn.entity.Group;
import brooklyn.entity.trait.Changeable;
import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.util.collections.SetFromLiveMap;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
@@ -42,7 +43,7 @@
Set<Entity> oldMembers = members;
- members = Collections.newSetFromMap(managementContext.getStorage().<Entity,Boolean>getMap(getId()+"-members"));
+ members = SetFromLiveMap.create(managementContext.getStorage().<Entity,Boolean>getMap(getId()+"-members"));
// Only override stored defaults if we have actual values. We might be in setManagementContext
// because we are reconstituting an existing entity in a new brooklyn management-node (in which
@@ -65,7 +66,7 @@
@Override
public boolean addMember(Entity member) {
synchronized (members) {
- member.addGroup(this);
+ member.addGroup((Group)getProxyIfAvailable());
boolean changed = members.add(member);
if (changed) {
log.debug("Group {} got new member {}", this, member);
diff --git a/core/src/main/java/brooklyn/entity/basic/EntityDynamicType.java b/core/src/main/java/brooklyn/entity/basic/EntityDynamicType.java
index ce83c1c..3c0df0a 100644
--- a/core/src/main/java/brooklyn/entity/basic/EntityDynamicType.java
+++ b/core/src/main/java/brooklyn/entity/basic/EntityDynamicType.java
@@ -30,6 +30,7 @@
import brooklyn.util.text.Strings;
import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
import com.google.common.base.Throwables;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
@@ -425,6 +426,10 @@
this.field = field;
this.value = value;
}
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this).add("field", field).add("value", value).toString();
+ }
}
private static <V> V value(FieldAndValue<V> fv) {
diff --git a/core/src/main/java/brooklyn/entity/proxying/EntityProxy.java b/core/src/main/java/brooklyn/entity/proxying/EntityProxy.java
index afbab69..a09cb32 100644
--- a/core/src/main/java/brooklyn/entity/proxying/EntityProxy.java
+++ b/core/src/main/java/brooklyn/entity/proxying/EntityProxy.java
@@ -1,5 +1,6 @@
package brooklyn.entity.proxying;
+
/**
* Marker interface, indicating that this is a proxy to an entity.
*
diff --git a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
index 285013d..f7249d0 100644
--- a/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
+++ b/core/src/main/java/brooklyn/entity/proxying/EntityProxyImpl.java
@@ -26,7 +26,7 @@
*
* @author aled
*/
-public class EntityProxyImpl implements java.lang.reflect.InvocationHandler {
+public class EntityProxyImpl implements java.lang.reflect.InvocationHandler, EntityProxy {
// TODO Currently the proxy references the real entity and invokes methods on it directly.
// As we work on remoting/distribution, this will be replaced by RPC.
@@ -43,6 +43,13 @@
}
}
+ private static final Set<MethodSignature> ENTITY_PROXY_METHODS = Sets.newLinkedHashSet();
+ static {
+ for (Method m : EntityProxy.class.getMethods()) {
+ ENTITY_PROXY_METHODS.add(new MethodSignature(m));
+ }
+ }
+
private static final Set<MethodSignature> ENTITY_NON_EFFECTOR_METHODS = Sets.newLinkedHashSet();
static {
for (Method m : Entity.class.getMethods()) {
@@ -65,6 +72,7 @@
return delegate.toString();
}
+ @Override
public Object invoke(Object proxy, final Method m, final Object[] args) throws Throwable {
if (proxy == null) {
throw new IllegalArgumentException("Static methods not supported via proxy on entity "+delegate);
@@ -75,6 +83,8 @@
Object result;
if (OBJECT_METHODS.contains(sig)) {
result = m.invoke(this, args);
+ } else if (ENTITY_PROXY_METHODS.contains(sig)) {
+ result = m.invoke(this, args);
} else if (ENTITY_NON_EFFECTOR_METHODS.contains(sig)) {
result = m.invoke(delegate, args);
} else {
diff --git a/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java b/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java
index d8b2b7f..afd74a0 100644
--- a/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java
+++ b/core/src/main/java/brooklyn/entity/proxying/InternalEntityFactory.java
@@ -1,9 +1,11 @@
package brooklyn.entity.proxying;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
import java.util.Map;
import java.util.Set;
@@ -99,19 +101,19 @@
this.entityTypeRegistry = checkNotNull(entityTypeRegistry, "entityTypeRegistry");
}
- @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()
- .addAll(EntityProxy.class, Entity.class, EntityLocal.class, EntityInternal.class);
+ .addAll(EntityProxy.class, Entity.class, EntityLocal.class, EntityInternal.class)
+ .addAll(spec.getAdditionalInterfaces());
+
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");
+ log.warn("Deprecated use of 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(
@@ -129,20 +131,14 @@
try {
Class<? extends T> clazz = getImplementedBy(spec);
- FactoryConstructionTracker.setConstructing();
- T entity;
- try {
- entity = construct(clazz, spec);
- } finally {
- FactoryConstructionTracker.reset();
- }
+ T entity = constructEntity(clazz, spec);
if (spec.getDisplayName()!=null)
((AbstractEntity)entity).setDisplayName(spec.getDisplayName());
if (isNewStyleEntity(clazz)) {
- ((AbstractEntity)entity).setManagementContext(managementContext);
((AbstractEntity)entity).setProxy(createEntityProxy(spec, entity));
+ ((AbstractEntity)entity).setManagementContext(managementContext);
((AbstractEntity)entity).configure(MutableMap.copyOf(spec.getFlags()));
}
@@ -167,22 +163,60 @@
}
}
- private <T extends Entity> Class<? extends T> getImplementedBy(EntitySpec<T> spec) {
- if (spec.getImplementation() != null) {
- return spec.getImplementation();
- } else {
- return entityTypeRegistry.getImplementedBy(spec.getType());
+ public void reconstituteEntity(Entity entity) {
+ checkArgument(!(entity instanceof EntityProxy), "entity %s must not be entity proxy", entity);
+ Class<?>[] additionalInterfaces = entity.getClass().getInterfaces();
+
+ try {
+ EntitySpec<Entity> entitySpec = EntitySpecs.spec(Entity.class).additionalInterfaces(additionalInterfaces);
+ ((AbstractEntity)entity).setProxy(createEntityProxy(entitySpec, entity));
+ ((AbstractEntity)entity).setManagementContext(managementContext);
+
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
}
}
- private <T extends Entity> T construct(Class<? extends T> clazz, EntitySpec<T> spec) throws InstantiationException, IllegalAccessException, InvocationTargetException {
- if (isNewStyleEntity(clazz)) {
- return clazz.newInstance();
- } else {
- return constructOldStyle(clazz, MutableMap.copyOf(spec.getFlags()));
+ /**
+ * 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);
@@ -194,4 +228,12 @@
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());
+ }
+ }
}
diff --git a/core/src/main/java/brooklyn/entity/proxying/InternalLocationFactory.java b/core/src/main/java/brooklyn/entity/proxying/InternalLocationFactory.java
index 9da8c5c..0b04301 100644
--- a/core/src/main/java/brooklyn/entity/proxying/InternalLocationFactory.java
+++ b/core/src/main/java/brooklyn/entity/proxying/InternalLocationFactory.java
@@ -146,4 +146,24 @@
throw new IllegalStateException("No valid constructor defined for "+clazz+" (expected no-arg or single java.util.Map argument)");
}
}
+
+ /**
+ * Constructs a new-style location (fails if no no-arg constructor).
+ */
+ public <T extends Location> T constructLocation(Class<T> clazz) {
+ try {
+ FactoryConstructionTracker.setConstructing();
+ try {
+ if (isNewStyleLocation(clazz)) {
+ return clazz.newInstance();
+ } else {
+ throw new IllegalStateException("Location class "+clazz+" must have a no-arg constructor");
+ }
+ } finally {
+ FactoryConstructionTracker.reset();
+ }
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
+ }
+ }
}
diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
index fe1b103..b285ed6 100644
--- a/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
+++ b/core/src/main/java/brooklyn/entity/rebind/BasicEntityRebindSupport.java
@@ -12,6 +12,7 @@
import brooklyn.config.ConfigKey;
import brooklyn.entity.Entity;
import brooklyn.entity.Group;
+import brooklyn.entity.basic.AbstractEntity;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.basic.EntityLocal;
import brooklyn.entity.rebind.dto.MementosGenerators;
@@ -101,6 +102,12 @@
addLocations(rebindContext, memento);
doReconstruct(rebindContext, memento);
+
+ if (entity instanceof AbstractEntity) {
+ ((AbstractEntity)entity).reconstruct();
+ } else {
+ LOG.info("rebind() not being called, because entity is not of type AbstractEntity: type={}; entity={}", entity.getClass(), entity);
+ }
}
/**
diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
index 2a91541..87af702 100644
--- a/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
+++ b/core/src/main/java/brooklyn/entity/rebind/BasicLocationRebindSupport.java
@@ -9,14 +9,14 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.collect.Sets;
-
import brooklyn.entity.rebind.dto.MementosGenerators;
import brooklyn.location.Location;
import brooklyn.location.basic.AbstractLocation;
import brooklyn.mementos.LocationMemento;
import brooklyn.util.flags.FlagUtils;
+import com.google.common.collect.Sets;
+
public class BasicLocationRebindSupport implements RebindSupport<LocationMemento> {
protected static final Logger LOG = LoggerFactory.getLogger(BasicLocationRebindSupport.class);
@@ -71,6 +71,12 @@
addChildren(rebindContext, memento);
doReconsruct(rebindContext, memento);
+
+ if (location instanceof AbstractLocation) {
+ ((AbstractLocation)location).reconstruct();
+ } else {
+ LOG.info("rebind() not being called, because location is not of type AbstractLocation: type={}; location={}", location.getClass(), location);
+ }
}
protected void addChildren(RebindContext rebindContext, LocationMemento memento) {
diff --git a/core/src/main/java/brooklyn/entity/rebind/BasicPolicyRebindSupport.java b/core/src/main/java/brooklyn/entity/rebind/BasicPolicyRebindSupport.java
index 33f2796..0f7ac7b 100644
--- a/core/src/main/java/brooklyn/entity/rebind/BasicPolicyRebindSupport.java
+++ b/core/src/main/java/brooklyn/entity/rebind/BasicPolicyRebindSupport.java
@@ -9,6 +9,7 @@
import org.slf4j.LoggerFactory;
import brooklyn.entity.rebind.dto.MementosGenerators;
+import brooklyn.location.basic.AbstractLocation;
import brooklyn.mementos.PolicyMemento;
import brooklyn.policy.basic.AbstractPolicy;
@@ -47,6 +48,12 @@
policy.setName(memento.getDisplayName());
doReconsruct(rebindContext, memento);
+
+ if (policy instanceof AbstractPolicy) {
+ ((AbstractPolicy)policy).reconstruct();
+ } else {
+ LOG.info("rebind() not being called, because policy is not of type AbstractPolicy: type={}; policy={}", policy.getClass(), policy);
+ }
}
/**
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
index 90a3bb4..8b74621 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindContextImpl.java
@@ -1,11 +1,13 @@
package brooklyn.entity.rebind;
import java.util.Map;
+import java.util.Set;
import brooklyn.entity.Entity;
import brooklyn.location.Location;
import brooklyn.policy.Policy;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
public class RebindContextImpl implements RebindContext {
@@ -32,6 +34,11 @@
}
@Override
+ public Set<Entity> getEntities() {
+ return ImmutableSet.copyOf(entities.values());
+ }
+
+ @Override
public Entity getEntity(String id) {
return entities.get(id);
}
diff --git a/core/src/main/java/brooklyn/event/basic/AttributeMap.java b/core/src/main/java/brooklyn/event/basic/AttributeMap.java
index c0dc41c..f34d7bb 100644
--- a/core/src/main/java/brooklyn/event/basic/AttributeMap.java
+++ b/core/src/main/java/brooklyn/event/basic/AttributeMap.java
@@ -26,7 +26,9 @@
static final Logger log = LoggerFactory.getLogger(AttributeMap.class);
- private final static Object NULL = new Object();
+ private static enum Marker {
+ NULL;
+ }
private final AbstractEntity entity;
@@ -149,11 +151,11 @@
@SuppressWarnings("unchecked")
private <T> T typedNull() {
- return (T) NULL;
+ return (T) Marker.NULL;
}
@SuppressWarnings("unchecked")
private boolean isNull(Object t) {
- return t == NULL;
+ return t == Marker.NULL;
}
}
diff --git a/core/src/main/java/brooklyn/internal/rebind/RebindFromDatagridManagerImpl.java b/core/src/main/java/brooklyn/internal/rebind/RebindFromDatagridManagerImpl.java
new file mode 100644
index 0000000..55d2d3e
--- /dev/null
+++ b/core/src/main/java/brooklyn/internal/rebind/RebindFromDatagridManagerImpl.java
@@ -0,0 +1,390 @@
+package brooklyn.internal.rebind;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+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.AbstractEntity;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.proxying.EntitySpec;
+import brooklyn.entity.proxying.EntitySpecs;
+import brooklyn.entity.proxying.InternalEntityFactory;
+import brooklyn.entity.proxying.InternalLocationFactory;
+import brooklyn.entity.rebind.ChangeListener;
+import brooklyn.entity.rebind.RebindContextImpl;
+import brooklyn.internal.storage.BrooklynStorage;
+import brooklyn.internal.storage.Reference;
+import brooklyn.location.Location;
+import brooklyn.location.basic.AbstractLocation;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.management.internal.ManagementContextInternal;
+import brooklyn.mementos.BrooklynMementoPersister.Delta;
+import brooklyn.mementos.EntityMemento;
+import brooklyn.mementos.LocationMemento;
+import brooklyn.mementos.PolicyMemento;
+import brooklyn.policy.Policy;
+import brooklyn.policy.basic.AbstractPolicy;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.collections.SetFromLiveMap;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.flags.FlagUtils;
+import brooklyn.util.javalang.Reflections;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+public class RebindFromDatagridManagerImpl {
+
+ public static final Logger LOG = LoggerFactory.getLogger(RebindFromDatagridManagerImpl.class);
+
+ private volatile boolean running = true;
+
+ private final ManagementContextInternal managementContext;
+ private final BrooklynStorage storage;
+ private final Map<String,String> entityTypes;
+ private final Set<String> applicationIds;
+ private final Map<String,String> locationTypes;
+ private final Map<String,String> policyTypes;
+
+ public RebindFromDatagridManagerImpl(ManagementContextInternal managementContext) {
+ this.managementContext = managementContext;
+ storage = ((ManagementContextInternal)managementContext).getStorage();
+ entityTypes = storage.getMap("entities");
+ locationTypes = storage.getMap("locations");
+ applicationIds = SetFromLiveMap.create(storage.<String,Boolean>getMap("applications"));
+ policyTypes = storage.getMap("policies");
+ }
+
+ //@Override
+ public void stop() {
+ running = false;
+ }
+
+ //@Override
+ @VisibleForTesting
+ public void waitForPendingComplete(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
+ // FIXME
+ if (!running) return;
+ //storage.waitForWritesCompleted(timeout, unit);
+ }
+
+ //@Override
+ public List<Application> rebind() {
+ return rebind(getClass().getClassLoader());
+ }
+
+ //@Override
+ public List<Application> rebind(ClassLoader classLoader) {
+ checkNotNull(classLoader, "classLoader");
+
+ Reflections reflections = new Reflections(classLoader);
+ Map<String,Entity> entities = Maps.newLinkedHashMap();
+ Map<String,Location> locations = Maps.newLinkedHashMap();
+ Map<String,Policy> policies = Maps.newLinkedHashMap();
+
+ final RebindContextImpl rebindContext = new RebindContextImpl(classLoader);
+
+ // Instantiate locations
+ LOG.info("RebindManager instantiating locations: {}", locationTypes.keySet());
+ for (Map.Entry<String,String> entry : locationTypes.entrySet()) {
+ String id = entry.getKey();
+ String type = entry.getValue();
+ if (LOG.isTraceEnabled()) LOG.trace("RebindManager instantiating location {} ({})", id, type);
+
+ Location location = newLocation(id, type, reflections);
+ locations.put(id, location);
+ rebindContext.registerLocation(id, location);
+ ((LocalManagementContext)managementContext).prePreManage(location); // FIXME
+ }
+
+ // Instantiate entities
+ LOG.info("RebindManager instantiating entities: {}", entityTypes.keySet());
+ for (Map.Entry<String, String> entry : entityTypes.entrySet()) {
+ String id = entry.getKey();
+ String type = entry.getValue();
+ if (LOG.isDebugEnabled()) LOG.debug("RebindManager instantiating entity {} ({})", id, type);
+
+ Entity entity = newEntity(id, type, reflections);
+ entities.put(id, entity);
+ rebindContext.registerEntity(id, entity);
+ ((LocalManagementContext)managementContext).prePreManage(entity); // FIXME
+ }
+
+ // Instantiate policies
+ LOG.info("RebindManager instantiating policies: {}", policyTypes.keySet());
+ for (Map.Entry<String, String> entry : policyTypes.entrySet()) {
+ String id = entry.getKey();
+ String type = entry.getValue();
+ if (LOG.isDebugEnabled()) LOG.debug("RebindManager instantiating policy {} ({})", id, type);
+
+ Policy policy = newPolicy(id, type, reflections);
+ policies.put(id, policy);
+ rebindContext.registerPolicy(id, policy);
+ }
+
+ // Reconstruct locations
+ LOG.info("RebindManager reconstructing locations");
+ for (String id : locationTypes.keySet()) {
+ Location location = rebindContext.getLocation(id);
+ if (LOG.isDebugEnabled()) LOG.debug("RebindManager reconstructing location {}", id);
+
+ ((AbstractLocation)location).setManagementContext(managementContext);
+ ((AbstractLocation)location).reconstruct();
+ }
+
+ // Reconstruct policies
+ LOG.info("RebindManager reconstructing policies");
+ for (String id : policyTypes.keySet()) {
+ Policy policy = rebindContext.getPolicy(id);
+ if (LOG.isDebugEnabled()) LOG.debug("RebindManager reconstructing policy {}", id);
+
+ ((AbstractPolicy)policy).reconstruct();
+ }
+
+ // Reconstruct entities
+ LOG.info("RebindManager reconstructing entities");
+ for (String id : entityTypes.keySet()) {
+ Entity entity = rebindContext.getEntity(id);
+ if (LOG.isDebugEnabled()) LOG.debug("RebindManager reconstructing entity {}", id);
+
+ ((AbstractEntity)entity).setManagementContext(managementContext);
+ ((AbstractEntity)entity).reconstruct();
+ }
+
+ // Manage the top-level locations (causing everything under them to become managed)
+ LOG.info("RebindManager managing locations");
+ for (Location location : findTopLevelLocations(locations.values())) {
+ Entities.manage(location, managementContext);
+ }
+
+ // Manage the top-level apps (causing everything under them to become managed)
+ LOG.info("RebindManager managing entities");
+ List<Application> apps = Lists.newArrayList();
+ for (String appId : applicationIds) {
+ Application app = (Application) rebindContext.getEntity(appId);
+ Entities.startManagement(app, managementContext);
+ apps.add(app);
+ }
+
+ LOG.info("RebindManager complete; return apps: {}", applicationIds);
+ return Collections.unmodifiableList(apps);
+ }
+
+ private List<Location> findTopLevelLocations(Collection<Location> locations) {
+ List<Location> result = new ArrayList<Location>();
+ for (Location contender : locations) {
+ if (contender.getParentLocation() == null) {
+ result.add(contender);
+ }
+ }
+ return result;
+ }
+
+ private Entity newEntity(String entityId, String entityType, Reflections reflections) {
+ Class<? extends Entity> entityClazz = (Class<? extends Entity>) reflections.loadClass(entityType);
+
+ Class<?>[] additionalInterfaces = entityClazz.getInterfaces();
+
+ try {
+ InternalEntityFactory entityFactory = ((ManagementContextInternal)managementContext).getEntityFactory();
+ Entity entity = entityFactory.constructEntity(entityClazz);
+
+ FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", entityId), entity);
+
+ EntitySpec<Entity> entitySpec = EntitySpecs.spec(Entity.class).additionalInterfaces(additionalInterfaces);
+ ((AbstractEntity)entity).setProxy(entityFactory.createEntityProxy(entitySpec, entity));
+
+ return entity;
+
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
+ }
+ }
+
+ /**
+ * Constructs a new location, passing to its constructor the location id and all of memento.getFlags().
+ */
+ private Location newLocation(String locationId, String locationType, Reflections reflections) {
+ Class<? extends Location> locationClazz = (Class<? extends Location>) reflections.loadClass(locationType);
+
+ // TODO Move this code inside location?
+ // FIXME What about config that refers to other location objects? Those won't have been instantiated yet.
+ // Need to separate constructor from configure
+ Reference<String> locationDisplayName = storage.getReference("location-"+locationId+"-displayName");
+ Map<String,?> locationConfig = storage.getMap("location-"+locationId+"-config");
+
+ InternalLocationFactory locationFactory = ((ManagementContextInternal)managementContext).getLocationFactory();
+ Location location = locationFactory.constructLocation(locationClazz);
+
+ FlagUtils.setFieldsFromFlags(ImmutableMap.of("id", locationId), location);
+
+ return location;
+ }
+
+ /**
+ * Constructs a new location, passing to its constructor the location id and all of memento.getFlags().
+ */
+ private Policy newPolicy(String policyId, String policyType, Reflections reflections) {
+ Class<?> policyClazz = reflections.loadClass(policyType);
+
+ // TODO Move this code inside location?
+ // FIXME What about config that refers to other location/entity/policy objects? Those won't have been instantiated yet.
+ // Need to separate constructor from configure
+ Reference<String> policyDisplayName = storage.getReference("policy-"+policyId+"-displayName");
+ Map<String,?> policyConfig = storage.getMap("policy-"+policyId+"-config");
+
+ Map<String, Object> flags = MutableMap.<String, Object>builder()
+ .put("id", policyId)
+ .putIfNotNull("displayName", policyDisplayName.get())
+ .putAll(policyConfig)
+ .build();
+
+ return (Policy) invokeConstructor(reflections, policyClazz, new Object[] {flags});
+ }
+
+ private <T> T invokeConstructor(Reflections reflections, Class<T> clazz, Object[]... possibleArgs) {
+ for (Object[] args : possibleArgs) {
+ Constructor<T> 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<LocationMemento> locations = Collections.emptyList();
+ Collection<EntityMemento> entities = Collections.emptyList();
+ Collection<PolicyMemento> policies = Collections.emptyList();
+ Collection <String> removedLocationIds = Collections.emptyList();
+ Collection <String> removedEntityIds = Collections.emptyList();
+ Collection <String> removedPolicyIds = Collections.emptyList();
+
+ @Override
+ public Collection<LocationMemento> locations() {
+ return locations;
+ }
+
+ @Override
+ public Collection<EntityMemento> entities() {
+ return entities;
+ }
+
+ @Override
+ public Collection<PolicyMemento> policies() {
+ return policies;
+ }
+
+ @Override
+ public Collection<String> removedLocationIds() {
+ return removedLocationIds;
+ }
+
+ @Override
+ public Collection<String> removedEntityIds() {
+ return removedEntityIds;
+ }
+
+ @Override
+ public Collection<String> 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);
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/brooklyn/internal/storage/DataGrid.java b/core/src/main/java/brooklyn/internal/storage/DataGrid.java
index 25f89ff..b43ac96 100644
--- a/core/src/main/java/brooklyn/internal/storage/DataGrid.java
+++ b/core/src/main/java/brooklyn/internal/storage/DataGrid.java
@@ -4,6 +4,8 @@
public interface DataGrid {
+ <S,T> void registerSerializer(Serializer<S,T> serializer, Class<S> originalClazz, Class<T> serializedClazz);
+
/**
* If a map already exists with this id, returns it; otherwise creates a new map stored
* in the datagrid.
diff --git a/core/src/main/java/brooklyn/internal/storage/Serializer.java b/core/src/main/java/brooklyn/internal/storage/Serializer.java
new file mode 100644
index 0000000..a37d879
--- /dev/null
+++ b/core/src/main/java/brooklyn/internal/storage/Serializer.java
@@ -0,0 +1,8 @@
+package brooklyn.internal.storage;
+
+public interface Serializer<S, T> {
+
+ T serialize(S orig);
+
+ S deserialize(T serializedForm);
+}
diff --git a/core/src/main/java/brooklyn/internal/storage/impl/ConcurrentMapAcceptingNullVals.java b/core/src/main/java/brooklyn/internal/storage/impl/ConcurrentMapAcceptingNullVals.java
index d301f19..c4a21f9 100644
--- a/core/src/main/java/brooklyn/internal/storage/impl/ConcurrentMapAcceptingNullVals.java
+++ b/core/src/main/java/brooklyn/internal/storage/impl/ConcurrentMapAcceptingNullVals.java
@@ -2,6 +2,7 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import java.io.Serializable;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
@@ -23,7 +24,7 @@
*
* @author aled
*/
-public class ConcurrentMapAcceptingNullVals<K, V> implements ConcurrentMap<K, V> {
+public class ConcurrentMapAcceptingNullVals<K, V> implements ConcurrentMap<K, V>, Serializable {
private static enum Marker {
NULL;
@@ -87,6 +88,8 @@
@Override
public V put(K key, V value) {
+ if (!(key == null || key instanceof Serializable)) throw new RuntimeException("NotSerializableException: key="+key+"; value="+value); // FIXME; remove fail-fast
+ if (!(value == null || value instanceof Serializable)) throw new RuntimeException("NotSerializableException: key="+key+"; value="+value); // FIXME; remove fail-fast
return (V) fromNonNullValue(delegate.put(key, (V) toNonNullValue(value)));
}
@@ -109,6 +112,8 @@
@Override
public V putIfAbsent(K key, V value) {
+ if (!(key == null || key instanceof Serializable)) throw new RuntimeException("NotSerializableException: key="+key+"; value="+value); // FIXME; remove fail-fast
+ if (!(value == null || value instanceof Serializable)) throw new RuntimeException("NotSerializableException: key="+key+"; value="+value); // FIXME; remove fail-fast
return (V) fromNonNullValue(delegate.putIfAbsent(key, (V) toNonNullValue(value)));
}
diff --git a/core/src/main/java/brooklyn/internal/storage/impl/ConcurrentMapAcceptingNullVals2.java b/core/src/main/java/brooklyn/internal/storage/impl/ConcurrentMapAcceptingNullVals2.java
new file mode 100644
index 0000000..336f86e
--- /dev/null
+++ b/core/src/main/java/brooklyn/internal/storage/impl/ConcurrentMapAcceptingNullVals2.java
@@ -0,0 +1,275 @@
+package brooklyn.internal.storage.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.annotation.Nullable;
+
+import brooklyn.internal.storage.Serializer;
+
+/**
+ * A decorator for a ConcurrentMap that allows null keys and values to be used.
+ *
+ * It also accepts a serializer that transforms the underlying keys/values stored
+ * in the backing map.
+ *
+ * However, {@link #values()} and {@link #entrySet()} return immutable snapshots
+ * of the map's contents. This may be revisited in a future version.
+ *
+ * @author aled
+ */
+public class ConcurrentMapAcceptingNullVals2<K, V> implements ConcurrentMap<K, V> {
+
+ private static enum Marker {
+ NULL;
+ }
+
+ private final ConcurrentMap<K, V> delegate;
+ private final Serializer<Object,Object> serializer;
+
+ public ConcurrentMapAcceptingNullVals2(ConcurrentMap<K,V> delegate, Serializer<Object,Object> serializer) {
+ this.delegate = checkNotNull(delegate, "delegate");
+ this.serializer = checkNotNull(serializer, "serializer");
+ }
+
+ @Override
+ public void clear() {
+ delegate.clear();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return delegate.containsKey(toKey(key));
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return delegate.containsValue(toValue(value));
+ }
+
+ @Override
+ public Set<Map.Entry<K, V>> entrySet() {
+ // Note that returns an immutable snapshot
+ Set<Map.Entry<K, V>> result = new LinkedHashSet<Map.Entry<K, V>>(delegate.size());
+ for (Map.Entry<K, V> entry : delegate.entrySet()) {
+ result.add(new AbstractMap.SimpleEntry<K,V>((K)fromKey(entry.getKey()), (V)fromValue(entry.getValue())));
+ }
+ return Collections.unmodifiableSet(result);
+ }
+
+ @Override
+ public Collection<V> values() {
+ // Note that returns an immutable snapshot
+ List<V> result = new ArrayList<V>(delegate.size());
+ for (V v : delegate.values()) {
+ result.add((V)fromValue(v));
+ }
+ return Collections.unmodifiableCollection(result);
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return new KeySet<K>(delegate.keySet());
+ }
+
+ @Override
+ public V get(Object key) {
+ return (V) fromValue(delegate.get(key));
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return delegate.isEmpty();
+ }
+
+ @Override
+ public V put(K key, V value) {
+ return (V) fromValue(delegate.put((K)toKey(key), (V) toValue(value)));
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> vals) {
+ for (Map.Entry<? extends K, ? extends V> entry : vals.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public V remove(Object key) {
+ return (V) fromValue(delegate.remove(toKey(key)));
+ }
+
+ @Override
+ public int size() {
+ return delegate.size();
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ return (V) fromValue(delegate.putIfAbsent((K)toKey(key), (V) toValue(value)));
+ }
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ return delegate.remove((K) toKey(key), (V) toValue(value));
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ return (V) fromValue(delegate.replace((K) toKey(key), (V) toValue(value)));
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ return delegate.replace((K) toKey(key), (V) toValue(oldValue), (V) toValue(newValue));
+ }
+
+ private class KeySet<T> extends AbstractSet<T> {
+
+ private final Set<T> delegate;
+
+ KeySet(Set<T> delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void clear() {
+ delegate.clear();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return delegate.contains(toKey(o));
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ for (Object e : c) {
+ if (!delegate.contains(toKey(e))) return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return delegate.isEmpty();
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ // Note that returns an immutable snapshot
+ List<T> result = new ArrayList<T>(delegate.size());
+ for (T k : delegate) {
+ result.add((T)fromKey(k));
+ }
+ return Collections.unmodifiableList(result).iterator();
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return delegate.remove(toKey(o));
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ boolean result = false;
+ for (Object e : c) {
+ result = result & delegate.remove(toKey(e));
+ }
+ return result;
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ boolean result = false;
+ for (Iterator<T> iter = delegate.iterator(); iter.hasNext();) {
+ T e = iter.next();
+ if (!c.contains(fromKey(e))) {
+ iter.remove();
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public int size() {
+ return delegate.size();
+ }
+
+ @Override
+ public Object[] toArray() {
+ Object[] result = delegate.toArray();
+ for (int i = 0; i < result.length; i++) {
+ result[i] = fromKey(result[i]);
+ }
+ return result;
+ }
+
+ @Override
+ public <T> T[] toArray(T[] a) {
+ // TODO Not type-safe if serializer is making changes
+ T[] result = delegate.toArray(a);
+ for (int i = 0; i < result.length; i++) {
+ result[i] = (T) fromKey(result[i]);
+ }
+ return result;
+ }
+
+ }
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ // copied from guava's non-public method Maps.equalsImpl
+ if (this == object) {
+ return true;
+ }
+ if (object instanceof Map) {
+ Map<?, ?> o = (Map<?, ?>) object;
+ return entrySet().equals(o.entrySet());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ // copied from guava's ImmutableMap.hashCode
+ return entrySet().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return delegate.toString();
+ }
+
+ private Object toValue(Object value) {
+ Object v2 = serializer.serialize(value);
+ return ((v2 != null) ? v2 : Marker.NULL);
+ }
+
+ private Object fromValue(Object value) {
+ Object v2 = (value == Marker.NULL) ? null : value;
+ return serializer.deserialize(v2);
+ }
+
+ private Object toKey(Object value) {
+ Object v2 = serializer.serialize(value);
+ return ((v2 != null) ? v2 : Marker.NULL);
+ }
+
+ private Object fromKey(Object value) {
+ Object v2 = (value == Marker.NULL) ? null : value;
+ return serializer.deserialize(v2);
+ }
+}
diff --git a/core/src/main/java/brooklyn/internal/storage/impl/EntitySerializer.java b/core/src/main/java/brooklyn/internal/storage/impl/EntitySerializer.java
new file mode 100644
index 0000000..0118bb2
--- /dev/null
+++ b/core/src/main/java/brooklyn/internal/storage/impl/EntitySerializer.java
@@ -0,0 +1,52 @@
+package brooklyn.internal.storage.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Serializable;
+
+import brooklyn.entity.Entity;
+import brooklyn.internal.storage.Serializer;
+import brooklyn.internal.storage.impl.EntitySerializer.EntityPointer;
+import brooklyn.management.internal.LocalEntityManager;
+import brooklyn.management.internal.ManagementContextInternal;
+
+import com.google.common.base.Objects;
+
+public class EntitySerializer implements Serializer<Entity, EntityPointer> {
+
+ public static class EntityPointer implements Serializable {
+ private static final long serialVersionUID = -3358568001462543001L;
+
+ final String id;
+
+ public EntityPointer(String id) {
+ this.id = checkNotNull(id, "id");
+ }
+ @Override public String toString() {
+ return Objects.toStringHelper(this).add("id", id).toString();
+ }
+ @Override public int hashCode() {
+ return id.hashCode();
+ }
+ @Override public boolean equals(Object obj) {
+ return (obj instanceof EntityPointer) && id.equals(((EntityPointer)obj).id);
+ }
+ }
+
+ private final ManagementContextInternal managementContext;
+
+ public EntitySerializer(ManagementContextInternal managementContext) {
+ this.managementContext = managementContext;
+ }
+
+ @Override
+ public EntityPointer serialize(Entity orig) {
+ return new EntityPointer(orig.getId());
+ }
+
+ @Override
+ public Entity deserialize(EntityPointer serializedForm) {
+ Entity result = ((LocalEntityManager)managementContext.getEntityManager()).getEntityEvenIfPreManaged(serializedForm.id);
+ return checkNotNull(result, "no entity with id %s", serializedForm.id);
+ }
+}
diff --git a/core/src/main/java/brooklyn/internal/storage/impl/InmemoryDatagrid.java b/core/src/main/java/brooklyn/internal/storage/impl/InmemoryDatagrid.java
index e7172e1..88e4534 100644
--- a/core/src/main/java/brooklyn/internal/storage/impl/InmemoryDatagrid.java
+++ b/core/src/main/java/brooklyn/internal/storage/impl/InmemoryDatagrid.java
@@ -1,10 +1,24 @@
package brooklyn.internal.storage.impl;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
-import brooklyn.internal.storage.DataGrid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import brooklyn.internal.storage.DataGrid;
+import brooklyn.internal.storage.Serializer;
+import brooklyn.util.exceptions.Exceptions;
+
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
/**
@@ -14,32 +28,50 @@
*/
public class InmemoryDatagrid implements DataGrid {
- private final Map<String,Map<?,?>> maps = Maps.newLinkedHashMap();
+ private static final Logger LOG = LoggerFactory.getLogger(InmemoryDatagrid.class);
+ private final Map<String,Map<Object,Object>> rawMaps = Maps.newLinkedHashMap();
+ private final Map<String,Map<Object,Object>> maps = Maps.newLinkedHashMap();
+ private final ConcurrentMap<Class<?>, Serializer<?,?>> serializers = Maps.newConcurrentMap();
+ private final ConcurrentMap<Class<?>, Serializer<?,?>> deserializers = Maps.newConcurrentMap();
+ private final GlobalSerializer globalSerializer = new GlobalSerializer();
+
+ @VisibleForTesting
+ public InmemoryDatagrid cloneData() {
+ InmemoryDatagrid result = new InmemoryDatagrid();
+
+ synchronized (maps) {
+ for (Map.Entry<String,Map<Object,Object>> entry : rawMaps.entrySet()) {
+ ConcurrentMap<Object, Object> map = result.getMap(entry.getKey());
+ try {
+ for (Map.Entry<Object, Object> entry2 : entry.getValue().entrySet()) {
+ map.put(serializeAndDeserialize(entry2.getKey()), serializeAndDeserialize(entry2.getValue()));
+ }
+ } catch (Exception e) {
+ LOG.error("Error cloning map "+entry.getKey()+" - rethrowing", e);
+ throw Exceptions.propagate(e);
+ }
+ }
+ }
+
+ return result;
+ }
+
@SuppressWarnings("unchecked")
@Override
public <K, V> ConcurrentMap<K, V> getMap(String id) {
synchronized (maps) {
ConcurrentMap<K, V> result = (ConcurrentMap<K, V>) maps.get(id);
if (result == null) {
- result = newMap();
- maps.put(id, result);
+ ConcurrentMap<K,V> rawMap = Maps.<K,V>newConcurrentMap();
+ result = new ConcurrentMapAcceptingNullVals2<K,V>(rawMap, globalSerializer);
+ rawMaps.put(id, (Map)rawMap);
+ maps.put(id, (Map)result);
}
return result;
}
}
- // TODO Not doing Maps.newConcurrentMap() because needs to store null values.
- // Easy to avoid for Refererence<?> but harder for entity ConfigMap where the user
- // can insert null values.
- //
- // Could write a decorator that switches null values for a null marker, and back again.
- //
- private <K,V> ConcurrentMap<K,V> newMap() {
- //return Collections.synchronizedMap(new HashMap<K, V>());
- return new ConcurrentMapAcceptingNullVals<K,V>(Maps.<K,V>newConcurrentMap());
- }
-
@Override
public void remove(String id) {
synchronized (maps) {
@@ -51,6 +83,102 @@
public void terminate() {
synchronized (maps) {
maps.clear();
+ }
+
+ @Override
+ public <S,T> void registerSerializer(Serializer<S,T> serializer, Class<S> originalClazz, Class<T> serializedClazz) {
+ serializers.put(originalClazz, serializer);
+ deserializers.put(serializedClazz, serializer);
+ }
+
+ private class GlobalSerializer implements Serializer<Object,Object> {
+
+ @Override
+ public Object serialize(Object orig) {
+ if (orig == null) {
+ return null;
+ } else {
+ Class<?> origClazz = orig.getClass();
+ for (Map.Entry<Class<?>,Serializer<?,?>> entry : serializers.entrySet()) {
+ if (entry.getKey().isAssignableFrom(origClazz)) {
+ return ((Serializer)entry.getValue()).serialize(orig);
+ }
+ }
+ return orig;
+ }
+ }
+
+ @Override
+ public Object deserialize(Object serializedForm) {
+ if (serializedForm == null) {
+ return null;
+ } else {
+ Class<?> serializedFormClazz = serializedForm.getClass();
+ for (Map.Entry<Class<?>,Serializer<?,?>> entry : deserializers.entrySet()) {
+ if (entry.getKey().isAssignableFrom(serializedFormClazz)) {
+ return ((Serializer)entry.getValue()).deserialize(serializedForm);
+ }
+ }
+ return serializedForm;
+ }
+ }
+ }
+
+ private <T> T serializeAndDeserialize(T obj) {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream oOut = new CustomObjectOutputStream(out);
+ oOut.writeObject(obj);
+ ObjectInputStream oIn = new CustomObjectInputStream(new ByteArrayInputStream(out.toByteArray()));
+ return (T) oIn.readObject();
+ } catch (Exception e) {
+ throw Exceptions.propagate(e);
+ }
+ }
+
+ private static class ConvertedRef implements Serializable {
+ private static final long serialVersionUID = 5555077847688776972L;
+
+ final Object ref;
+
+ ConvertedRef(Object ref) {
+ this.ref = ref;
+ }
+ }
+
+ // TODO If a value in the map is a list of locations or some such, then they don't get auto-converted
+ // by the map.put wrapper. So do it here.
+ private class CustomObjectOutputStream extends ObjectOutputStream {
+ protected CustomObjectOutputStream(OutputStream out) throws IOException, SecurityException {
+ super(out);
+ enableReplaceObject(true);
+ }
+
+ @Override
+ protected Object replaceObject(Object obj) throws IOException {
+ Object result = globalSerializer.serialize(obj);
+ if (result == obj) {
+ return result;
+ } else {
+ return new ConvertedRef(result);
+ }
+ }
+ }
+
+ private class CustomObjectInputStream extends ObjectInputStream {
+
+ public CustomObjectInputStream(InputStream in) throws IOException {
+ super(in);
+ enableResolveObject(true);
+ }
+
+ @Override
+ protected Object resolveObject(Object obj) throws IOException {
+ if (obj instanceof ConvertedRef) {
+ return globalSerializer.deserialize(((ConvertedRef)obj).ref);
+ } else {
+ return obj;
+ }
}
}
}
diff --git a/core/src/main/java/brooklyn/internal/storage/impl/LocationSerializer.java b/core/src/main/java/brooklyn/internal/storage/impl/LocationSerializer.java
new file mode 100644
index 0000000..8e14c3b
--- /dev/null
+++ b/core/src/main/java/brooklyn/internal/storage/impl/LocationSerializer.java
@@ -0,0 +1,52 @@
+package brooklyn.internal.storage.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.Serializable;
+
+import brooklyn.internal.storage.Serializer;
+import brooklyn.internal.storage.impl.LocationSerializer.LocationPointer;
+import brooklyn.location.Location;
+import brooklyn.management.internal.LocalLocationManager;
+import brooklyn.management.internal.ManagementContextInternal;
+
+import com.google.common.base.Objects;
+
+public class LocationSerializer implements Serializer<Location, LocationPointer> {
+
+ public static class LocationPointer implements Serializable {
+ private static final long serialVersionUID = -3358568001462543001L;
+
+ final String id;
+
+ public LocationPointer(String id) {
+ this.id = checkNotNull(id, "id");
+ }
+ @Override public String toString() {
+ return Objects.toStringHelper(this).add("id", id).toString();
+ }
+ @Override public int hashCode() {
+ return id.hashCode();
+ }
+ @Override public boolean equals(Object obj) {
+ return (obj instanceof LocationPointer) && id.equals(((LocationPointer)obj).id);
+ }
+ }
+
+ private final ManagementContextInternal managementContext;
+
+ public LocationSerializer(ManagementContextInternal managementContext) {
+ this.managementContext = managementContext;
+ }
+
+ @Override
+ public LocationPointer serialize(Location orig) {
+ return new LocationPointer(orig.getId());
+ }
+
+ @Override
+ public Location deserialize(LocationPointer serializedForm) {
+ Location result = ((LocalLocationManager)managementContext.getLocationManager()).getLocationEvenIfPreManaged(serializedForm.id);
+ return checkNotNull(result, "no location with id %s", serializedForm.id);
+ }
+}
diff --git a/core/src/main/java/brooklyn/location/basic/AbstractLocation.java b/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
index a76c5f0..73eeb90 100644
--- a/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/AbstractLocation.java
@@ -1,6 +1,6 @@
package brooklyn.location.basic;
-import static brooklyn.util.GroovyJavaMethods.truth;
+import static brooklyn.util.JavaGroovyEquivalents.groovyTruth;
import java.io.Closeable;
import java.util.Collection;
@@ -8,6 +8,7 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -18,12 +19,16 @@
import brooklyn.entity.rebind.RebindSupport;
import brooklyn.entity.trait.Configurable;
import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.internal.storage.Reference;
+import brooklyn.internal.storage.impl.BasicReference;
import brooklyn.location.Location;
import brooklyn.location.LocationSpec;
import brooklyn.location.geo.HasHostGeoInfo;
import brooklyn.location.geo.HostGeoInfo;
import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.ManagementContextInternal;
import brooklyn.mementos.LocationMemento;
+import brooklyn.util.collections.SetFromLiveMap;
import brooklyn.util.config.ConfigBag;
import brooklyn.util.flags.FlagUtils;
import brooklyn.util.flags.SetFromFlag;
@@ -32,11 +37,10 @@
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
-import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import com.google.common.io.Closeables;
/**
@@ -53,21 +57,24 @@
public static final ConfigKey<Location> PARENT_LOCATION = new BasicConfigKey<Location>(Location.class, "parentLocation");
- @SetFromFlag
- String id;
+ private final AtomicBoolean configured = new AtomicBoolean(false);
+
+ @SetFromFlag(value="id")
+ private String id = Identifiers.makeRandomId(8);
// _not_ set from flag; configured explicitly in configure, because we also need to update the parent's list of children
private Location parentLocation;
-
- private final Collection<Location> childLocations = Lists.newArrayList();
- private final Collection<Location> childLocationsReadOnly = Collections.unmodifiableCollection(childLocations);
-
- @SetFromFlag
- protected String name;
-
- protected HostGeoInfo hostGeoInfo;
- final private ConfigBag configBag = new ConfigBag();
+ private Reference<Long> creationTimeUtc = new BasicReference<Long>(System.currentTimeMillis());
+ private Reference<Location> parent = new BasicReference<Location>();
+ private Set<Location> children = Sets.newLinkedHashSet();
+
+ private Reference<String> name = new BasicReference<String>();
+ private boolean displayNameAutoGenerated = true;
+
+ private Reference<HostGeoInfo> hostGeoInfo = new BasicReference<HostGeoInfo>();
+
+ private ConfigBag configBag = new ConfigBag();
private volatile ManagementContext managementContext;
private volatile boolean managed;
@@ -76,6 +83,7 @@
private boolean inConstruction;
+
/**
* Construct a new instance of an AbstractLocation.
*
@@ -125,8 +133,43 @@
//throw new IllegalStateException("Cannot set configuration "+key+" on active location "+this)
}
- public void setManagementContext(ManagementContext managementContext) {
+ public void setManagementContext(ManagementContextInternal managementContext) {
this.managementContext = managementContext;
+ if (displayNameAutoGenerated && id != null) name.set(getClass().getSimpleName()+":"+id.substring(0, 4));
+
+ Location oldParent = parent.get();
+ Set<Location> oldChildren = children;
+ Map<String, Object> oldConfig = configBag.getAllConfigRaw();
+ long oldCreationTimeUtc = creationTimeUtc.get();
+ String oldDisplayName = name.get();
+ HostGeoInfo oldHostGeoInfo = hostGeoInfo.get();
+
+ parent = managementContext.getStorage().getReference(id+"-parent");
+ children = SetFromLiveMap.create(managementContext.getStorage().<Location,Boolean>getMap(id+"-children"));
+ creationTimeUtc = managementContext.getStorage().getReference(id+"-creationTime");
+ hostGeoInfo = managementContext.getStorage().getReference(id+"-hostGeoInfo");
+ name = managementContext.getStorage().getReference(id+"-displayName");
+
+ // Only override stored defaults if we have actual values. We might be in setManagementContext
+ // because we are reconstituting an existing entity in a new brooklyn management-node (in which
+ // case believe what is already in the storage), or we might be in the middle of creating a new
+ // entity. Normally for a new entity (using EntitySpec creation approach), this will get called
+ // before setting the parent etc. However, for backwards compatibility we still support some
+ // things calling the entity's constructor directly.
+ if (oldParent != null) parent.set(oldParent);
+ if (oldChildren.size() > 0) children.addAll(oldChildren);
+ if (creationTimeUtc.isNull()) creationTimeUtc.set(oldCreationTimeUtc);
+ if (hostGeoInfo.isNull()) hostGeoInfo.set(oldHostGeoInfo);
+ if (name.isNull()) {
+ name.set(oldDisplayName);
+ } else {
+ displayNameAutoGenerated = false;
+ }
+
+ configBag = new ConfigBag(managementContext.getStorage().<String,Object>getMap(id+"-config"));
+ if (oldConfig.size() > 0) {
+ configBag.putAll(oldConfig);
+ }
}
protected ManagementContext getManagementContext() {
@@ -151,11 +194,8 @@
public void configure(Map properties) {
assertNotYetManaged();
- boolean firstTime = (id==null);
- if (firstTime) {
- // pick a random ID if one not set
- id = properties.containsKey("id") ? (String)properties.get("id") : Identifiers.makeRandomId(8);
- }
+ boolean firstTime = configured.getAndSet(true);
+
configBag.putAll(properties);
if (properties.containsKey(PARENT_LOCATION.getName())) {
@@ -168,17 +208,23 @@
// NB: flag-setting done here must also be done in BasicLocationRebindSupport
FlagUtils.setFieldsFromFlagsWithBag(this, properties, configBag, firstTime);
-
- if (!truth(name) && truth(properties.get("displayName"))) {
- //'displayName' is a legacy way to refer to a location's name
- //FIXME could this be a GString?
- Preconditions.checkArgument(properties.get("displayName") instanceof String, "'displayName' property should be a string");
- name = (String) properties.remove("displayName");
+
+ // TODO For consistency with entity, we should use "displayName" as primary;
+ // previous comment was that "name" was primary and "displayName" was legacy - that's still the case...
+ if (properties.get("name") != null) {
+ name.set((String) properties.remove("name"));
+ displayNameAutoGenerated = false;
+ } else if (properties.get("displayName") != null) {
+ name.set((String) properties.remove("displayName"));
+ displayNameAutoGenerated = false;
+ } else if (isLegacyConstruction()) {
+ name.set(getClass().getSimpleName()+":"+id.substring(0, 4));
+ displayNameAutoGenerated = true;
}
-
+
// TODO Explicitly dealing with iso3166 here because want custom splitter rule comma-separated string.
// Is there a better way to do it (e.g. more similar to latitude, where configKey+TypeCoercion is enough)?
- if (truth(properties.get("iso3166"))) {
+ if (groovyTruth(properties.get("iso3166"))) {
Object rawCodes = properties.remove("iso3166");
Set<String> codes;
if (rawCodes instanceof CharSequence) {
@@ -209,11 +255,23 @@
// no-op
}
+ /**
+ * Called by framework (in new-style entities) when recreating a location, on restart.
+ *
+ * To preserve backwards compatibility, the {@linke #getRebindSupport()}'s
+ * {@link BasicLocationRebindSupport#reconstruct(brooklyn.entity.rebind.RebindContext, LocationMemento)}
+ * will call this method.
+ */
+ public void reconstruct() {
+ // no-op
+ }
+
public boolean isManaged() {
return managementContext != null && managed;
}
public void onManagementStarted() {
+ if (displayNameAutoGenerated) name.set(getClass().getSimpleName()+":"+id.substring(0, 4));
this.managed = true;
}
@@ -232,7 +290,7 @@
@Override
public String getDisplayName() {
- return name;
+ return name.get();
}
@Override
@@ -249,7 +307,7 @@
@Override
public Collection<Location> getChildren() {
- return childLocationsReadOnly;
+ return Collections.unmodifiableCollection(children);
}
@Override
@@ -342,9 +400,16 @@
public <T> T setConfig(ConfigKey<T> key, T value) {
return configBag.put(key, value);
}
-
- public void setName(String name) {
- this.name = name;
+
+ /** @since 0.6.0 (?) - use getDisplayName */
+ public void setName(String newName) {
+ setDisplayName(newName);
+ displayNameAutoGenerated = false;
+ }
+
+ public void setDisplayName(String newName) {
+ name.set(newName);
+ displayNameAutoGenerated = false;
}
@Override
@@ -396,19 +461,25 @@
// We continue to use a list to allow identical-looking locations, but they must be different
// instances.
- for (Location contender : childLocations) {
+ for (Location contender : children) {
if (contender == child) {
// don't re-add; no-op
return;
}
}
- childLocations.add(child);
- child.setParent(this);
-
+// if (managementContext != null && !isManaged()) {
+// // Some entities within their init/constructor will add child locations.
+//// However, if we are not yet managed then when the chilld does
+// // FIXME GOT HERE
+// managementContext.getLocationManager().manage(child);
+// }
if (isManaged()) {
managementContext.getLocationManager().manage(child);
}
+
+ children.add(child);
+ child.setParent(this);
}
/**
@@ -421,7 +492,7 @@
}
protected boolean removeChild(Location child) {
- boolean removed = childLocations.remove(child);
+ boolean removed = children.remove(child);
if (removed) {
if (child instanceof Closeable) {
Closeables.closeQuietly((Closeable)child);
@@ -473,11 +544,11 @@
}
@Override
- public HostGeoInfo getHostGeoInfo() { return hostGeoInfo; }
+ public HostGeoInfo getHostGeoInfo() { return hostGeoInfo.get(); }
public void setHostGeoInfo(HostGeoInfo hostGeoInfo) {
if (hostGeoInfo!=null) {
- this.hostGeoInfo = hostGeoInfo;
+ this.hostGeoInfo.set(hostGeoInfo);
setConfig(LocationConfigKeys.LATITUDE, hostGeoInfo.latitude);
setConfig(LocationConfigKeys.LONGITUDE, hostGeoInfo.longitude);
}
diff --git a/core/src/main/java/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java b/core/src/main/java/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java
index a2dd06a..c9940d4 100644
--- a/core/src/main/java/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/LocalhostMachineProvisioningLocation.java
@@ -76,7 +76,7 @@
public void configure(Map flags) {
super.configure(flags);
- if (!truth(name)) { name = "localhost"; }
+ if (!truth(getDisplayName())) { setName("localhost"); }
if (!truth(address)) address = getLocalhostInetAddress();
// TODO should try to confirm this machine is accessible on the given address ... but there's no
// immediate convenience in java so early-trapping of that particular error is deferred
diff --git a/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java b/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java
index c4033ee..695c1cb 100644
--- a/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/SshMachineLocation.java
@@ -168,7 +168,7 @@
private BasicPool<SshTool> buildVanillaPool() {
return BasicPool.<SshTool>builder()
- .name(name+"@"+address+
+ .name(getDisplayName()+"@"+address+
(hasConfig(SSH_HOST) ? "("+getConfig(SSH_HOST)+":"+getConfig(SSH_PORT)+")" : "")+
":"+
System.identityHashCode(this))
@@ -209,8 +209,8 @@
user = ""+properties.get("username");
}
- if (name == null) {
- name = (truth(user) ? user+"@" : "") + address.getHostName();
+ if (getDisplayName() == null) {
+ setDisplayName((truth(user) ? user+"@" : "") + address.getHostName());
}
if (getHostGeoInfo() == null) {
@@ -637,7 +637,7 @@
@Override
public String toString() {
- return "SshMachineLocation["+name+":"+address+"]";
+ return "SshMachineLocation["+getDisplayName()+":"+address+"]";
}
@Override
diff --git a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
index 2e5b1d5..8febacd 100644
--- a/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/AbstractManagementContext.java
@@ -1,5 +1,7 @@
package brooklyn.management.internal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.Collection;
@@ -29,10 +31,14 @@
import brooklyn.entity.drivers.downloads.DownloadResolverManager;
import brooklyn.entity.rebind.RebindManager;
import brooklyn.entity.rebind.RebindManagerImpl;
+import brooklyn.internal.rebind.RebindFromDatagridManagerImpl;
import brooklyn.internal.storage.BrooklynStorage;
import brooklyn.internal.storage.DataGrid;
import brooklyn.internal.storage.impl.BrooklynStorageImpl;
+import brooklyn.internal.storage.impl.EntitySerializer;
import brooklyn.internal.storage.impl.InmemoryDatagrid;
+import brooklyn.internal.storage.impl.LocationSerializer;
+import brooklyn.location.Location;
import brooklyn.location.LocationRegistry;
import brooklyn.location.basic.BasicLocationRegistry;
import brooklyn.management.ExecutionContext;
@@ -61,7 +67,9 @@
protected Iterable<URL> baseClassPathForScanning;
// TODO leaking "this" reference; yuck
- private final RebindManager rebindManager = new RebindManagerImpl(this);
+ private final RebindManager rebindManager;
+
+ private final RebindFromDatagridManagerImpl rebindFromDatagridManager;
protected volatile BrooklynGarbageCollector gc;
@@ -69,14 +77,35 @@
private final DownloadResolverManager downloadsManager;
- private final DataGrid datagrid = new InmemoryDatagrid();
+ private final BrooklynStorage storage;
- private final BrooklynStorage storage = new BrooklynStorageImpl(datagrid);
+ public AbstractManagementContext(){
+ this(BrooklynProperties.Factory.newDefault());
+ }
- public AbstractManagementContext(BrooklynProperties brooklynProperties){
- this.configMap = brooklynProperties;
- this.entityDriverManager = new BasicEntityDriverManager();
- this.downloadsManager = BasicDownloadsManager.newDefault(configMap);
+ public AbstractManagementContext(DataGrid datagrid) {
+ this(datagrid, BrooklynProperties.Factory.newDefault());
+ }
+
+ public AbstractManagementContext(BrooklynProperties brooklynProperties) {
+ this(new InmemoryDatagrid(), brooklynProperties);
+ }
+
+ public AbstractManagementContext(DataGrid datagrid, BrooklynProperties brooklynProperties) {
+ datagrid.registerSerializer(new EntitySerializer(this), Entity.class, EntitySerializer.EntityPointer.class);
+ datagrid.registerSerializer(new LocationSerializer(this), Location.class, LocationSerializer.LocationPointer.class);
+ this.storage = new BrooklynStorageImpl(datagrid);
+
+ rebindManager = new RebindManagerImpl(this);
+ rebindFromDatagridManager = new RebindFromDatagridManagerImpl(this);
+
+ this.configMap = brooklynProperties;
+
+ // TODO Why do this? It's moved here from LocalManagementContext
+ configMap.putAll(checkNotNull(brooklynProperties, "brooklynProperties"));
+
+ this.entityDriverManager = new BasicEntityDriverManager();
+ this.downloadsManager = BasicDownloadsManager.newDefault(configMap);
}
static {
@@ -98,6 +127,7 @@
private volatile boolean running = true;
+ @Override
public void terminate() {
running = false;
rebindManager.stop();
@@ -107,6 +137,7 @@
// group itself has been told that it is unmanaged).
}
+ @Override
public boolean isRunning() {
return running;
}
@@ -121,14 +152,21 @@
return rebindManager;
}
+ public RebindFromDatagridManagerImpl getRebindFromDatagridManager() {
+ return rebindFromDatagridManager;
+ }
+
+ @Override
public long getTotalEffectorInvocations() {
return totalEffectorInvocationCount.get();
}
+ @Override
public ExecutionContext getExecutionContext(Entity e) {
return new BasicExecutionContext(MutableMap.of("tag", e), getExecutionManager());
}
+ @Override
public SubscriptionContext getSubscriptionContext(Entity e) {
return new BasicSubscriptionContext(getSubscriptionManager(), e);
}
diff --git a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
index 88f31cc..18030ff 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalEntityManager.java
@@ -22,8 +22,10 @@
import brooklyn.entity.proxying.EntityTypeRegistry;
import brooklyn.entity.proxying.InternalEntityFactory;
import brooklyn.entity.trait.Startable;
+import brooklyn.internal.storage.BrooklynStorage;
import brooklyn.management.EntityManager;
import brooklyn.management.internal.ManagementTransitionInfo.ManagementTransitionMode;
+import brooklyn.util.collections.SetFromLiveMap;
import brooklyn.util.exceptions.Exceptions;
import com.google.common.base.Predicate;
@@ -57,10 +59,18 @@
/** Proxies of the managed entities that are applications */
protected final Set<Application> applications = Sets.newLinkedHashSet();
+ private final BrooklynStorage storage;
+ private final Map<String,String> entityTypes;
+ private final Set<String> applicationIds;
+
public LocalEntityManager(LocalManagementContext managementContext) {
this.managementContext = checkNotNull(managementContext, "managementContext");
+ this.storage = managementContext.getStorage();
this.entityTypeRegistry = new BasicEntityTypeRegistry();
this.entityFactory = new InternalEntityFactory(managementContext, entityTypeRegistry);
+
+ entityTypes = storage.getMap("entities");
+ applicationIds = SetFromLiveMap.create(storage.<String,Boolean>getMap("applications"));
}
@Override
@@ -97,6 +107,31 @@
return entityProxiesById.get(id);
}
+ public synchronized Entity getEntityEvenIfPreManaged(String id) {
+ Entity result = entityProxiesById.get(id);
+ if (result == null) {
+ result = entitiesById.get(id);
+ }
+ if (result == null) {
+ result = preManagedEntitiesById.get(id);
+ }
+ if (result == null) {
+ result = preRegisteredEntitiesById.get(id);
+ }
+ if (result instanceof AbstractEntity) {
+ result = ((AbstractEntity)result).getProxyIfAvailable();
+ }
+ return result;
+ }
+
+ public synchronized Entity getRealEntity(String id) {
+ Entity result = toRealEntityOrNull(id);
+ if (result == null) {
+ throw new IllegalStateException("No concrete entity known for entity "+id);
+ }
+ return result;
+ }
+
synchronized Collection<Application> getApplications() {
return ImmutableList.copyOf(applications);
}
@@ -267,6 +302,8 @@
// the legacy way of creating the entity so didn't get a preManage() call
entityProxiesById.put(e.getId(), proxyE);
+ entityTypes.put(e.getId(), realE.getClass().getName());
+
Object old = entitiesById.put(e.getId(), realE);
if (old!=null) {
if (old.equals(e)) {
@@ -280,6 +317,7 @@
preManagedEntitiesById.remove(e.getId());
if ((e instanceof Application) && (e.getParent()==null)) {
applications.add((Application)proxyE);
+ applicationIds.add(e.getId());
}
entities.add(proxyE);
return true;
@@ -294,11 +332,15 @@
Entity proxyE = toProxyEntityIfAvailable(e);
e.clearParent();
- if (e instanceof Application) applications.remove(proxyE);
+ if (e instanceof Application) {
+ applications.remove(proxyE);
+ applicationIds.remove(e.getId());
+ }
entities.remove(proxyE);
entityProxiesById.remove(e.getId());
Object old = entitiesById.remove(e.getId());
-
+ entityTypes.remove(e.getId());
+
if (old==null) {
log.warn("{} call to stop management of unknown entity (already unmanaged?) {}", this, e);
return false;
@@ -323,6 +365,10 @@
entities.removePropertyChangeListener(new GroovyObservablesPropertyChangeToCollectionChangeAdapter(wrappedListener));
}
+ public InternalEntityFactory getEntityFactory() {
+ return entityFactory;
+ }
+
private boolean shouldSkipUnmanagement(Entity e) {
if (e==null) {
log.warn(""+this+" call to unmanage null entity; skipping",
@@ -355,19 +401,24 @@
if (e instanceof AbstractEntity) {
return e;
} else {
- Entity result = entitiesById.get(e.getId());
+ Entity result = toRealEntityOrNull(e.getId());
if (result == null) {
- result = preManagedEntitiesById.get(e.getId());
- }
- if (result == null) {
- result = preRegisteredEntitiesById.get(e.getId());
- }
- if (result == null) {
- throw new IllegalStateException("No concrete entity known for "+e+" ("+e.getId()+", "+e.getEntityType().getName()+")");
+ throw new IllegalStateException("No concrete entity known for entity "+e+" ("+e.getId()+", "+e.getEntityType().getName()+")");
}
return result;
}
}
+
+ private Entity toRealEntityOrNull(String id) {
+ Entity result = entitiesById.get(id);
+ if (result == null) {
+ result = preManagedEntitiesById.get(id);
+ }
+ if (result == null) {
+ result = preRegisteredEntitiesById.get(id);
+ }
+ return result;
+ }
private boolean isRunning() {
return managementContext.isRunning();
diff --git a/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java b/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java
index 4760625..94faf8c 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalLocationManager.java
@@ -9,6 +9,7 @@
import org.slf4j.LoggerFactory;
import brooklyn.entity.proxying.InternalLocationFactory;
+import brooklyn.internal.storage.BrooklynStorage;
import brooklyn.location.Location;
import brooklyn.location.LocationSpec;
import brooklyn.location.basic.AbstractLocation;
@@ -28,10 +29,16 @@
protected final Map<String,Location> locationsById = Maps.newLinkedHashMap();
private final Map<String, Location> preRegisteredLocationsById = Maps.newLinkedHashMap();
+
+ private final BrooklynStorage storage;
+ private Map<String, String> locationTypes;
public LocalLocationManager(LocalManagementContext managementContext) {
this.managementContext = checkNotNull(managementContext, "managementContext");
this.locationFactory = new InternalLocationFactory(managementContext);
+
+ this.storage = managementContext.getStorage();
+ locationTypes = storage.getMap("locations");
}
@Override
@@ -152,6 +159,8 @@
Object old = locationsById.put(loc.getId(), loc);
preRegisteredLocationsById.remove(loc.getId());
+ locationTypes.put(loc.getId(), loc.getClass().getName());
+
if (old!=null) {
if (old.equals(loc)) {
log.warn("{} redundant call to start management of location {}", this, loc);
@@ -171,6 +180,7 @@
private synchronized boolean unmanageNonRecursive(AbstractLocation loc) {
loc.setParentLocation(null);
Object old = locationsById.remove(loc.getId());
+ locationTypes.remove(loc.getId());
if (old==null) {
log.warn("{} call to stop management of unknown location (already unmanaged?) {}", this, loc);
@@ -185,6 +195,10 @@
}
}
+ public InternalLocationFactory getLocationFactory() {
+ return locationFactory;
+ }
+
private boolean shouldSkipUnmanagement(Location loc) {
if (loc==null) {
log.warn(""+this+" call to unmanage null location; skipping",
diff --git a/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java b/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
index 64f5b69..d6f8e28 100644
--- a/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/LocalManagementContext.java
@@ -1,7 +1,6 @@
package brooklyn.management.internal;
import static brooklyn.util.JavaGroovyEquivalents.elvis;
-import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Arrays;
import java.util.Collection;
@@ -14,8 +13,12 @@
import brooklyn.config.BrooklynProperties;
import brooklyn.entity.Application;
import brooklyn.entity.Entity;
+import brooklyn.entity.proxying.InternalEntityFactory;
+import brooklyn.entity.proxying.InternalLocationFactory;
+import brooklyn.internal.storage.DataGrid;
import brooklyn.location.Location;
import brooklyn.management.ExecutionManager;
+import brooklyn.management.LocationManager;
import brooklyn.management.ManagementContext;
import brooklyn.management.SubscriptionManager;
import brooklyn.management.Task;
@@ -41,15 +44,29 @@
* Creates a LocalManagement with default BrooklynProperties.
*/
public LocalManagementContext() {
- this(BrooklynProperties.Factory.newDefault());
+ super();
+ this.locationManager = new LocalLocationManager(this);
+ }
+
+ public LocalManagementContext(DataGrid datagrid) {
+ super(datagrid);
+ this.locationManager = new LocalLocationManager(this);
}
public LocalManagementContext(BrooklynProperties brooklynProperties) {
super(brooklynProperties);
- configMap.putAll(checkNotNull(brooklynProperties, "brooklynProperties"));
this.locationManager = new LocalLocationManager(this);
}
+ public LocalManagementContext(DataGrid datagrid, BrooklynProperties brooklynProperties) {
+ super(datagrid, brooklynProperties);
+ this.locationManager = new LocalLocationManager(this);
+ }
+
+ protected LocationManager newLocationManager() {
+ return new LocalLocationManager(this);
+ }
+
public void prePreManage(Entity entity) {
getEntityManager().prePreManage(entity);
}
@@ -79,6 +96,11 @@
}
@Override
+ public LocalLocationManager getLocationManager() {
+ return locationManager;
+ }
+
+ @Override
public synchronized LocalEntityManager getEntityManager() {
if (!isRunning()) throw new IllegalStateException("Management context no longer running");
@@ -89,9 +111,13 @@
}
@Override
- public synchronized LocalLocationManager getLocationManager() {
- if (!isRunning()) throw new IllegalStateException("Management context no longer running");
- return locationManager;
+ public InternalEntityFactory getEntityFactory() {
+ return getEntityManager().getEntityFactory();
+ }
+
+ @Override
+ public InternalLocationFactory getLocationFactory() {
+ return getLocationManager().getLocationFactory();
}
@Override
@@ -142,4 +168,10 @@
public String toString() {
return tostring;
}
+
+ @Override
+ @Deprecated
+ public Entity getRealEntity(String entityId) {
+ return entityManager.getRealEntity(entityId);
+ }
}
diff --git a/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java b/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java
index 8fa0d50..a396a38 100644
--- a/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java
+++ b/core/src/main/java/brooklyn/management/internal/ManagementContextInternal.java
@@ -8,6 +8,8 @@
import brooklyn.entity.Effector;
import brooklyn.entity.Entity;
import brooklyn.entity.basic.ConfigKeys;
+import brooklyn.entity.proxying.InternalEntityFactory;
+import brooklyn.entity.proxying.InternalLocationFactory;
import brooklyn.internal.storage.BrooklynStorage;
import brooklyn.management.ManagementContext;
import brooklyn.management.Task;
@@ -40,4 +42,18 @@
<T> Task<T> invokeEffector(final Entity entity, final Effector<T> eff, @SuppressWarnings("rawtypes") final Map parameters);
BrooklynStorage getStorage();
+
+ InternalEntityFactory getEntityFactory();
+
+ InternalLocationFactory getLocationFactory();
+
+ /**
+ * TODO Will be deleted shortly; placeholder while implementing remoting. When running with
+ * a distributed brooklyn, this method is meaningless as the real entity instance may not
+ * be local.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ Entity getRealEntity(String entityId);
}
diff --git a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
index 6918d89..0935379 100644
--- a/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
+++ b/core/src/main/java/brooklyn/management/internal/NonDeploymentManagementContext.java
@@ -20,6 +20,8 @@
import brooklyn.entity.basic.AbstractEntity;
import brooklyn.entity.drivers.EntityDriverManager;
import brooklyn.entity.drivers.downloads.DownloadResolverManager;
+import brooklyn.entity.proxying.InternalEntityFactory;
+import brooklyn.entity.proxying.InternalLocationFactory;
import brooklyn.entity.rebind.ChangeListener;
import brooklyn.entity.rebind.RebindManager;
import brooklyn.internal.storage.BrooklynStorage;
@@ -269,6 +271,22 @@
initialManagementContext.setBaseClassPathForScanning(urls);
}
+ @Override
+ public InternalEntityFactory getEntityFactory() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public InternalLocationFactory getLocationFactory() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ @Deprecated
+ public Entity getRealEntity(String entityId) {
+ throw new UnsupportedOperationException();
+ }
+
private boolean isInitialManagementContextReal() {
return (initialManagementContext != null && !(initialManagementContext instanceof NonDeploymentManagementContext));
}
diff --git a/core/src/main/java/brooklyn/policy/basic/AbstractPolicy.java b/core/src/main/java/brooklyn/policy/basic/AbstractPolicy.java
index 93b30f6..38bf80b 100644
--- a/core/src/main/java/brooklyn/policy/basic/AbstractPolicy.java
+++ b/core/src/main/java/brooklyn/policy/basic/AbstractPolicy.java
@@ -11,10 +11,12 @@
import org.slf4j.LoggerFactory;
import brooklyn.config.ConfigKey;
+import brooklyn.entity.rebind.BasicLocationRebindSupport;
import brooklyn.entity.rebind.BasicPolicyRebindSupport;
import brooklyn.entity.rebind.RebindSupport;
import brooklyn.entity.trait.Configurable;
import brooklyn.management.ExecutionContext;
+import brooklyn.mementos.LocationMemento;
import brooklyn.mementos.PolicyMemento;
import brooklyn.policy.Policy;
import brooklyn.policy.PolicyType;
@@ -105,10 +107,23 @@
}
}
+ /**
+ * Called by framework (in new-style entities) when recreating a policy, on restart.
+ *
+ * To preserve backwards compatibility, the {@linke #getRebindSupport()}'s
+ * {@link BasicPolicyRebindSupport#reconstruct(brooklyn.entity.rebind.RebindContext, PolicyMemento)}
+ * will call this method.
+ */
+ public void reconstruct() {
+ // no-op
+ }
+
+ @Override
public <T> T getConfig(ConfigKey<T> key) {
return configsInternal.getConfig(key);
}
+ @Override
public <T> T setConfig(ConfigKey<T> key, T val) {
if (entity != null && isRunning()) {
doReconfigureConfig(key, val);
@@ -116,6 +131,7 @@
return (T) configsInternal.setConfig(key, val);
}
+ @Override
public Map<ConfigKey<?>, Object> getAllConfig() {
return configsInternal.getAllConfig();
}
@@ -136,14 +152,17 @@
return policyType;
}
+ @Override
public void suspend() {
suspended.set(true);
}
+ @Override
public void resume() {
suspended.set(false);
}
+ @Override
public boolean isSuspended() {
return suspended.get();
}
diff --git a/core/src/main/java/brooklyn/util/config/ConfigBag.java b/core/src/main/java/brooklyn/util/config/ConfigBag.java
index 66a22b0..d854f43 100644
--- a/core/src/main/java/brooklyn/util/config/ConfigBag.java
+++ b/core/src/main/java/brooklyn/util/config/ConfigBag.java
@@ -1,5 +1,7 @@
package brooklyn.util.config;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -33,8 +35,8 @@
protected String description;
- private Map<String,Object> config = new LinkedHashMap<String,Object>();
- private Map<String,Object> unusedConfig = new LinkedHashMap<String,Object>();
+ private final Map<String,Object> config;
+ private final Map<String,Object> unusedConfig;
private boolean sealed = false;
/** creates a new ConfigBag instance, empty and ready for population */
@@ -66,6 +68,15 @@
}.copy(configBag).putAll(flags);
}
+ public ConfigBag() {
+ this(new LinkedHashMap<String,Object>());
+ }
+
+ public ConfigBag(Map<String,Object> storage) {
+ this.config = checkNotNull(storage, "storage map must be specified");
+ this.unusedConfig = new LinkedHashMap<String,Object>();
+
+ }
public ConfigBag setDescription(String description) {
if (sealed)
throw new IllegalStateException("Cannot set description to '"+description+"': this config bag has been sealed and is now immutable.");
@@ -87,7 +98,11 @@
/** internal map containing the current values for all entries;
* for use where the caller wants to modify this directly and knows it is safe to do so */
public Map<String,Object> getAllConfigRaw() {
- return config;
+ // TODO sealed no longer works as before, because `config` is the backing storage map.
+ // Therefore returning it is dangerous! Even if we were to replace our field with an immutable copy,
+ // the underlying datagrid's map would still be modifiable. We need a way to switch the returned
+ // value's behaviour to sealable (i.e. wrapping the returned map).
+ return (sealed) ? Collections.unmodifiableMap(config) : config;
}
/** current values for all entries which have not yet been used
@@ -333,7 +348,7 @@
* returns this for convenience (fluent usage) */
public ConfigBag seal() {
sealed = true;
- config = getAllConfig();
+ //TODO config.seal();
return this;
}
}
diff --git a/core/src/test/java/brooklyn/internal/rebind/Dumpers.java b/core/src/test/java/brooklyn/internal/rebind/Dumpers.java
new file mode 100644
index 0000000..2665194
--- /dev/null
+++ b/core/src/test/java/brooklyn/internal/rebind/Dumpers.java
@@ -0,0 +1,209 @@
+package brooklyn.internal.rebind;
+
+import java.io.PrintStream;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.collections.Lists;
+import org.testng.collections.Maps;
+
+import brooklyn.util.flags.FlagUtils;
+import brooklyn.util.javalang.Serializers;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+
+/**
+ * Convenience for writing out an object hierarchy.
+ *
+ * This is particularly useful for NotSerializableExceptions, where it does not tell you
+ * which object contained the unserializable field.
+ *
+ * @author aled
+ */
+public class Dumpers {
+
+ protected static final Logger LOG = LoggerFactory.getLogger(Dumpers.class);
+
+ private static List<String> UNTRAVERSED_PREFIXES = ImmutableList.of("java.lang", "java.io");
+
+ private static final int MAX_MEMBERS = 100;
+
+ private static final Predicate<Field> SERIALIZED_FIELD_PREDICATE = new Predicate<Field>() {
+ @Override public boolean apply(@Nullable Field input) {
+ int excludedModifiers = Modifier.TRANSIENT ^ Modifier.STATIC;
+ return (input.getModifiers() & excludedModifiers) == 0;
+ }
+ };
+
+ public static void logUnserializableChains(Object root) throws IllegalArgumentException, IllegalAccessException {
+ final Map<List<Object>, Class<?>> unserializablePaths = Maps.newLinkedHashMap();
+
+ Visitor visitor = new Visitor() {
+ @Override public boolean visit(Object o, Iterable<Object> refChain) {
+ try {
+ Serializers.reconstitute(o);
+ return true;
+ } catch (Exception e) {
+ // not serializable in some way: report
+ ImmutableList<Object> refChainList = ImmutableList.copyOf(refChain);
+
+ // First strip out any less specific paths
+ for (Iterator<List<Object>> iter = unserializablePaths.keySet().iterator(); iter.hasNext();) {
+ List<Object> existing = iter.next();
+ if (refChainList.size() >= existing.size() && refChainList.subList(0, existing.size()).equals(existing)) {
+ iter.remove();
+ }
+ }
+
+ // Then add this list
+ unserializablePaths.put(ImmutableList.copyOf(refChainList), o.getClass());
+ return false;
+ }
+ }
+ };
+ deepVisitInternal(root, SERIALIZED_FIELD_PREDICATE, Lists.newArrayList(), new LinkedList<Object>(), visitor);
+
+ LOG.warn("Not serializable ("+root+"):");
+ for (Map.Entry<List<Object>, Class<?>> entry : unserializablePaths.entrySet()) {
+ LOG.warn("\t"+"type="+entry.getValue()+"; chain="+entry.getKey());
+ }
+ }
+
+ public static void deepDumpSerializableness(Object o) {
+ deepDump(o, SERIALIZED_FIELD_PREDICATE, System.out);
+ }
+
+ public static void deepDump(Object o, Predicate<Field> fieldPredicate, PrintStream out) {
+ try {
+ out.println("Deep dump:");
+ deepDumpInternal(o, fieldPredicate, out, 1, "", Lists.newArrayList());
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ private static void deepDumpInternal(Object o, Predicate<Field> fieldPredicate, PrintStream out, int indentSize, String prefix, List<Object> visited) throws IllegalArgumentException, IllegalAccessException {
+ String indent = com.google.common.base.Strings.repeat(" ", indentSize*2);
+ Class<?> clazz = (o != null) ? o.getClass() : null;
+
+ if (o == null) {
+ out.println(indent+prefix+"null");
+ } else if (isClassUntraversable(clazz)) {
+ out.println(indent+prefix+"(untraversable) type="+clazz+"; val="+o.toString());
+ } else if (containsSame(visited, o)) {
+ out.println(indent+prefix+"duplicate (type="+clazz+"; val="+o.toString()+")");
+ } else {
+ visited.add(o);
+ out.println(indent+prefix+"type="+clazz+"; val="+o.toString());
+ Map<String, Object> members = findMembers(o, fieldPredicate);
+ for (Map.Entry<String, Object> entry : Iterables.limit(members.entrySet(), MAX_MEMBERS)) {
+ deepDumpInternal(entry.getValue(), fieldPredicate, out, indentSize+1, ""+entry.getKey()+": ", visited);
+ }
+ if (members.size() > MAX_MEMBERS) {
+ out.println(indent+prefix+"TRUNCATED ("+members.size()+" members in total)");
+ }
+ }
+ }
+
+ private static void deepVisitInternal(Object o, Predicate<Field> fieldPredicate, List<Object> visited, Deque<Object> refChain, Visitor visitor) throws IllegalArgumentException, IllegalAccessException {
+ Class<?> clazz = (o != null) ? o.getClass() : null;
+ refChain.addLast(o);
+ Iterable<Object> filteredRefChain = Iterables.filter(refChain, Predicates.not(Predicates.instanceOf(Dumpers.Entry.class)));
+ try {
+ if (o == null) {
+ // no-op
+ } else if (isClassUntraversable(clazz)) {
+ visitor.visit(o, filteredRefChain);
+ } else if (containsSame(visited, o)) {
+ // no-op
+ } else {
+ visited.add(o);
+ boolean subTreeComplete = visitor.visit(o, filteredRefChain);
+ if (!subTreeComplete) {
+ Map<String, Object> members = findMembers(o, fieldPredicate);
+ for (Map.Entry<String, Object> entry : members.entrySet()) {
+ deepVisitInternal(entry.getValue(), fieldPredicate, visited, refChain, visitor);
+ }
+ }
+ }
+ } finally {
+ refChain.removeLast();
+ }
+ }
+
+ public interface Visitor {
+ /**
+ * @param refChain The chain of references leading to this object (starting at the root)
+ * @return True if this part of the tree is complete; false if need to continue visiting children
+ */
+ public boolean visit(Object o, Iterable<Object> refChain);
+ }
+
+ private static Map<String,Object> findMembers(Object o, Predicate<Field> fieldPredicate) throws IllegalArgumentException, IllegalAccessException {
+ Map<String,Object> result = Maps.newLinkedHashMap();
+ Class<?> clazz = (o != null) ? o.getClass() : null;
+
+ if (o instanceof Iterable) {
+ int i = 0;
+ for (Object member : (Iterable)o) {
+ result.put("member"+(i++), member);
+ }
+ } else if (o instanceof Map) {
+ int i = 0;
+ Map<?,?> m = (Map<?,?>) o;
+ for (Map.Entry<?,?> entry : m.entrySet()) {
+ result.put("member"+(i++), new Entry(entry.getKey(), entry.getValue()));
+ }
+ } else {
+ for (Field field : FlagUtils.getAllFields(clazz, fieldPredicate)) {
+ field.setAccessible(true);
+ String fieldName = field.getName();
+ Object fieldVal = field.get(o);
+ result.put(fieldName, fieldVal);
+ }
+ }
+
+ return result;
+ }
+
+ private static boolean isClassUntraversable(Class<?> clazz) {
+ String clazzName = clazz.getName();
+ for (String prefix : UNTRAVERSED_PREFIXES) {
+ if (clazzName.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean containsSame(Iterable<?> vals, Object val) {
+ for (Object contender : vals) {
+ if (contender == val) return true;
+ }
+ return false;
+ }
+
+ private static class Entry implements Serializable {
+ final Object key;
+ final Object value;
+
+ public Entry(Object key, Object value) {
+ this.key = key;
+ this.value = value;
+ }
+ }
+}
diff --git a/core/src/test/java/brooklyn/internal/rebind/RebindDynamicGroupTest.java.inprogress b/core/src/test/java/brooklyn/internal/rebind/RebindDynamicGroupTest.java.inprogress
new file mode 100644
index 0000000..7f18361
--- /dev/null
+++ b/core/src/test/java/brooklyn/internal/rebind/RebindDynamicGroupTest.java.inprogress
@@ -0,0 +1,80 @@
+package brooklyn.internal.rebind;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.File;
+import java.util.Collection;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.DynamicGroup;
+import brooklyn.entity.basic.DynamicGroupImpl;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.rebind.RebindEntityTest.MyEntity;
+import brooklyn.entity.rebind.RebindEntityTest.MyEntityImpl;
+import brooklyn.management.ManagementContext;
+import brooklyn.test.TestUtils;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.test.entity.TestApplicationImpl;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+
+public class RebindDynamicGroupTest {
+
+ private ClassLoader classLoader = getClass().getClassLoader();
+ private ManagementContext managementContext;
+ private TestApplication origApp;
+ private File mementoDir;
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ mementoDir = Files.createTempDir();
+ managementContext = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader, 1);
+ origApp = new TestApplicationImpl();
+ }
+
+ @AfterMethod
+ public void tearDown() throws Exception {
+ if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+ }
+
+ @Test
+ public void testRestoresDynamicGroup() throws Exception {
+ MyEntity origE = new MyEntityImpl(origApp);
+ DynamicGroup origG = new DynamicGroupImpl(origApp, Predicates.instanceOf(MyEntity.class));
+ Entities.startManagement(origApp, managementContext);
+
+ TestApplication newApp = rebind();
+ final DynamicGroup newG = (DynamicGroup) Iterables.find(newApp.getChildren(), Predicates.instanceOf(DynamicGroup.class));
+ final MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
+
+ // Rebound group should contain same members as last time
+ assertGroupMemebers(newG, ImmutableSet.of(newE));
+
+ // And should detect new members that match the filter
+ final MyEntity newE2 = new MyEntityImpl(newApp);
+ Entities.manage(newE2);
+
+ TestUtils.assertEventually(new Runnable() {
+ public void run() {
+ assertGroupMemebers(newG, ImmutableSet.of(newE, newE2));
+ }});
+ }
+
+ private void assertGroupMemebers(DynamicGroup group, Collection<? extends Entity> expected) {
+ assertEquals(Sets.newHashSet(group.getMembers()), ImmutableSet.copyOf(expected));
+ assertEquals(group.getMembers().size(), expected.size(), "members="+group.getMembers());
+ }
+
+ private TestApplication rebind() throws Exception {
+ RebindTestUtils.waitForPersisted(origApp);
+ return (TestApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+ }
+}
diff --git a/core/src/test/java/brooklyn/internal/rebind/RebindEntityTest.java b/core/src/test/java/brooklyn/internal/rebind/RebindEntityTest.java
new file mode 100644
index 0000000..11169d3
--- /dev/null
+++ b/core/src/test/java/brooklyn/internal/rebind/RebindEntityTest.java
@@ -0,0 +1,654 @@
+package brooklyn.internal.rebind;
+
+import static brooklyn.test.EntityTestUtils.assertAttributeEquals;
+import static brooklyn.test.EntityTestUtils.assertConfigEquals;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNotSame;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.entity.Entity;
+import brooklyn.entity.basic.AbstractEntity;
+import brooklyn.entity.basic.ApplicationBuilder;
+import brooklyn.entity.basic.BasicGroup;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityLocal;
+import brooklyn.entity.proxying.EntitySpecs;
+import brooklyn.entity.proxying.ImplementedBy;
+import brooklyn.entity.rebind.BasicEntityRebindSupport;
+import brooklyn.entity.rebind.RebindContext;
+import brooklyn.entity.rebind.RebindLocationTest.MyLocation;
+import brooklyn.entity.rebind.RebindSupport;
+import brooklyn.entity.trait.Startable;
+import brooklyn.event.AttributeSensor;
+import brooklyn.event.SensorEvent;
+import brooklyn.event.SensorEventListener;
+import brooklyn.event.basic.BasicAttributeSensor;
+import brooklyn.event.basic.BasicConfigKey;
+import brooklyn.event.basic.BasicSensorEvent;
+import brooklyn.internal.storage.impl.InmemoryDatagrid;
+import brooklyn.location.Location;
+import brooklyn.location.LocationSpec;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.mementos.EntityMemento;
+import brooklyn.test.TestUtils;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.test.entity.TestEntity;
+import brooklyn.util.exceptions.RuntimeInterruptedException;
+import brooklyn.util.flags.SetFromFlag;
+
+import com.google.common.base.Predicates;
+import com.google.common.base.Suppliers;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class RebindEntityTest {
+
+ // FIXME Add test about dependent configuration serialization?!
+
+ // TODO Convert, so not calling entity constructors
+
+ private static final long TIMEOUT_MS = 2*1000;
+
+ private InmemoryDatagrid origDatagrid;
+ private ClassLoader classLoader = getClass().getClassLoader();
+ private LocalManagementContext origManagementContext;
+ private TestApplication origApp;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ origDatagrid = new InmemoryDatagrid();
+ origManagementContext = RebindTestUtils.newPersistingManagementContext(origDatagrid, classLoader);
+ origApp = ApplicationBuilder.newManagedApp(TestApplication.class, origManagementContext);
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (origManagementContext != null) origManagementContext.terminate();
+ }
+
+ @Test
+ public void testRestoresSimpleApp() throws Exception {
+ TestApplication newApp = rebind();
+ assertNotSame(newApp, origApp);
+ assertEquals(newApp.getId(), origApp.getId());
+ }
+
+ @Test
+ public void testRestoresEntitySimpleHierarchy() throws Exception {
+ MyEntity origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntity.class));
+
+ TestApplication newApp = rebind();
+
+ // Assert has expected config/fields
+ assertEquals(newApp.getId(), origApp.getId());
+
+ assertEquals(newApp.getChildren().size(), 1, "children="+newApp.getChildren());
+ MyEntity newE = (MyEntity) Iterables.get(newApp.getChildren(), 0);
+ assertEquals(newE.getId(), origE.getId());
+
+ assertEquals(newE.getChildren().size(), 0);
+
+ assertNotSame(origApp, newApp);
+ assertNotSame(origApp.getManagementContext(), newApp.getManagementContext());
+ assertNotSame(origE, newE);
+ }
+
+ @Test
+ public void testRestoresEntityHierarchy() throws Exception {
+ MyEntity origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntity.class));
+ MyEntity origE2 = origE.addChild(EntitySpecs.spec(MyEntity.class));
+ Entities.manage(origE2);
+
+ TestApplication newApp = rebind();
+
+ // Assert has expected config/fields
+ assertEquals(newApp.getId(), origApp.getId());
+
+ assertEquals(newApp.getChildren().size(), 1, "children="+newApp.getChildren());
+ MyEntity newE = (MyEntity) Iterables.get(newApp.getChildren(), 0);
+ assertEquals(newE.getId(), origE.getId());
+
+ assertEquals(newE.getChildren().size(), 1);
+ MyEntity newE2 = (MyEntity) Iterables.get(newE.getChildren(), 0);
+ assertEquals(newE2.getId(), origE2.getId());
+
+ assertNotSame(origApp, newApp);
+ assertNotSame(origApp.getManagementContext(), newApp.getManagementContext());
+ assertNotSame(origE, newE);
+ assertNotSame(origE2, newE2);
+ }
+
+ @Test
+ public void testRestoresGroupMembers() throws Exception {
+ MyEntity origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntity.class));
+ MyEntity origE2 = origApp.createAndManageChild(EntitySpecs.spec(MyEntity.class));
+ BasicGroup origG = origApp.createAndManageChild(EntitySpecs.spec(BasicGroup.class));
+ origG.addMember(origE);
+ origG.addMember(origE2);
+
+ TestApplication newApp = rebind();
+
+ BasicGroup newG = (BasicGroup) Iterables.find(newApp.getChildren(), Predicates.instanceOf(BasicGroup.class));
+ Iterable<Entity> newEs = Iterables.filter(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
+ assertEquals(ImmutableSet.copyOf(newG.getMembers()), ImmutableSet.copyOf(newEs));
+ }
+
+ @Test
+ public void testRestoresEntityConfig() throws Exception {
+ MyEntity origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntity.class)
+ .configure("myconfig", "myval"));
+
+ TestApplication newApp = rebind();
+ MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
+ assertEquals(newE.getConfig(MyEntity.MY_CONFIG), "myval");
+ }
+
+ @Test
+ public void testRestoresEntitySensors() throws Exception {
+ AttributeSensor<String> myCustomAttribute = new BasicAttributeSensor<String>(String.class, "my.custom.attribute");
+
+ MyEntity origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntity.class));
+ origE.setAttribute(myCustomAttribute, "myval");
+
+ TestApplication newApp = rebind();
+ MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
+ assertEquals(newE.getAttribute(myCustomAttribute), "myval");
+ }
+
+ @Test
+ public void testRestoresEntityIdAndDisplayName() throws Exception {
+ MyEntity origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntity.class)
+ .displayName("mydisplayname"));
+ String eId = origE.getId();
+
+ TestApplication newApp = rebind();
+ MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
+ assertEquals(newE.getId(), eId);
+ assertEquals(newE.getDisplayName(), "mydisplayname");
+ }
+
+ @Test
+ public void testRebindsSubscriptions() throws Exception {
+ MyEntity2 origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntity2.class)
+ .configure("subscribe", true));
+
+ TestApplication newApp = rebind();
+ MyEntity2 newE = (MyEntity2) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity2.class));
+
+ newApp.setAttribute(TestApplication.MY_ATTRIBUTE, "mysensorval");
+ TestUtils.assertEventually(Suppliers.ofInstance(newE.getEvents()), Predicates.equalTo(ImmutableList.of("mysensorval")));
+ }
+
+ @Test
+ public void testHandlesReferencingOtherEntities() throws Exception {
+ MyEntity origOtherE = origApp.createAndManageChild(EntitySpecs.spec(MyEntity.class));
+ MyEntityReffingOthers origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntityReffingOthers.class)
+ .configure("entityRef", origOtherE));
+ origE.setAttribute(MyEntityReffingOthers.ENTITY_REF_SENSOR, origOtherE);
+
+ TestApplication newApp = rebind();
+ MyEntityReffingOthers newE = (MyEntityReffingOthers) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntityReffingOthers.class));
+ MyEntity newOtherE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
+
+ assertAttributeEquals(newE, MyEntityReffingOthers.ENTITY_REF_SENSOR, newOtherE);
+ assertConfigEquals(newE, MyEntityReffingOthers.ENTITY_REF_CONFIG, newOtherE);
+ }
+
+ @Test
+ public void testHandlesReferencingOtherLocations() throws Exception {
+ MyLocation origLoc = origManagementContext.getLocationManager().createLocation(LocationSpec.spec(MyLocation.class));
+ MyEntityReffingOthers origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntityReffingOthers.class)
+ .configure("locationRef", origLoc));
+ origE.setAttribute(MyEntityReffingOthers.LOCATION_REF_SENSOR, origLoc);
+ origApp.start(ImmutableList.of(origLoc));
+
+ TestApplication newApp = rebind();
+ MyEntityReffingOthers newE = (MyEntityReffingOthers) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntityReffingOthers.class));
+ MyLocation newLoc = (MyLocation) Iterables.getOnlyElement(newApp.getLocations());
+
+ assertAttributeEquals(newE, MyEntityReffingOthers.LOCATION_REF_SENSOR, newLoc);
+ assertConfigEquals(newE, MyEntityReffingOthers.LOCATION_REF_CONFIG, newLoc);
+ }
+
+ @Test
+ public void testEntityManagementLifecycleAndVisibilityDuringRebind() throws Exception {
+ MyLatchingEntityImpl.latching = false;
+ MyLatchingEntity origE = origApp.createAndManageChild(EntitySpecs.spec(MyLatchingEntity.class));
+ MyLatchingEntityImpl.reset(); // after origE has been managed
+ MyLatchingEntityImpl.latching = true;
+
+ // Serialize and rebind, but don't yet manage the app
+ RebindTestUtils.waitForPersisted(origApp);
+ RebindTestUtils.checkCurrentMementoSerializable(origApp);
+ InmemoryDatagrid newDatagrid = origDatagrid.cloneData();
+ final LocalManagementContext newManagementContext = new LocalManagementContext(newDatagrid);
+
+ Thread thread = new Thread() {
+ @Override public void run() {
+ try {
+ RebindTestUtils.rebind(newManagementContext, getClass().getClassLoader());
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+ };
+ try {
+ thread.start();
+
+ assertTrue(MyLatchingEntityImpl.reconstructStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertNull(newManagementContext.getEntityManager().getEntity(origApp.getId()));
+ assertNull(newManagementContext.getEntityManager().getEntity(origE.getId()));
+ assertTrue(MyLatchingEntityImpl.managingStartedLatch.getCount() > 0);
+
+ MyLatchingEntityImpl.reconstructContinuesLatch.countDown();
+ assertTrue(MyLatchingEntityImpl.managingStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertNotNull(newManagementContext.getEntityManager().getEntity(origApp.getId()));
+ assertNull(newManagementContext.getEntityManager().getEntity(origE.getId()));
+ assertTrue(MyLatchingEntityImpl.managedStartedLatch.getCount() > 0);
+
+ MyLatchingEntityImpl.managingContinuesLatch.countDown();
+ assertTrue(MyLatchingEntityImpl.managedStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertNotNull(newManagementContext.getEntityManager().getEntity(origApp.getId()));
+ assertNotNull(newManagementContext.getEntityManager().getEntity(origE.getId()));
+ MyLatchingEntityImpl.managedContinuesLatch.countDown();
+
+ thread.join(TIMEOUT_MS);
+ assertFalse(thread.isAlive());
+
+ } finally {
+ thread.interrupt();
+ MyLatchingEntityImpl.reset();
+ }
+ }
+
+ @Test(groups="Integration") // takes more than 4 seconds, due to assertContinually calls
+ public void testSubscriptionAndPublishingOnlyActiveWhenEntityIsManaged() throws Exception {
+ MyLatchingEntityImpl.latching = false;
+ MyLatchingEntity origE = origApp.createAndManageChild(EntitySpecs.spec(MyLatchingEntity.class)
+ .configure("subscribe", TestApplication.MY_ATTRIBUTE)
+ .configure("publish", "myvaltopublish"));
+ MyLatchingEntityImpl.reset(); // after origE has been managed
+ MyLatchingEntityImpl.latching = true;
+
+ // Serialize and rebind, but don't yet manage the app
+ RebindTestUtils.waitForPersisted(origApp);
+ RebindTestUtils.checkCurrentMementoSerializable(origApp);
+ InmemoryDatagrid newDatagrid = origDatagrid.cloneData();
+ final LocalManagementContext newManagementContext = new LocalManagementContext(newDatagrid);
+
+ Thread thread = new Thread() {
+ public void run() {
+ try {
+ RebindTestUtils.rebind(newManagementContext, getClass().getClassLoader());
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+ };
+ try {
+ thread.start();
+ final List<Object> events = new CopyOnWriteArrayList<Object>();
+
+ newManagementContext.getSubscriptionManager().subscribe(null, MyLatchingEntityImpl.MY_SENSOR, new SensorEventListener<Object>() {
+ @Override public void onEvent(SensorEvent<Object> event) {
+ events.add(event.getValue());
+ }});
+
+ // In entity's reconstruct, publish-events are queued, and subscriptions don't yet take effect
+ assertTrue(MyLatchingEntityImpl.reconstructStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ newManagementContext.getSubscriptionManager().publish(new BasicSensorEvent<String>(TestApplication.MY_ATTRIBUTE, null, "myvaltooearly"));
+
+ TestUtils.assertContinuallyFromJava(Suppliers.ofInstance(MyLatchingEntityImpl.events), Predicates.equalTo(Collections.emptyList()));
+ TestUtils.assertContinuallyFromJava(Suppliers.ofInstance(events), Predicates.equalTo(Collections.emptyList()));
+
+
+ // When the entity is notified of "managing", then subscriptions take effect (but missed events not delivered);
+ // published events remain queued
+ MyLatchingEntityImpl.reconstructContinuesLatch.countDown();
+ assertTrue(MyLatchingEntityImpl.managingStartedLatch.getCount() > 0);
+
+ TestUtils.assertContinuallyFromJava(Suppliers.ofInstance(events), Predicates.equalTo(Collections.emptyList()));
+ TestUtils.assertContinuallyFromJava(Suppliers.ofInstance(MyLatchingEntityImpl.events), Predicates.equalTo(Collections.emptyList()));
+
+ newManagementContext.getSubscriptionManager().publish(new BasicSensorEvent<String>(TestApplication.MY_ATTRIBUTE, null, "myvaltoreceive"));
+ TestUtils.assertEventually(Suppliers.ofInstance(MyLatchingEntityImpl.events), Predicates.equalTo(ImmutableList.of("myvaltoreceive")));
+
+ // When the entity is notified of "managed", its events are only then delivered
+ MyLatchingEntityImpl.managingContinuesLatch.countDown();
+ assertTrue(MyLatchingEntityImpl.managedStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ TestUtils.assertEventually(Suppliers.ofInstance(MyLatchingEntityImpl.events), Predicates.equalTo(ImmutableList.of("myvaltoreceive")));
+
+ MyLatchingEntityImpl.managedContinuesLatch.countDown();
+
+ thread.join(TIMEOUT_MS);
+ assertFalse(thread.isAlive());
+
+ } finally {
+ thread.interrupt();
+ MyLatchingEntityImpl.reset();
+ }
+
+ }
+
+ @Test
+ public void testRestoresConfigKeys() throws Exception {
+ TestEntity origE = origApp.createAndManageChild(EntitySpecs.spec(TestEntity.class));
+ origE.setConfig(TestEntity.CONF_LIST_PLAIN, ImmutableList.of("val1", "val2"));
+ origE.setConfig(TestEntity.CONF_MAP_PLAIN, ImmutableMap.of("akey", "aval"));
+
+ TestApplication newApp = rebind();
+ final TestEntity newE = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
+
+ assertEquals(newE.getConfig(TestEntity.CONF_LIST_PLAIN), ImmutableList.of("val1", "val2"));
+ assertEquals(newE.getConfig(TestEntity.CONF_MAP_PLAIN), ImmutableMap.of("akey", "aval"));
+ }
+
+
+ @Test
+ public void testRestoresListConfigKey() throws Exception {
+ TestEntity origE = origApp.createAndManageChild(EntitySpecs.spec(TestEntity.class));
+ origE.setConfig(TestEntity.CONF_SET_THING.subKey(), "val1");
+ origE.setConfig(TestEntity.CONF_SET_THING.subKey(), "val2");
+
+ TestApplication newApp = rebind();
+ final TestEntity newE = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
+
+ assertEquals(newE.getConfig(TestEntity.CONF_SET_THING), ImmutableSet.of("val1", "val2"));
+ }
+
+ @Test
+ public void testRestoresMapConfigKey() throws Exception {
+ TestEntity origE = origApp.createAndManageChild(EntitySpecs.spec(TestEntity.class));
+ origE.setConfig(TestEntity.CONF_MAP_THING.subKey("akey"), "aval");
+ origE.setConfig(TestEntity.CONF_MAP_THING.subKey("bkey"), "bval");
+
+ TestApplication newApp = rebind();
+ final TestEntity newE = (TestEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(TestEntity.class));
+
+ assertEquals(newE.getConfig(TestEntity.CONF_MAP_THING), ImmutableMap.of("akey", "aval", "bkey", "bval"));
+ }
+
+ @Test
+ public void testRebindPreservesInheritedConfig() throws Exception {
+ MyEntity origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntity.class));
+ origApp.setConfig(MyEntity.MY_CONFIG, "myValInSuper");
+
+ // rebind: inherited config is preserved
+ TestApplication newApp = rebind();
+ MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
+
+ assertEquals(newE.getConfig(MyEntity.MY_CONFIG), "myValInSuper");
+ assertEquals(newApp.getConfig(MyEntity.MY_CONFIG), "myValInSuper");
+
+ // This config should be inherited by dynamically-added children of app
+ MyEntity newE2 = newApp.createAndManageChild(EntitySpecs.spec(MyEntity.class));
+ Entities.manage(newE2);
+
+ assertEquals(newE2.getConfig(MyEntity.MY_CONFIG), "myValInSuper");
+
+ }
+
+ @Test
+ public void testRebindPreservesGetConfigWithDefault() throws Exception {
+ MyEntity origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntity.class));
+
+ assertNull(origE.getConfig(MyEntity.MY_CONFIG));
+ assertEquals(origE.getConfig(MyEntity.MY_CONFIG, "mydefault"), "mydefault");
+
+ TestApplication newApp = rebind();
+ MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
+
+ assertNull(newE.getConfig(MyEntity.MY_CONFIG));
+ assertEquals(newE.getConfig(MyEntity.MY_CONFIG, "mydefault"), "mydefault");
+ }
+
+ @Test
+ public void testRebindPersistsNullAttribute() throws Exception {
+ MyEntity origE = origApp.createAndManageChild(EntitySpecs.spec(MyEntity.class));
+ origE.setAttribute(MyEntity.MY_SENSOR, null);
+
+ assertNull(origE.getAttribute(MyEntity.MY_SENSOR));
+
+ TestApplication newApp = rebind();
+ MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
+
+ assertNull(newE.getAttribute(MyEntity.MY_SENSOR));
+ }
+
+ private TestApplication rebind() throws Exception {
+ RebindTestUtils.waitForPersisted(origApp);
+ RebindTestUtils.checkCurrentMementoSerializable(origApp);
+ InmemoryDatagrid newDatagrid = origDatagrid.cloneData();
+ return (TestApplication) RebindTestUtils.rebind(newDatagrid, getClass().getClassLoader());
+ }
+
+ // TODO Don't want to extend EntityLocal, but tests want to call app.setAttribute
+ @ImplementedBy(MyEntityImpl.class)
+ public interface MyEntity extends Entity, Startable, EntityLocal {
+ @SetFromFlag("myconfig")
+ public static final ConfigKey<String> MY_CONFIG = new BasicConfigKey<String>(
+ String.class, "test.myentity.myconfig", "My test config");
+
+ public static final AttributeSensor<String> MY_SENSOR = new BasicAttributeSensor<String>(
+ String.class, "test.myentity.mysensor", "My test sensor");
+ }
+
+ public static class MyEntityImpl extends AbstractEntity implements MyEntity {
+ private static final long serialVersionUID = 1L;
+
+ private final Object dummy = new Object(); // so not serializable
+
+ public MyEntityImpl() {
+ super();
+ }
+
+ @Override
+ public void start(Collection<? extends Location> locations) {
+ addLocations(locations);
+ }
+
+ @Override
+ public void stop() {
+ }
+
+ @Override
+ public void restart() {
+ }
+ }
+
+ // TODO Don't want to extend EntityLocal, but tests want to call app.setAttribute
+ @ImplementedBy(MyEntityReffingOthersImpl.class)
+ public interface MyEntityReffingOthers extends Entity, EntityLocal {
+ @SetFromFlag("entityRef")
+ public static final ConfigKey<Entity> ENTITY_REF_CONFIG = new BasicConfigKey<Entity>(
+ Entity.class, "test.config.entityref", "Ref to other entity");
+
+ @SetFromFlag("locationRef")
+ public static final ConfigKey<Location> LOCATION_REF_CONFIG = new BasicConfigKey<Location>(
+ Location.class, "test.config.locationref", "Ref to other location");
+
+ public static final AttributeSensor<Entity> ENTITY_REF_SENSOR = new BasicAttributeSensor<Entity>(
+ Entity.class, "test.attribute.entityref", "Ref to other entity");
+
+ public static final AttributeSensor<Location> LOCATION_REF_SENSOR = new BasicAttributeSensor<Location>(
+ Location.class, "test.attribute.locationref", "Ref to other location");
+ }
+
+ public static class MyEntityReffingOthersImpl extends AbstractEntity implements MyEntityReffingOthers {
+ private static final long serialVersionUID = 1L;
+
+ @SetFromFlag("entityRef")
+ public static final ConfigKey<Entity> ENTITY_REF_CONFIG = new BasicConfigKey<Entity>(
+ Entity.class, "test.config.entityref", "Ref to other entity");
+
+ @SetFromFlag("locationRef")
+ public static final ConfigKey<Location> LOCATION_REF_CONFIG = new BasicConfigKey<Location>(
+ Location.class, "test.config.locationref", "Ref to other location");
+
+ public static final AttributeSensor<Entity> ENTITY_REF_SENSOR = new BasicAttributeSensor<Entity>(
+ Entity.class, "test.attribute.entityref", "Ref to other entity");
+
+ public static final AttributeSensor<Location> LOCATION_REF_SENSOR = new BasicAttributeSensor<Location>(
+ Location.class, "test.attribute.locationref", "Ref to other location");
+
+ private final Object dummy = new Object(); // so not serializable
+ }
+
+ @ImplementedBy(MyEntity2Impl.class)
+ public interface MyEntity2 extends Entity {
+ @SetFromFlag("myconfig")
+ public static final ConfigKey<String> MY_CONFIG = new BasicConfigKey<String>(
+ String.class, "test.myconfig", "My test config");
+
+ @SetFromFlag("subscribe")
+ public static final ConfigKey<Boolean> SUBSCRIBE = new BasicConfigKey<Boolean>(
+ Boolean.class, "test.subscribe", "Whether to do some subscriptions on re-bind", false);
+
+ public List<String> getEvents();
+ }
+
+ public static class MyEntity2Impl extends AbstractEntity implements MyEntity2 {
+ private static final long serialVersionUID = 1L;
+
+ final List<String> events = new CopyOnWriteArrayList<String>();
+
+ private final Object dummy = new Object(); // so not serializable
+
+ public MyEntity2Impl() {
+ super();
+ }
+
+ public List<String> getEvents() {
+ return events;
+ }
+
+ @Override
+ public void onManagementStarting() {
+ if (getConfig(SUBSCRIBE)) {
+ subscribe(getApplication(), TestApplication.MY_ATTRIBUTE, new SensorEventListener<String>() {
+ @Override public void onEvent(SensorEvent<String> event) {
+ events.add(event.getValue());
+ }
+ });
+ }
+ }
+ }
+
+ @ImplementedBy(MyLatchingEntityImpl.class)
+ public interface MyLatchingEntity extends Entity {
+ @SetFromFlag("subscribe")
+ public static final ConfigKey<AttributeSensor<?>> SUBSCRIBE = new BasicConfigKey(
+ AttributeSensor.class, "test.mylatchingentity.subscribe", "Sensor to subscribe to (or null means don't)", null);
+
+ @SetFromFlag("publish")
+ public static final ConfigKey<String> PUBLISH = new BasicConfigKey<String>(
+ String.class, "test.mylatchingentity.publish", "Value to publish (or null means don't)", null);
+
+ public static final AttributeSensor<String> MY_SENSOR = new BasicAttributeSensor<String>(
+ String.class, "test.mylatchingentity.mysensor", "My test sensor");
+ }
+
+ public static class MyLatchingEntityImpl extends AbstractEntity implements MyLatchingEntity {
+ private static final long serialVersionUID = 1L;
+ static volatile CountDownLatch reconstructStartedLatch;
+ static volatile CountDownLatch reconstructContinuesLatch;
+ static volatile CountDownLatch managingStartedLatch;
+ static volatile CountDownLatch managingContinuesLatch;
+ static volatile CountDownLatch managedStartedLatch;
+ static volatile CountDownLatch managedContinuesLatch;
+
+ static volatile boolean latching = false;
+ static volatile List<Object> events;
+
+ static void reset() {
+ latching = false;
+ events = new CopyOnWriteArrayList<Object>();
+
+ reconstructStartedLatch = new CountDownLatch(1);
+ reconstructContinuesLatch = new CountDownLatch(1);
+ managingStartedLatch = new CountDownLatch(1);
+ managingContinuesLatch = new CountDownLatch(1);
+ managedStartedLatch = new CountDownLatch(1);
+ managedContinuesLatch = new CountDownLatch(1);
+ }
+
+ public MyLatchingEntityImpl() {
+ super();
+ }
+
+ @Override
+ public void reconstruct() {
+ if (getConfig(SUBSCRIBE) != null) {
+ getManagementSupport().getSubscriptionContext().subscribe(null, getConfig(SUBSCRIBE), new SensorEventListener<Object>() {
+ @Override public void onEvent(SensorEvent<Object> event) {
+ events.add(event.getValue());
+ }});
+ }
+
+ if (getConfig(PUBLISH) != null) {
+ setAttribute(MY_SENSOR, getConfig(PUBLISH));
+ }
+
+ if (latching) {
+ reconstructStartedLatch.countDown();
+ try {
+ reconstructContinuesLatch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeInterruptedException(e);
+ }
+ }
+ }
+
+ @Override
+ public void onManagementStarting() {
+ if (latching) {
+ managingStartedLatch.countDown();
+ try {
+ managingContinuesLatch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeInterruptedException(e);
+ }
+ }
+ }
+
+ @Override
+ public void onManagementStarted() {
+ if (latching) {
+ managedStartedLatch.countDown();
+ try {
+ managedContinuesLatch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeInterruptedException(e);
+ }
+ }
+ }
+
+ @Override
+ public RebindSupport<EntityMemento> getRebindSupport() {
+ return new BasicEntityRebindSupport(this) {
+ @Override protected void doReconstruct(RebindContext rebindContext, EntityMemento memento) {
+ MyLatchingEntityImpl.this.reconstruct();
+ }
+ };
+ }
+ }
+}
diff --git a/core/src/test/java/brooklyn/internal/rebind/RebindLocationTest.java.inprogress b/core/src/test/java/brooklyn/internal/rebind/RebindLocationTest.java.inprogress
new file mode 100644
index 0000000..9ca157f
--- /dev/null
+++ b/core/src/test/java/brooklyn/internal/rebind/RebindLocationTest.java.inprogress
@@ -0,0 +1,276 @@
+package brooklyn.internal.rebind;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.Application;
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.basic.EntityInternal;
+import brooklyn.entity.rebind.RebindEntityTest.MyApplication;
+import brooklyn.entity.rebind.RebindEntityTest.MyApplicationImpl;
+import brooklyn.entity.rebind.RebindEntityTest.MyEntity;
+import brooklyn.entity.rebind.RebindEntityTest.MyEntityImpl;
+import brooklyn.location.Location;
+import brooklyn.location.basic.AbstractLocation;
+import brooklyn.management.ManagementContext;
+import brooklyn.mementos.LocationMemento;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.flags.SetFromFlag;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+public class RebindLocationTest {
+
+ private ClassLoader classLoader = getClass().getClassLoader();
+ private ManagementContext managementContext;
+ private MyApplication origApp;
+ private MyEntity origE;
+ private File mementoDir;
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ mementoDir = Files.createTempDir();
+ managementContext = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader, 1);
+ origApp = new MyApplicationImpl();
+ origE = new MyEntityImpl(origApp);
+ Entities.startManagement(origApp, managementContext);
+ }
+
+ @AfterMethod
+ public void tearDown() throws Exception {
+ if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+ }
+
+ @Test
+ public void testSetsLocationOnEntities() throws Exception {
+ MyLocation origLoc = new MyLocation(MutableMap.of("name", "mylocname"));
+ origApp.start(ImmutableList.of(origLoc));
+
+ MyApplication newApp = (MyApplication) rebind();
+ MyEntity newE = (MyEntity) Iterables.find(newApp.getChildren(), Predicates.instanceOf(MyEntity.class));
+
+ assertEquals(newApp.getLocations().size(), 1, "locs="+newE.getLocations());
+ assertTrue(Iterables.get(newApp.getLocations(), 0) instanceof MyLocation);
+
+ assertEquals(newE.getLocations().size(), 1, "locs="+newE.getLocations());
+ assertTrue(Iterables.get(newE.getLocations(), 0) instanceof MyLocation);
+ }
+
+ @Test
+ public void testRestoresLocationIdAndDisplayName() throws Exception {
+ MyLocation origLoc = new MyLocation(MutableMap.of("name", "mylocname"));
+ origApp.start(ImmutableList.of(origLoc));
+
+ MyApplication newApp = (MyApplication) rebind();
+ MyLocation newLoc = (MyLocation) Iterables.get(newApp.getLocations(), 0);
+
+ assertEquals(newLoc.getId(), origLoc.getId());
+ assertEquals(newLoc.getName(), origLoc.getName());
+ }
+
+ @Test
+ public void testCanCustomizeLocationRebind() throws Exception {
+ MyLocationCustomProps origLoc = new MyLocationCustomProps(MutableMap.of("name", "mylocname", "myfield", "myval"));
+ origApp.start(ImmutableList.of(origLoc));
+
+ MyApplication newApp = (MyApplication) rebind();
+ MyLocationCustomProps newLoc2 = (MyLocationCustomProps) Iterables.get(newApp.getLocations(), 0);
+
+ assertEquals(newLoc2.getId(), origLoc.getId());
+ assertEquals(newLoc2.getName(), origLoc.getName());
+ assertEquals(newLoc2.rebound, true);
+ assertEquals(newLoc2.myfield, "myval");
+ }
+
+ @Test
+ public void testRestoresFieldsWithSetFromFlag() throws Exception {
+ MyLocation origLoc = new MyLocation(MutableMap.of("myfield", "myval"));
+ origApp.start(ImmutableList.of(origLoc));
+
+ MyApplication newApp = (MyApplication) rebind();
+ MyLocation newLoc = (MyLocation) Iterables.get(newApp.getLocations(), 0);
+
+ assertEquals(newLoc.myfield, "myval");
+ }
+
+ @Test
+ public void testRestoresAtomicLongWithSetFromFlag() throws Exception {
+ MyLocation origLoc = new MyLocation(MutableMap.of("myAtomicLong", "123"));
+ origApp.start(ImmutableList.of(origLoc));
+
+ origLoc.myAtomicLong.incrementAndGet();
+ assertEquals(origLoc.myAtomicLong.get(), 124L);
+ ((EntityInternal)origApp).getManagementContext().getRebindManager().getChangeListener().onChanged(origLoc);
+
+ MyApplication newApp = (MyApplication) rebind();
+ MyLocation newLoc = (MyLocation) Iterables.get(newApp.getLocations(), 0);
+
+ // should get _modified_ value, not the one in the config map
+ assertEquals(newLoc.myAtomicLong.get(), 124L);
+ }
+
+ @Test
+ public void testIgnoresTransientFieldsNotSetFromFlag() throws Exception {
+ MyLocation origLoc = new MyLocation(MutableMap.of());
+ origLoc.myTransientFieldNotSetFromFlag = "myval";
+ origApp.start(ImmutableList.of(origLoc));
+
+ MyApplication newApp = (MyApplication) rebind();
+ MyLocation newLoc = (MyLocation) Iterables.get(newApp.getLocations(), 0);
+
+ // transient fields normally not persisted
+ assertEquals(newLoc.myTransientFieldNotSetFromFlag, null);
+ }
+
+ @Test
+ public void testIgnoresTransientFieldsSetFromFlag() throws Exception {
+ MyLocation origLoc = new MyLocation(MutableMap.of("myTransientFieldSetFromFlag", "myval"));
+ origApp.start(ImmutableList.of(origLoc));
+
+ MyApplication newApp = (MyApplication) rebind();
+ MyLocation newLoc = (MyLocation) Iterables.get(newApp.getLocations(), 0);
+
+ assertEquals(newLoc.myTransientFieldSetFromFlag, null);
+ }
+
+ @Test
+ public void testIgnoresStaticFieldsNotSetFromFlag() throws Exception {
+ MyLocation origLoc = new MyLocation(MutableMap.of());
+ origLoc.myStaticFieldNotSetFromFlag = "myval";
+ origApp.start(ImmutableList.of(origLoc));
+
+ RebindTestUtils.waitForPersisted(origApp);
+ MyLocation.myStaticFieldNotSetFromFlag = "mynewval";
+ MyApplication newApp = (MyApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+ MyLocation newLoc = (MyLocation) Iterables.get(newApp.getLocations(), 0);
+
+ // static fields normally not persisted (we see new value)
+ assertEquals(newLoc.myStaticFieldNotSetFromFlag, "mynewval");
+ }
+
+ @Test
+ public void testIgnoresStaticFieldsSetFromFlag() throws Exception {
+ MyLocation origLoc = new MyLocation(MutableMap.of("myStaticFieldSetFromFlag", "myval"));
+ origApp.start(ImmutableList.of(origLoc));
+
+ RebindTestUtils.waitForPersisted(origApp);
+ MyLocation.myStaticFieldSetFromFlag = "mynewval"; // not auto-checkpointed
+ MyApplication newApp = (MyApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+ MyLocation newLoc = (MyLocation) Iterables.get(newApp.getLocations(), 0);
+
+ assertEquals(newLoc.myStaticFieldSetFromFlag, "mynewval");
+ }
+
+ @Test
+ public void testHandlesFieldReferencingOtherLocations() throws Exception {
+ MyLocation origOtherLoc = new MyLocation();
+ MyLocationReffingOthers origLoc = new MyLocationReffingOthers(MutableMap.of("otherLocs", ImmutableList.of(origOtherLoc), "myfield", "myval"));
+ origOtherLoc.setParentLocation(origLoc);
+
+ origApp.start(ImmutableList.of(origLoc));
+
+ Application newApp = rebind();
+ MyLocationReffingOthers newLoc = (MyLocationReffingOthers) Iterables.get(newApp.getLocations(), 0);
+
+ assertEquals(newLoc.getChildLocations().size(), 1);
+ assertTrue(Iterables.get(newLoc.getChildLocations(), 0) instanceof MyLocation, "children="+newLoc.getChildLocations());
+ assertEquals(newLoc.otherLocs, ImmutableList.copyOf(newLoc.getChildLocations()));
+
+ // Confirm this didn't override other values (e.g. setting other fields back to their defaults, as was once the case!)
+ assertEquals(newLoc.myfield, "myval");
+ }
+
+ private MyApplication rebind() throws Exception {
+ RebindTestUtils.waitForPersisted(origApp);
+ return (MyApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+ }
+
+ public static class MyLocation extends AbstractLocation {
+ private static final long serialVersionUID = 1L;
+
+ @SetFromFlag
+ String myfield;
+
+ @SetFromFlag(defaultVal="1")
+ AtomicLong myAtomicLong;
+
+ private final Object dummy = new Object(); // so not serializable
+
+ @SetFromFlag
+ transient String myTransientFieldSetFromFlag;
+
+ transient String myTransientFieldNotSetFromFlag;
+
+ @SetFromFlag
+ static String myStaticFieldSetFromFlag;
+
+ static String myStaticFieldNotSetFromFlag;
+
+ public MyLocation() {
+ }
+
+ public MyLocation(Map flags) {
+ super(flags);
+ }
+ }
+
+ public static class MyLocationReffingOthers extends AbstractLocation {
+ private static final long serialVersionUID = 1L;
+
+ @SetFromFlag(defaultVal="a")
+ String myfield;
+
+ @SetFromFlag
+ List<Location> otherLocs;
+
+ private final Object dummy = new Object(); // so not serializable
+
+ public MyLocationReffingOthers(Map flags) {
+ super(flags);
+ }
+ }
+
+ public static class MyLocationCustomProps extends AbstractLocation {
+ private static final long serialVersionUID = 1L;
+
+ String myfield;
+ boolean rebound;
+
+ private final Object dummy = new Object(); // so not serializable
+
+ public MyLocationCustomProps() {
+ }
+
+ public MyLocationCustomProps(Map flags) {
+ super(flags);
+ myfield = (String) flags.get("myfield");
+ }
+
+ @Override
+ public RebindSupport<LocationMemento> getRebindSupport() {
+ return new BasicLocationRebindSupport(this) {
+ @Override public LocationMemento getMemento() {
+ return getMementoWithProperties(MutableMap.<String,Object>of("myfield", myfield));
+ }
+ @Override
+ protected void doReconsruct(RebindContext rebindContext, LocationMemento memento) {
+ super.doReconsruct(rebindContext, memento);
+ myfield = (String) memento.getCustomField("myfield");
+ rebound = true;
+ }
+ };
+ }
+ }
+}
diff --git a/core/src/test/java/brooklyn/internal/rebind/RebindPolicyTest.java.inprogress b/core/src/test/java/brooklyn/internal/rebind/RebindPolicyTest.java.inprogress
new file mode 100644
index 0000000..2635ce1
--- /dev/null
+++ b/core/src/test/java/brooklyn/internal/rebind/RebindPolicyTest.java.inprogress
@@ -0,0 +1,86 @@
+package brooklyn.internal.rebind;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.management.ManagementContext;
+import brooklyn.policy.Policy;
+import brooklyn.policy.basic.AbstractPolicy;
+import brooklyn.test.entity.TestApplication;
+import brooklyn.test.entity.TestApplicationImpl;
+import brooklyn.util.collections.MutableMap;
+import brooklyn.util.flags.SetFromFlag;
+
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+public class RebindPolicyTest {
+
+ private ClassLoader classLoader = getClass().getClassLoader();
+ private ManagementContext managementContext;
+ private TestApplication origApp;
+ private File mementoDir;
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ mementoDir = Files.createTempDir();
+ managementContext = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader);
+ origApp = new TestApplicationImpl();
+ Entities.startManagement(origApp, managementContext);
+ }
+
+ @AfterMethod
+ public void tearDown() throws Exception {
+ if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+ }
+
+ /*
+ * FIXME Need to decide what to do about policy mementos and restoring.
+ * Lots of places register anonymous inner class policies.
+ * (e.g. AbstractController registering a AbstractMembershipTrackingPolicy)
+ * Also, the entity constructor often re-creates the policy.
+ *
+ * See RebindManagerImpl.CheckpointingChangeListener.onChanged(Entity) and
+ * MementosGenerator.newEntityMementoBuilder()
+ */
+ @Test(enabled=false)
+ public void testRestoresSimplePolicy() throws Exception {
+ MyPolicy origPolicy = new MyPolicy(MutableMap.of("myfield", "myval"));
+ origApp.addPolicy(origPolicy);
+
+ TestApplication newApp = rebind();
+ Collection<Policy> policies = newApp.getPolicies();
+ MyPolicy newPolicy = (MyPolicy) Iterables.get(policies, 0);
+
+ assertEquals(newPolicy.myfield, origPolicy.myfield);
+ }
+
+ private TestApplication rebind() throws Exception {
+ RebindTestUtils.waitForPersisted(origApp);
+ return (TestApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+ }
+
+ public static class MyPolicy extends AbstractPolicy {
+ private static final long serialVersionUID = 1L;
+
+ @SetFromFlag
+ String myfield;
+
+ private final Object dummy = new Object(); // so not serializable
+
+ public MyPolicy() {
+ }
+
+ public MyPolicy(Map flags) {
+ super(flags);
+ }
+ }
+}
diff --git a/core/src/test/java/brooklyn/internal/rebind/RebindSshMachineLocationTest.java.inprogress b/core/src/test/java/brooklyn/internal/rebind/RebindSshMachineLocationTest.java.inprogress
new file mode 100644
index 0000000..3437e30
--- /dev/null
+++ b/core/src/test/java/brooklyn/internal/rebind/RebindSshMachineLocationTest.java.inprogress
@@ -0,0 +1,61 @@
+package brooklyn.internal.rebind;
+
+import static org.testng.Assert.assertEquals;
+
+import java.io.File;
+import java.util.Collections;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.entity.basic.Entities;
+import brooklyn.entity.rebind.RebindEntityTest.MyApplication;
+import brooklyn.entity.rebind.RebindEntityTest.MyApplicationImpl;
+import brooklyn.location.basic.SshMachineLocation;
+import brooklyn.management.ManagementContext;
+import brooklyn.util.collections.MutableMap;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.io.Files;
+
+public class RebindSshMachineLocationTest {
+
+ private ClassLoader classLoader = getClass().getClassLoader();
+ private ManagementContext managementContext;
+ private MyApplication origApp;
+ private SshMachineLocation origLoc;
+ private File mementoDir;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ mementoDir = Files.createTempDir();
+ managementContext = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader, 1);
+ origApp = new MyApplicationImpl();
+ origLoc = new SshMachineLocation(MutableMap.of("address", "localhost"));
+ Entities.startManagement(origApp, managementContext);
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ if (mementoDir != null) RebindTestUtils.deleteMementoDir(mementoDir);
+ }
+
+ @Test(groups="Integration")
+ public void testMachineUsableAfterRebind() throws Exception {
+ origApp.start(ImmutableList.of(origLoc));
+
+ assertEquals(origLoc.execScript(Collections.<String,Object>emptyMap(), "mysummary", ImmutableList.of("true")), 0);
+
+ MyApplication newApp = (MyApplication) rebind();
+ SshMachineLocation newLoc = (SshMachineLocation) Iterables.getOnlyElement(newApp.getLocations(), 0);
+
+ assertEquals(newLoc.execScript(Collections.<String,Object>emptyMap(), "mysummary", ImmutableList.of("true")), 0);
+ }
+
+ private MyApplication rebind() throws Exception {
+ RebindTestUtils.waitForPersisted(origApp);
+ return (MyApplication) RebindTestUtils.rebind(mementoDir, getClass().getClassLoader());
+ }
+}
diff --git a/core/src/test/java/brooklyn/internal/rebind/RebindTestUtils.java b/core/src/test/java/brooklyn/internal/rebind/RebindTestUtils.java
new file mode 100644
index 0000000..dc5884c
--- /dev/null
+++ b/core/src/test/java/brooklyn/internal/rebind/RebindTestUtils.java
@@ -0,0 +1,79 @@
+package brooklyn.internal.rebind;
+
+import java.io.File;
+import java.util.List;
+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.rebind.dto.MementosGenerators;
+import brooklyn.internal.storage.DataGrid;
+import brooklyn.management.internal.AbstractManagementContext;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.mementos.BrooklynMemento;
+import brooklyn.util.javalang.Serializers;
+
+public class RebindTestUtils {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RebindTestUtils.class);
+
+ private static final long TIMEOUT_MS = 20*1000;
+
+ @SuppressWarnings("unchecked")
+ public static <T> T serializeAndDeserialize(T memento) throws Exception {
+ try {
+ return Serializers.reconstitute(memento);
+ } catch (Exception e) {
+ try {
+ Dumpers.logUnserializableChains(memento);
+ //Dumpers.deepDumpSerializableness(memento);
+ } catch (Throwable t) {
+ LOG.warn("Error logging unserializable chains for memento "+memento+" (propagating original exception)", t);
+ }
+ throw e;
+ }
+ }
+
+ public static void deleteMementoDir(File f) {
+ if (f.isDirectory()) {
+ for (File c : f.listFiles())
+ deleteMementoDir(c);
+ }
+ f.delete();
+ }
+
+ public static void checkMementoSerializable(Application app) throws Exception {
+ BrooklynMemento memento = MementosGenerators.newBrooklynMemento(app.getManagementContext());
+ serializeAndDeserialize(memento);
+ }
+
+ public static LocalManagementContext newPersistingManagementContext(DataGrid datagrid, ClassLoader classLoader) {
+ return new LocalManagementContext(datagrid);
+ }
+
+ public static Application rebind(LocalManagementContext newManagementContext, ClassLoader classLoader) throws Exception {
+ LOG.info("Rebinding management context "+newManagementContext);
+
+ List<Application> newApps = newManagementContext.getRebindFromDatagridManager().rebind(classLoader);
+ return newApps.get(0);
+ }
+
+ public static Application rebind(DataGrid datagrid, ClassLoader classLoader) throws Exception {
+ LOG.info("Rebinding app, using datagrid "+datagrid);
+
+ LocalManagementContext newManagementContext = newPersistingManagementContext(datagrid, classLoader);
+ List<Application> newApps = newManagementContext.getRebindFromDatagridManager().rebind(classLoader);
+ return newApps.get(0);
+ }
+
+ public static void waitForPersisted(Application origApp) throws InterruptedException, TimeoutException {
+ ((AbstractManagementContext)origApp.getManagementContext()).getRebindFromDatagridManager().waitForPendingComplete(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ public static void checkCurrentMementoSerializable(Application app) throws Exception {
+ // TODO Check everything in datagrid is serializable
+ }
+}
diff --git a/core/src/test/java/brooklyn/internal/storage/impl/BrooklynStorageImplTest.java b/core/src/test/java/brooklyn/internal/storage/impl/BrooklynStorageImplTest.java
index 5fdff7b..5a08e3b 100644
--- a/core/src/test/java/brooklyn/internal/storage/impl/BrooklynStorageImplTest.java
+++ b/core/src/test/java/brooklyn/internal/storage/impl/BrooklynStorageImplTest.java
@@ -155,6 +155,7 @@
@Test
public void testMapKeySetIterator() throws Exception {
Map<Object,Object> map = storage.getMap("mykey");
+
map.put("k1", "v1");
assertEquals(iteratorToList(map.keySet().iterator()), ImmutableList.of("k1"));
@@ -163,6 +164,12 @@
assertTrue(iter1.hasNext());
map.remove("k1");
assertEquals(iteratorToList(iter1), ImmutableList.of("k1"));
+ }
+
+ // TODO ConcurrentMapAcceptingNullVals does not support iter.remove() on keySet/values/entries
+ @Test(enabled=false)
+ public void testMapKeySetIteratorMutatingThroughIterator() throws Exception {
+ Map<Object,Object> map = storage.getMap("mykey");
// iter.remove removes value
map.clear();
diff --git a/core/src/test/java/brooklyn/location/basic/LocalhostResolverTest.java b/core/src/test/java/brooklyn/location/basic/LocalhostResolverTest.java
index 1133ae1..3943d85 100644
--- a/core/src/test/java/brooklyn/location/basic/LocalhostResolverTest.java
+++ b/core/src/test/java/brooklyn/location/basic/LocalhostResolverTest.java
@@ -37,13 +37,13 @@
@Test
public void testResolvesName() throws Exception {
- Location location = resolve("localhost");
- assertTrue(location instanceof LocalhostMachineProvisioningLocation);
- assertEquals(location.getDisplayName(), "localhost");
-
- Location location2 = resolve("localhost:()");
- assertTrue(location2 instanceof LocalhostMachineProvisioningLocation);
- assertEquals(location2.getDisplayName(), "localhost");
+// Location location = resolve("localhost");
+// assertTrue(location instanceof LocalhostMachineProvisioningLocation);
+// assertEquals(location.getDisplayName(), "localhost");
+//
+// Location location2 = resolve("localhost:()");
+// assertTrue(location2 instanceof LocalhostMachineProvisioningLocation);
+// assertEquals(location2.getDisplayName(), "localhost");
Location location3 = resolve("localhost:(name=myname)");
assertTrue(location3 instanceof LocalhostMachineProvisioningLocation);
diff --git a/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java b/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java
index 8e4faa7..f4a8fb2 100644
--- a/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java
+++ b/core/src/test/java/brooklyn/test/entity/TestApplicationImpl.java
@@ -45,7 +45,7 @@
@Override
public <T extends Entity> T createAndManageChild(EntitySpec<T> spec) {
if (!getManagementSupport().isDeployed()) throw new IllegalStateException("Entity "+this+" not managed");
- T child = createChild(spec);
+ T child = addChild(spec);
getEntityManager().manage(child);
return child;
}
diff --git a/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java b/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java
index 03b3ad8..c917a46 100644
--- a/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java
+++ b/utils/common/src/main/java/brooklyn/util/collections/MutableSet.java
@@ -35,6 +35,15 @@
return result;
}
+ public static <V> MutableSet<V> of(V v1, V v2, V v3, V v4) {
+ MutableSet<V> result = new MutableSet<V>();
+ result.add(v1);
+ result.add(v2);
+ result.add(v3);
+ result.add(v4);
+ return result;
+ }
+
public static <V> MutableSet<V> copyOf(Iterable<? extends V> orig) {
return new MutableSet<V>(orig);
}
diff --git a/utils/common/src/main/java/brooklyn/util/collections/SetFromLiveMap.java b/utils/common/src/main/java/brooklyn/util/collections/SetFromLiveMap.java
new file mode 100644
index 0000000..1e124e2
--- /dev/null
+++ b/utils/common/src/main/java/brooklyn/util/collections/SetFromLiveMap.java
@@ -0,0 +1,121 @@
+package brooklyn.util.collections;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import com.google.common.annotations.GwtIncompatible;
+import com.google.common.collect.Sets;
+
+/**
+ * As {@link Collections#newSetFromMap(Map)} and guava's {@link Sets#newSetFromMap(Map)}, but accepts
+ * a non-empty map. Also supports others modifying the backing map simultaneously, if the backing map
+ * is a ConcurrentMap.
+ */
+public class SetFromLiveMap<E> extends AbstractSet<E> implements Set<E>, Serializable {
+
+ public static <E> Set<E> create(Map<E, Boolean> map) {
+ return new SetFromLiveMap<E>(map);
+ }
+
+ private final Map<E, Boolean> m; // The backing map
+ private transient Set<E> s; // Its keySet
+
+ SetFromLiveMap(Map<E, Boolean> map) {
+ m = map;
+ s = map.keySet();
+ }
+
+ @Override
+ public void clear() {
+ m.clear();
+ }
+
+ @Override
+ public int size() {
+ return m.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return m.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return m.containsKey(o);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return m.remove(o) != null;
+ }
+
+ @Override
+ public boolean add(E e) {
+ return m.put(e, Boolean.TRUE) == null;
+ }
+
+ @Override
+ public Iterator<E> iterator() {
+ return s.iterator();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return s.toArray();
+ }
+
+ @Override
+ public <T> T[] toArray(T[] a) {
+ return s.toArray(a);
+ }
+
+ @Override
+ public String toString() {
+ return s.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return s.hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ return this == object || this.s.equals(object);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return s.containsAll(c);
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ return s.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ return s.retainAll(c);
+ }
+
+ // addAll is the only inherited implementation
+ @GwtIncompatible("not needed in emulated source")
+ private static final long serialVersionUID = 0;
+
+ @GwtIncompatible("java.io.ObjectInputStream")
+ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+ stream.defaultReadObject();
+ s = m.keySet();
+ }
+}