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.