You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/02/09 16:36:34 UTC
[15/22] incubator-brooklyn git commit: special xml serialization for
EntitySpec to support CatalogItem libraries
special xml serialization for EntitySpec to support CatalogItem libraries
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/0dc533d1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/0dc533d1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/0dc533d1
Branch: refs/heads/master
Commit: 0dc533d116cc92a7d5811338cec060a7868e64a0
Parents: 6156a77
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Feb 6 17:58:41 2015 +0000
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Feb 6 22:12:14 2015 +0000
----------------------------------------------------------------------
.../basic/AbstractBrooklynObjectSpec.java | 17 ++
.../rebind/persister/XmlMementoSerializer.java | 160 ++++++++++++++++++-
.../persister/XmlMementoSerializerTest.java | 32 ++++
3 files changed, 208 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0dc533d1/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java b/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
index c0a4ccc..83db969 100644
--- a/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
+++ b/api/src/main/java/brooklyn/basic/AbstractBrooklynObjectSpec.java
@@ -121,4 +121,21 @@ public abstract class AbstractBrooklynObjectSpec<T,K extends AbstractBrooklynObj
if (Modifier.isAbstract(val.getModifiers())) throw new IllegalStateException("Implementation "+val+" is abstract, but must be a non-abstract class");
}
+ @Override
+ public boolean equals(Object obj) {
+ if (obj==null) return false;
+ if (!obj.getClass().equals(getClass())) return false;
+ AbstractBrooklynObjectSpec<?,?> other = (AbstractBrooklynObjectSpec<?,?>)obj;
+ if (!Objects.equal(getDisplayName(), other.getDisplayName())) return false;
+ if (!Objects.equal(getCatalogItemId(), other.getCatalogItemId())) return false;
+ if (!Objects.equal(getType(), other.getType())) return false;
+ if (!Objects.equal(getTags(), other.getTags())) return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getCatalogItemId(), getDisplayName(), getType(), getTags());
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0dc533d1/core/src/main/java/brooklyn/entity/rebind/persister/XmlMementoSerializer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/XmlMementoSerializer.java b/core/src/main/java/brooklyn/entity/rebind/persister/XmlMementoSerializer.java
index 5f3fb5c..78644b3 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/XmlMementoSerializer.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/XmlMementoSerializer.java
@@ -22,13 +22,18 @@ import static com.google.common.base.Preconditions.checkNotNull;
import java.io.IOException;
import java.io.Writer;
+import java.util.NoSuchElementException;
+import java.util.Stack;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import brooklyn.basic.AbstractBrooklynObjectSpec;
import brooklyn.catalog.CatalogItem;
import brooklyn.catalog.internal.CatalogBundleDto;
+import brooklyn.catalog.internal.CatalogUtils;
import brooklyn.entity.Effector;
import brooklyn.entity.Entity;
import brooklyn.entity.Feed;
@@ -49,20 +54,27 @@ import brooklyn.event.basic.BasicConfigKey;
import brooklyn.location.Location;
import brooklyn.management.ManagementContext;
import brooklyn.management.Task;
+import brooklyn.management.classloading.BrooklynClassLoadingContext;
+import brooklyn.management.classloading.BrooklynClassLoadingContextSequential;
+import brooklyn.management.classloading.ClassLoaderFromBrooklynClassLoadingContext;
+import brooklyn.management.classloading.JavaBrooklynClassLoadingContext;
import brooklyn.mementos.BrooklynMementoPersister.LookupContext;
import brooklyn.policy.Enricher;
import brooklyn.policy.Policy;
import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.text.Strings;
import brooklyn.util.xstream.XmlSerializer;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.SingleValueConverter;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.core.ReferencingMarshallingContext;
import com.thoughtworks.xstream.core.util.HierarchicalStreams;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.path.PathTrackingReader;
import com.thoughtworks.xstream.mapper.Mapper;
import com.thoughtworks.xstream.mapper.MapperWrapper;
@@ -110,9 +122,9 @@ public class XmlMementoSerializer<T> extends XmlSerializer<T> implements Memento
xstream.registerConverter(new EntityConverter());
xstream.registerConverter(new FeedConverter());
xstream.registerConverter(new CatalogItemConverter());
+ xstream.registerConverter(new SpecConverter());
xstream.registerConverter(new ManagementContextConverter());
-
xstream.registerConverter(new TaskConverter(xstream.getMapper()));
//For compatibility with existing persistence stores content.
@@ -341,5 +353,151 @@ public class XmlMementoSerializer<T> extends XmlSerializer<T> implements Memento
return lookupContext.lookupManagementContext();
}
}
+
+ /** When reading/writing specs, it checks whether there is a catalog item id set and uses it to load */
+ public class SpecConverter extends ReflectionConverter {
+ SpecConverter() {
+ super(xstream.getMapper(), xstream.getReflectionProvider());
+ }
+ @Override
+ public boolean canConvert(@SuppressWarnings("rawtypes") Class type) {
+ return AbstractBrooklynObjectSpec.class.isAssignableFrom(type);
+ }
+ @Override
+ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+ if (source == null) return;
+ AbstractBrooklynObjectSpec<?, ?> spec = (AbstractBrooklynObjectSpec<?, ?>) source;
+ String catalogItemId = spec.getCatalogItemId();
+ if (Strings.isNonBlank(catalogItemId)) {
+ // write this field first, so we can peek at it when we read
+ writer.startNode("catalogItemId");
+ writer.setValue(catalogItemId);
+ writer.endNode();
+
+ // we're going to write the catalogItemId field twice :( but that's okay.
+ // better solution would be to have mark/reset on reader so we can peek for such a field;
+ // see comment below
+ super.marshal(source, writer, context);
+ } else {
+ super.marshal(source, writer, context);
+ }
+ }
+ @Override
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ String catalogItemId = null;
+ instantiateNewInstanceSettingCache(reader, context);
+
+ if (reader instanceof PathTrackingReader) {
+ // have to assume this is first; there is no mark/reset support on these readers
+ // (if there were then it would be easier, we could just look for that child anywhere,
+ // and not need a custom writer!)
+ if ("catalogItemId".equals( ((PathTrackingReader)reader).peekNextChild() )) {
+ // cache the instance
+
+ reader.moveDown();
+ catalogItemId = reader.getValue();
+ reader.moveUp();
+ }
+ }
+ boolean customLoaderSet = false;
+ try {
+ if (Strings.isNonBlank(catalogItemId)) {
+ if (lookupContext==null) throw new NullPointerException("lookupContext required to load catalog item "+catalogItemId);
+ CatalogItem<?, ?> cat = CatalogUtils.getCatalogItemOptionalVersion(lookupContext.lookupManagementContext(), catalogItemId);
+ if (cat==null) throw new NoSuchElementException("catalog item: "+catalogItemId);
+ BrooklynClassLoadingContext clcNew = CatalogUtils.newClassLoadingContext(lookupContext.lookupManagementContext(), cat);
+ pushXstreamCustomClassLoader(clcNew);
+ customLoaderSet = true;
+ }
+
+ AbstractBrooklynObjectSpec<?, ?> result = (AbstractBrooklynObjectSpec<?, ?>) super.unmarshal(reader, context);
+ // we wrote it twice so this shouldn't be necessary; but if we fix it so we only write once, we'd need this
+ result.catalogItemId(catalogItemId);
+ return result;
+ } finally {
+ instance = null;
+ if (customLoaderSet) {
+ popXstreamCustomClassLoader();
+ }
+ }
+ }
+
+ Object instance;
+
+ @Override
+ protected Object instantiateNewInstance(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ // the super calls getAttribute which requires that we have not yet done moveDown,
+ // so we do this earlier and cache it for when we call super.unmarshal
+ if (instance==null)
+ throw new IllegalStateException("Instance should be created and cached");
+ return instance;
+ }
+ protected void instantiateNewInstanceSettingCache(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ instance = super.instantiateNewInstance(reader, context);
+ }
+ }
+
+ Stack<BrooklynClassLoadingContext> contexts = new Stack<BrooklynClassLoadingContext>();
+ Stack<ClassLoader> cls = new Stack<ClassLoader>();
+ AtomicReference<Thread> xstreamLockOwner = new AtomicReference<Thread>();
+ int lockCount;
+ /** Must be accompanied by a corresponding {@link #popXstreamCustomClassLoader()} when finished. */
+ @SuppressWarnings("deprecation")
+ protected void pushXstreamCustomClassLoader(BrooklynClassLoadingContext clcNew) {
+ acquireXstreamLock();
+ BrooklynClassLoadingContext oldClc;
+ if (!contexts.isEmpty()) {
+ oldClc = contexts.peek();
+ } else {
+ // TODO XmlMementoSerializer should take a BCLC instead of a CL
+ oldClc = JavaBrooklynClassLoadingContext.create(lookupContext.lookupManagementContext(), xstream.getClassLoader());
+ }
+ BrooklynClassLoadingContextSequential clcMerged = new BrooklynClassLoadingContextSequential(lookupContext.lookupManagementContext(),
+ oldClc, clcNew);
+ contexts.push(clcMerged);
+ cls.push(xstream.getClassLoader());
+ ClassLoader newCL = ClassLoaderFromBrooklynClassLoadingContext.of(clcMerged);
+ xstream.setClassLoader(newCL);
+ }
+
+ protected void popXstreamCustomClassLoader() {
+ synchronized (xstreamLockOwner) {
+ releaseXstreamLock();
+ xstream.setClassLoader(cls.pop());
+ contexts.pop();
+ }
+ }
+
+ protected void acquireXstreamLock() {
+ synchronized (xstreamLockOwner) {
+ while (true) {
+ if (xstreamLockOwner.compareAndSet(null, Thread.currentThread()) ||
+ Thread.currentThread().equals( xstreamLockOwner.get() )) {
+ break;
+ }
+ try {
+ xstreamLockOwner.wait(1000);
+ } catch (InterruptedException e) {
+ throw Exceptions.propagate(e);
+ }
+ }
+ lockCount++;
+ }
+ }
+
+ protected void releaseXstreamLock() {
+ synchronized (xstreamLockOwner) {
+ if (lockCount<=0) {
+ throw new IllegalStateException("xstream not locked");
+ }
+ if (--lockCount == 0) {
+ if (!xstreamLockOwner.compareAndSet(Thread.currentThread(), null)) {
+ Thread oldOwner = xstreamLockOwner.getAndSet(null);
+ throw new IllegalStateException("xstream was locked by "+oldOwner+" but unlock attempt by "+Thread.currentThread());
+ }
+ }
+ }
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/0dc533d1/core/src/test/java/brooklyn/entity/rebind/persister/XmlMementoSerializerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/persister/XmlMementoSerializerTest.java b/core/src/test/java/brooklyn/entity/rebind/persister/XmlMementoSerializerTest.java
index e0b4f4e..6083134 100644
--- a/core/src/test/java/brooklyn/entity/rebind/persister/XmlMementoSerializerTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/persister/XmlMementoSerializerTest.java
@@ -37,17 +37,22 @@ import brooklyn.basic.BrooklynObject;
import brooklyn.catalog.CatalogItem;
import brooklyn.catalog.internal.CatalogItemBuilder;
import brooklyn.catalog.internal.CatalogItemDtoAbstract;
+import brooklyn.catalog.internal.CatalogUtils;
import brooklyn.entity.Entity;
import brooklyn.entity.Feed;
import brooklyn.entity.basic.Entities;
+import brooklyn.entity.group.DynamicCluster;
import brooklyn.entity.proxying.EntitySpec;
import brooklyn.entity.rebind.BrooklynObjectType;
import brooklyn.location.Location;
import brooklyn.location.LocationSpec;
import brooklyn.management.ManagementContext;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.management.osgi.OsgiVersionMoreEntityTest;
import brooklyn.mementos.BrooklynMementoPersister.LookupContext;
import brooklyn.policy.Enricher;
import brooklyn.policy.Policy;
+import brooklyn.test.entity.LocalManagementContextForTests;
import brooklyn.test.entity.TestApplication;
import brooklyn.test.entity.TestEntity;
import brooklyn.util.collections.MutableList;
@@ -129,6 +134,12 @@ public class XmlMementoSerializerTest {
}
@Test
+ public void testClass() throws Exception {
+ Class<?> t = XmlMementoSerializer.class;
+ assertSerializeAndDeserialize(t);
+ }
+
+ @Test
public void testEntity() throws Exception {
final TestApplication app = TestApplication.Factory.newManagedInstanceForTests();
ManagementContext managementContext = app.getManagementContext();
@@ -180,6 +191,27 @@ public class XmlMementoSerializerTest {
}
@Test
+ public void testEntitySpec() throws Exception {
+ EntitySpec<?> obj = EntitySpec.create(TestEntity.class);
+ assertSerializeAndDeserialize(obj);
+ }
+
+ @Test
+ public void testEntitySpecFromOsgi() throws Exception {
+ ManagementContext mgmt = LocalManagementContextForTests.builder(true).disableOsgi(false).build();
+ CatalogItem<?, ?> ci = OsgiVersionMoreEntityTest.addMoreEntityV1(mgmt, "1.0");
+
+ EntitySpec<DynamicCluster> spec = EntitySpec.create(DynamicCluster.class)
+ .configure(DynamicCluster.INITIAL_SIZE, 1)
+ .configure(DynamicCluster.MEMBER_SPEC, CatalogUtils.createEntitySpec(mgmt, ci));
+
+ serializer.setLookupContext(new LookupContextImpl(mgmt,
+ ImmutableList.<Entity>of(), ImmutableList.<Location>of(), ImmutableList.<Policy>of(),
+ ImmutableList.<Enricher>of(), ImmutableList.<Feed>of(), ImmutableList.<CatalogItem<?,?>>of(), true));
+ assertSerializeAndDeserialize(spec);
+ }
+
+ @Test
public void testImmutableCollectionsWithDanglingEntityRef() throws Exception {
// If there's a dangling entity in an ImmutableList etc, then discard it entirely.
// If we try to insert null then it fails, breaking the deserialization of that entire file.