brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rich...@apache.org
Subject [1/3] git commit: adds an xml memento serializer converter to intercept and restore the management context, as that is inserted in various places e.g. for DSL; also several related fixes and tidies, moving anonymous inner classes to be named or statics,
Date Tue, 10 Jun 2014 02:35:16 GMT
Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 4a480b5d9 -> c5dd1c1fa


adds an xml memento serializer converter to intercept and restore the management context, as that is inserted in various places e.g. for DSL;
also several related fixes and tidies, moving anonymous inner classes to be named or statics, to make the memento xml a bit nicer.
updates xstream to 1.4.7. this is not needed but it seems a bit faster and friendlier (though i can't put my finger on either...).


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/e0591f07
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/e0591f07
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/e0591f07

Branch: refs/heads/master
Commit: e0591f07fd6c2ef3774f05f1763dbb020636c8f6
Parents: 4a480b5
Author: Alex Heneveld <alex.heneveld@cloudsoftcorp.com>
Authored: Fri Jun 6 01:10:01 2014 +0100
Committer: Alex Heneveld <alex.heneveld@cloudsoftcorp.com>
Committed: Mon Jun 9 19:25:38 2014 -0700

----------------------------------------------------------------------
 .../mementos/BrooklynMementoPersister.java      |   2 +
 .../java/brooklyn/entity/basic/Entities.java    |  26 ++++
 .../rebind/RebindContextLookupContext.java      |  19 +++
 .../entity/rebind/RebindManagerImpl.java        |   2 +-
 .../BrooklynMementoPersisterInMemory.java       |   5 +
 .../rebind/persister/XmlMementoSerializer.java  |  40 +++++-
 .../brooklyn/util/xstream/MapConverter.java     |  30 ++++-
 .../java/brooklyn/entity/rebind/Dumpers.java    |   7 +-
 .../BrooklynMementoPersisterTestFixture.java    |   2 +-
 .../persister/XmlMementoSerializerTest.java     |  18 ++-
 pom.xml                                         |   2 +-
 .../BrooklynComponentTemplateResolver.java      |  94 +++++++-------
 .../spi/dsl/BrooklynDslDeferredSupplier.java    |   5 +-
 .../spi/dsl/methods/BrooklynDslCommon.java      |  32 +++--
 .../brooklyn/spi/dsl/methods/DslComponent.java  | 123 +++++++++++--------
 ...aWebAppWithDslYamlRebindIntegrationTest.java |  10 +-
 .../main/java/brooklyn/util/time/Duration.java  |   5 +-
 17 files changed, 283 insertions(+), 139 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
----------------------------------------------------------------------
diff --git a/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java b/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
index 1b49eda..94630ed 100644
--- a/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
+++ b/api/src/main/java/brooklyn/mementos/BrooklynMementoPersister.java
@@ -9,6 +9,7 @@ import brooklyn.entity.Entity;
 import brooklyn.entity.rebind.RebindExceptionHandler;
 import brooklyn.entity.rebind.RebindManager;
 import brooklyn.location.Location;
+import brooklyn.management.ManagementContext;
 import brooklyn.policy.Enricher;
 import brooklyn.policy.Policy;
 import brooklyn.util.time.Duration;
@@ -22,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting;
 public interface BrooklynMementoPersister {
 
     public static interface LookupContext {
+        ManagementContext lookupManagementContext();
         Entity lookupEntity(String id);
         Location lookupLocation(String id);
         Policy lookupPolicy(String id);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/core/src/main/java/brooklyn/entity/basic/Entities.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/basic/Entities.java b/core/src/main/java/brooklyn/entity/basic/Entities.java
index 19ec412..ff523c4 100644
--- a/core/src/main/java/brooklyn/entity/basic/Entities.java
+++ b/core/src/main/java/brooklyn/entity/basic/Entities.java
@@ -68,7 +68,9 @@ import brooklyn.util.task.system.SystemTasks;
 import brooklyn.util.time.Duration;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -811,4 +813,28 @@ public class Entities {
         return t;
     }
     
+    /** returns the entity, its children, and all its children, and so on;
+     * @param root entity whose descendants should be iterated
+     */
+    public static Iterable<Entity> descendants(Entity root) {
+        Iterable<Entity> descs = Iterables.concat(Iterables.transform(root.getChildren(), new Function<Entity,Iterable<Entity>>() {
+            @Override
+            public Iterable<Entity> apply(Entity input) {
+                return descendants(input);
+            }
+        }));
+        return Iterables.concat(descs, Collections.singleton(root));
+    }
+
+    /** return all descendants of given entity matching the given predicate.
+     * see {@link EntityPredicates} for useful second arguments! */
+    public static Iterable<Entity> descendants(Entity root, Predicate<Entity> matching) {
+        return Iterables.filter(descendants(root), matching);
+    }
+
+    /** return all descendants of given entity of the given type */
+    public static <T extends Entity> Iterable<T> descendants(Entity root, Class<T> ofType) {
+        return Iterables.filter(descendants(root), ofType);
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java b/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
index 392ea06..3c105b0 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindContextLookupContext.java
@@ -1,21 +1,40 @@
 package brooklyn.entity.rebind;
 
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import brooklyn.entity.Entity;
 import brooklyn.location.Location;
+import brooklyn.management.ManagementContext;
 import brooklyn.mementos.BrooklynMementoPersister.LookupContext;
 import brooklyn.policy.Enricher;
 import brooklyn.policy.Policy;
 
 public class RebindContextLookupContext implements LookupContext {
     
+    private static final Logger LOG = LoggerFactory.getLogger(RebindContextLookupContext.class);
+    
+    @Nullable
+    protected final ManagementContext managementContext;
+    
     protected final RebindContext rebindContext;
     protected final RebindExceptionHandler exceptionHandler;
     
     public RebindContextLookupContext(RebindContext rebindContext, RebindExceptionHandler exceptionHandler) {
+        this(null, rebindContext, exceptionHandler);
+    }
+    public RebindContextLookupContext(ManagementContext managementContext, RebindContext rebindContext, RebindExceptionHandler exceptionHandler) {
+        this.managementContext = managementContext;
         this.rebindContext = rebindContext;
         this.exceptionHandler = exceptionHandler;
     }
     
+    @Override public ManagementContext lookupManagementContext() {
+        return managementContext;
+    }
+    
     @Override public Entity lookupEntity(String id) {
         Entity result = rebindContext.getEntity(id);
         if (result == null) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
index c10a17d..c9a0661 100644
--- a/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
+++ b/core/src/main/java/brooklyn/entity/rebind/RebindManagerImpl.java
@@ -236,7 +236,7 @@ public class RebindManagerImpl implements RebindManager {
             Map<String,Enricher> enrichers = Maps.newLinkedHashMap();
             
             final RebindContextImpl rebindContext = new RebindContextImpl(classLoader);
-            LookupContext realLookupContext = new RebindContextLookupContext(rebindContext, exceptionHandler);
+            LookupContext realLookupContext = new RebindContextLookupContext(managementContext, rebindContext, exceptionHandler);
             
             // Two-phase deserialization.
             // First we deserialize just the "manifest" to find all instances (and their types).

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterInMemory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterInMemory.java b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterInMemory.java
index 6593b33..ffefc9d 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterInMemory.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterInMemory.java
@@ -17,6 +17,7 @@ import brooklyn.entity.rebind.RebindExceptionHandler;
 import brooklyn.entity.rebind.RebindExceptionHandlerImpl;
 import brooklyn.entity.rebind.RebindManager;
 import brooklyn.location.Location;
+import brooklyn.management.ManagementContext;
 import brooklyn.mementos.BrooklynMemento;
 import brooklyn.mementos.BrooklynMementoManifest;
 import brooklyn.policy.Enricher;
@@ -90,6 +91,10 @@ public class BrooklynMementoPersisterInMemory extends AbstractBrooklynMementoPer
                 persister.checkpoint(memento);
                 final BrooklynMementoManifest manifest = persister.loadMementoManifest(exceptionHandler);
                 LookupContext dummyLookupContext = new LookupContext() {
+                    @Override
+                    public ManagementContext lookupManagementContext() {
+                        return null;
+                    }
                     @Override public Entity lookupEntity(String id) {
                         List<Class<?>> types = MutableList.<Class<?>>builder()
                                 .add(Entity.class, EntityInternal.class, EntityProxy.class)

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/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 ab33e8b..b0ac285 100644
--- a/core/src/main/java/brooklyn/entity/rebind/persister/XmlMementoSerializer.java
+++ b/core/src/main/java/brooklyn/entity/rebind/persister/XmlMementoSerializer.java
@@ -24,6 +24,7 @@ import brooklyn.entity.trait.Identifiable;
 import brooklyn.event.basic.BasicAttributeSensor;
 import brooklyn.event.basic.BasicConfigKey;
 import brooklyn.location.Location;
+import brooklyn.management.ManagementContext;
 import brooklyn.management.Task;
 import brooklyn.mementos.BrooklynMementoPersister.LookupContext;
 import brooklyn.policy.Enricher;
@@ -35,6 +36,7 @@ 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.core.ReferencingMarshallingContext;
 import com.thoughtworks.xstream.core.util.HierarchicalStreams;
 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
@@ -80,6 +82,9 @@ public class XmlMementoSerializer<T> extends XmlSerializer<T> implements Memento
         xstream.registerConverter(new PolicyConverter());
         xstream.registerConverter(new EnricherConverter());
         xstream.registerConverter(new EntityConverter());
+        
+        xstream.registerConverter(new ManagementContextConverter());
+        
         xstream.registerConverter(new TaskConverter(xstream.getMapper()));
     }
     
@@ -218,6 +223,7 @@ public class XmlMementoSerializer<T> extends XmlSerializer<T> implements Memento
         }
     }
 
+    static boolean loggedTaskWarning = false;
     public class TaskConverter implements Converter {
         private final Mapper mapper;
         
@@ -237,10 +243,18 @@ public class XmlMementoSerializer<T> extends XmlSerializer<T> implements Memento
                 } catch (InterruptedException e) {
                     throw Exceptions.propagate(e);
                 } catch (ExecutionException e) {
-                    LOG.warn("Unexpected exception getting done (and non-error) task result for "+source+"; continuing", e);
+                    LOG.warn("Unexpected exception getting done (and non-error) task result for "+source+"; continuing: "+e, e);
                 }
             } else {
                 // TODO How to log sensibly, without it logging this every second?!
+                // jun 2014, have added a "log once" which is not ideal but better than the log never behaviour
+                if (!loggedTaskWarning) {
+                    LOG.warn("Intercepting and skipping request to serialize a Task"
+                        + (context instanceof ReferencingMarshallingContext ? " at "+((ReferencingMarshallingContext)context).currentPath() : "")+
+                        " (only logging this once): "+source);
+                    loggedTaskWarning = true;
+                }
+                
                 return;
             }
         }
@@ -257,4 +271,28 @@ public class XmlMementoSerializer<T> extends XmlSerializer<T> implements Memento
             }
         }
     }
+    
+    public class ManagementContextConverter implements Converter {
+        @Override
+        public boolean canConvert(@SuppressWarnings("rawtypes") Class type) {
+            return ManagementContext.class.isAssignableFrom(type);
+        }
+        @Override
+        public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
+            // we write nothing, and always insert the current mgmt context
+            
+            // if we want to explore this further, see #1422; but in short, mgmt is a common part of DSL resolution
+//            if (!loggedMgmtContextWarning) {
+//                LOG.warn("Intercepting request to serialize management context"
+//                    + (context instanceof ReferencingMarshallingContext ? " at "+((ReferencingMarshallingContext)context).currentPath() : "")+
+//                    " (only logging this once): "+source);
+//                loggedMgmtContextWarning = true;
+//            }
+        }
+        @Override
+        public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+            return lookupContext.lookupManagementContext();
+        }
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/core/src/main/java/brooklyn/util/xstream/MapConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/brooklyn/util/xstream/MapConverter.java b/core/src/main/java/brooklyn/util/xstream/MapConverter.java
index 0c791e9..837b7ed 100644
--- a/core/src/main/java/brooklyn/util/xstream/MapConverter.java
+++ b/core/src/main/java/brooklyn/util/xstream/MapConverter.java
@@ -1,27 +1,49 @@
 package brooklyn.util.xstream;
 
+import java.util.ConcurrentModificationException;
 import java.util.Iterator;
 import java.util.Map;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
 import com.thoughtworks.xstream.converters.MarshallingContext;
 import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.core.ReferencingMarshallingContext;
 import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
 import com.thoughtworks.xstream.mapper.Mapper;
 
-/** equivalent to super, but cleaner methods */
+/** equivalent to super, but cleaner methods, overridable, logging, and some retries */
 public class MapConverter extends com.thoughtworks.xstream.converters.collections.MapConverter {
 
+    private static final Logger log = LoggerFactory.getLogger(MapConverter.class);
+    
     public MapConverter(Mapper mapper) {
         super(mapper);
     }
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
         Map map = (Map) source;
-        for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
-            Map.Entry entry = (Map.Entry) iterator.next();
-            marshalEntry(writer, context, entry);
+        try {
+            for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
+                Map.Entry entry = (Map.Entry) iterator.next();
+                marshalEntry(writer, context, entry);
+            }
+        } catch (ConcurrentModificationException e) {
+            log.warn("Map "
+                // seems there is no non-deprecated way to get the path...
+                + (context instanceof ReferencingMarshallingContext ? "at "+((ReferencingMarshallingContext)context).currentPath() : "")
+                + "["+source+"] modified while serializing; trying alternate technique");
+            ImmutableList entries = ImmutableList.copyOf(map.entrySet());
+            // FIXME i think this will probably cause bogus output as it will have written some non-terminated things ... but we can try!
+            for (Iterator iterator = entries.iterator(); iterator.hasNext();) {
+                Map.Entry entry = (Map.Entry) iterator.next();
+                marshalEntry(writer, context, entry);                
+            }
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/core/src/test/java/brooklyn/entity/rebind/Dumpers.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/Dumpers.java b/core/src/test/java/brooklyn/entity/rebind/Dumpers.java
index 754425b..5ce9d1f 100644
--- a/core/src/test/java/brooklyn/entity/rebind/Dumpers.java
+++ b/core/src/test/java/brooklyn/entity/rebind/Dumpers.java
@@ -84,9 +84,14 @@ public class Dumpers {
                 try {
                     Serializers.reconstitute(o, replacer);
                     return true;
-                } catch (Exception e) {
+                } catch (Throwable e) {
+                    Exceptions.propagateIfFatal(e);
+                    
                     // not serializable in some way: report
                     ImmutableList<Object> refChainList = ImmutableList.copyOf(refChain);
+                    // for debugging it can be useful to turn this on
+//                    LOG.warn("Unreconstitutable object detected ("+o+"): "+e);
+                    
                     
                     // First strip out any less specific paths
                     for (Iterator<List<Object>> iter = unserializablePaths.keySet().iterator(); iter.hasNext();) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterTestFixture.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterTestFixture.java b/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterTestFixture.java
index b757b17..13793a8 100644
--- a/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterTestFixture.java
+++ b/core/src/test/java/brooklyn/entity/rebind/persister/BrooklynMementoPersisterTestFixture.java
@@ -86,7 +86,7 @@ public abstract class BrooklynMementoPersisterTestFixture {
         
         RecordingRebindExceptionHandler failFast = new RecordingRebindExceptionHandler(RebindFailureMode.FAIL_FAST, RebindFailureMode.FAIL_FAST);
         RebindContextImpl rebindContext = new RebindContextImpl(classLoader);
-        RebindContextLookupContext lookupContext = new RebindContextLookupContext(rebindContext, failFast);
+        RebindContextLookupContext lookupContext = new RebindContextLookupContext(localManagementContext, rebindContext, failFast);
         // here we force these two to be reegistered in order to resolve the enricher and policy
         // (normally rebind will do that after loading the manifests, but in this test we are just looking at persistence/manifest)
         rebindContext.registerEntity(app.getId(), app);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/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 c995ddf..22ccec4 100644
--- a/core/src/test/java/brooklyn/entity/rebind/persister/XmlMementoSerializerTest.java
+++ b/core/src/test/java/brooklyn/entity/rebind/persister/XmlMementoSerializerTest.java
@@ -109,7 +109,7 @@ public class XmlMementoSerializerTest {
         final TestApplication app = ApplicationBuilder.newManagedApp(TestApplication.class);
         ManagementContext managementContext = app.getManagementContext();
         try {
-            serializer.setLookupContext(new LookupContextImpl(ImmutableList.of(app), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of()));
+            serializer.setLookupContext(new LookupContextImpl(managementContext, ImmutableList.of(app), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of()));
             assertSerializeAndDeserialize(app);
         } finally {
             Entities.destroyAll(managementContext);
@@ -123,7 +123,7 @@ public class XmlMementoSerializerTest {
         try {
             @SuppressWarnings("deprecation")
             final Location loc = managementContext.getLocationManager().createLocation(LocationSpec.create(brooklyn.location.basic.SimulatedLocation.class));
-            serializer.setLookupContext(new LookupContextImpl(ImmutableList.<Entity>of(), ImmutableList.of(loc), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of()));
+            serializer.setLookupContext(new LookupContextImpl(managementContext, ImmutableList.<Entity>of(), ImmutableList.of(loc), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of()));
             assertSerializeAndDeserialize(loc);
         } finally {
             Entities.destroyAll(managementContext);
@@ -136,7 +136,7 @@ public class XmlMementoSerializerTest {
         ReffingEntity reffer = new ReffingEntity(app);
         ManagementContext managementContext = app.getManagementContext();
         try {
-            serializer.setLookupContext(new LookupContextImpl(ImmutableList.of(app), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of()));
+            serializer.setLookupContext(new LookupContextImpl(managementContext, ImmutableList.of(app), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of()));
             ReffingEntity reffer2 = assertSerializeAndDeserialize(reffer);
             assertEquals(reffer2.entity, app);
         } finally {
@@ -150,7 +150,7 @@ public class XmlMementoSerializerTest {
         ReffingEntity reffer = new ReffingEntity((Object)app);
         ManagementContext managementContext = app.getManagementContext();
         try {
-            serializer.setLookupContext(new LookupContextImpl(ImmutableList.of(app), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of()));
+            serializer.setLookupContext(new LookupContextImpl(managementContext, ImmutableList.of(app), ImmutableList.<Location>of(), ImmutableList.<Policy>of(), ImmutableList.<Enricher>of()));
             ReffingEntity reffer2 = assertSerializeAndDeserialize(reffer);
             assertEquals(reffer2.obj, app);
         } finally {
@@ -187,13 +187,15 @@ public class XmlMementoSerializerTest {
     }
 
     static class LookupContextImpl implements LookupContext {
+        private final ManagementContext mgmt;
         private final Map<String, Entity> entities;
         private final Map<String, Location> locations;
         private final Map<String, Policy> policies;
         private final Map<String, Enricher> enrichers;
 
-        LookupContextImpl(Iterable<? extends Entity> entities, Iterable<? extends Location> locations,
+        LookupContextImpl(ManagementContext mgmt, Iterable<? extends Entity> entities, Iterable<? extends Location> locations,
                 Iterable<? extends Policy> policies, Iterable<? extends Enricher> enrichers) {
+            this.mgmt = mgmt;
             this.entities = Maps.newLinkedHashMap();
             this.locations = Maps.newLinkedHashMap();
             this.policies = Maps.newLinkedHashMap();
@@ -203,13 +205,17 @@ public class XmlMementoSerializerTest {
             for (Policy policy : policies) this.policies.put(policy.getId(), policy);
             for (Enricher enricher : enrichers) this.enrichers.put(enricher.getId(), enricher);
         }
-        LookupContextImpl(Map<String,? extends Entity> entities, Map<String,? extends Location> locations,
+        LookupContextImpl(ManagementContext mgmt, Map<String,? extends Entity> entities, Map<String,? extends Location> locations,
                 Map<String,? extends Policy> policies, Map<String,? extends Enricher> enrichers) {
+            this.mgmt = mgmt;
             this.entities = ImmutableMap.copyOf(entities);
             this.locations = ImmutableMap.copyOf(locations);
             this.policies = ImmutableMap.copyOf(policies);
             this.enrichers = ImmutableMap.copyOf(enrichers);
         }
+        @Override public ManagementContext lookupManagementContext() {
+            return mgmt;
+        }
         @Override public Entity lookupEntity(String id) {
             if (entities.containsKey(id)) {
                 return entities.get(id);

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 48ba728..da9c9b1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -86,7 +86,7 @@
         <mockito.version>1.9.5</mockito.version>
         <whirr.version>0.8.1</whirr.version>
         <slf4j.version>1.6.6</slf4j.version>  <!-- used for java.util.logging jul-to-slf4j interception -->
-        <xstream.version>1.4.3</xstream.version>
+        <xstream.version>1.4.7</xstream.version>
         <jackson.version>1.9.13</jackson.version>  <!-- codehaus jackson, used by brooklyn rest server -->
         <fasterxml.jackson.version>2.2.0</fasterxml.jackson.version>  <!-- fasterxml (newer) jackson, used by camp -->
         <jersey.version>1.12</jersey.version> <!-- NB: 1.17 requires changes to how we hanlde multipart in CatalogApi -->

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
index f5dd05c..c0667ca 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java
@@ -255,12 +255,12 @@ public class BrooklynComponentTemplateResolver {
         Set<String> keyNamesUsed = new LinkedHashSet<String>();
         for (FlagConfigKeyAndValueRecord r: records) {
             if (r.getFlagMaybeValue().isPresent()) {
-                Object transformed = transformSpecialFlags(r.getFlagMaybeValue().get(), mgmt);
+                Object transformed = new SpecialFlagsTransformer(mgmt).transformSpecialFlags(r.getFlagMaybeValue().get(), mgmt);
                 spec.configure(r.getFlagName(), transformed);
                 keyNamesUsed.add(r.getFlagName());
             }
             if (r.getConfigKeyMaybeValue().isPresent()) {
-                Object transformed = transformSpecialFlags(r.getConfigKeyMaybeValue().get(), mgmt);
+                Object transformed = new SpecialFlagsTransformer(mgmt).transformSpecialFlags(r.getConfigKeyMaybeValue().get(), mgmt);
                 spec.configure((ConfigKey<Object>)r.getConfigKey(), transformed);
                 keyNamesUsed.add(r.getConfigKey().getName());
             }
@@ -273,62 +273,54 @@ public class BrooklynComponentTemplateResolver {
             // we don't let a flag with the same name as a config key override the config key
             // (that's why we check whether it is used)
             if (!keyNamesUsed.contains(key)) {
-                Object transformed = transformSpecialFlags(bag.getStringKey(key), mgmt);
+                Object transformed = new SpecialFlagsTransformer(mgmt).transformSpecialFlags(bag.getStringKey(key), mgmt);
                 spec.configure(ConfigKeys.newConfigKey(Object.class, key.toString()), transformed);
             }
         }
     }
 
-    /**
-     * Makes additional transformations to the given flag with the extra knowledge of the flag's management context.
-     * @return The modified flag, or the flag unchanged.
-     */
-    protected Object transformSpecialFlags(Object flag, ManagementContext mgmt) {
-        if (flag instanceof EntitySpecConfiguration) {
-            EntitySpecConfiguration specConfig = (EntitySpecConfiguration) flag;
-            // TODO: This should called from BrooklynAssemblyTemplateInstantiator.configureEntityConfig
-            // And have transformSpecialFlags(Object flag, ManagementContext mgmt) drill into the Object flag if it's a map or iterable?
-            @SuppressWarnings("unchecked")
-            Map<String, Object> resolvedConfig = (Map<String, Object>)transformSpecialFlags(specConfig.getSpecConfiguration(), mgmt);
-            specConfig.setSpecConfiguration(resolvedConfig);
-            return Factory.newInstance(mgmt, specConfig.getSpecConfiguration()).resolveSpec();
+    protected static class SpecialFlagsTransformer implements Function<Object, Object> {
+        final ManagementContext mgmt;
+        public SpecialFlagsTransformer(ManagementContext mgmt) {
+            this.mgmt = mgmt;
         }
-        return flag;
-    }
-    
-    protected Map<?, ?> transformSpecialFlags(Map<?, ?> flag, final ManagementContext mgmt) {
-        // TODO: Re-usable function
-        return Maps.transformValues(flag, new Function<Object, Object>() {
-            public Object apply(Object input) {
-                if (input instanceof Map)
-                    return transformSpecialFlags((Map<?, ?>)input, mgmt);
-                else if (input instanceof Set<?>)
-                    return MutableSet.of(transformSpecialFlags((Iterable<?>)input, mgmt));
-                else if (input instanceof List<?>)
-                    return MutableList.copyOf(transformSpecialFlags((Iterable<?>)input, mgmt));
-                else if (input instanceof Iterable<?>)
-                    return transformSpecialFlags((Iterable<?>)input, mgmt);
-                else 
-                    return transformSpecialFlags((Object)input, mgmt);
-            }
-        });
-    }
-    
-    protected Iterable<?> transformSpecialFlags(Iterable<?> flag, final ManagementContext mgmt) {
-        return Iterables.transform(flag, new Function<Object, Object>() {
-            public Object apply(Object input) {
-                if (input instanceof Map<?, ?>)
-                    return transformSpecialFlags((Map<?, ?>)input, mgmt);
-                else if (input instanceof Set<?>)
-                    return MutableSet.of(transformSpecialFlags((Iterable<?>)input, mgmt));
-                else if (input instanceof List<?>)
-                    return MutableList.copyOf(transformSpecialFlags((Iterable<?>)input, mgmt));
-                else if (input instanceof Iterable<?>)
-                    return transformSpecialFlags((Iterable<?>)input, mgmt);
-                else 
-                    return transformSpecialFlags((Object)input, mgmt);
+        public Object apply(Object input) {
+            if (input instanceof Map)
+                return transformSpecialFlags((Map<?, ?>)input, mgmt);
+            else if (input instanceof Set<?>)
+                return MutableSet.of(transformSpecialFlags((Iterable<?>)input, mgmt));
+            else if (input instanceof List<?>)
+                return MutableList.copyOf(transformSpecialFlags((Iterable<?>)input, mgmt));
+            else if (input instanceof Iterable<?>)
+                return transformSpecialFlags((Iterable<?>)input, mgmt);
+            else 
+                return transformSpecialFlags((Object)input, mgmt);
+        }
+        
+        protected Map<?, ?> transformSpecialFlags(Map<?, ?> flag, final ManagementContext mgmt) {
+            return Maps.transformValues(flag, this);
+        }
+        
+        protected Iterable<?> transformSpecialFlags(Iterable<?> flag, final ManagementContext mgmt) {
+            return Iterables.transform(flag, this);
+        }
+        
+        /**
+         * Makes additional transformations to the given flag with the extra knowledge of the flag's management context.
+         * @return The modified flag, or the flag unchanged.
+         */
+        protected Object transformSpecialFlags(Object flag, ManagementContext mgmt) {
+            if (flag instanceof EntitySpecConfiguration) {
+                EntitySpecConfiguration specConfig = (EntitySpecConfiguration) flag;
+                // TODO: This should called from BrooklynAssemblyTemplateInstantiator.configureEntityConfig
+                // And have transformSpecialFlags(Object flag, ManagementContext mgmt) drill into the Object flag if it's a map or iterable?
+                @SuppressWarnings("unchecked")
+                Map<String, Object> resolvedConfig = (Map<String, Object>)transformSpecialFlags(specConfig.getSpecConfiguration(), mgmt);
+                specConfig.setSpecConfiguration(resolvedConfig);
+                return Factory.newInstance(mgmt, specConfig.getSpecConfiguration()).resolveSpec();
             }
-        });
+            return flag;
+        }
     }
 
     @SuppressWarnings("unchecked")

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java
index 9fe4458..c446d74 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/BrooklynDslDeferredSupplier.java
@@ -47,7 +47,8 @@ public abstract class BrooklynDslDeferredSupplier<T> implements DeferredSupplier
     // TODO json of this object should *be* this, not wrapped this ($brooklyn:literal is a bit of a hack, though it might work!)
     @JsonInclude
     @JsonProperty(value="$brooklyn:literal")
-    public final Object dsl;
+    // currently marked transient because it's only needed for logging
+    private transient Object dsl = "(gone)";
     
     public BrooklynDslDeferredSupplier() {
         PlanInterpretationNode sourceNode = BrooklynDslInterpreter.currentNode();
@@ -55,7 +56,7 @@ public abstract class BrooklynDslDeferredSupplier<T> implements DeferredSupplier
     }
     
     /** returns the current entity; for use in implementations of {@link #get()} */
-    protected final EntityInternal entity() {
+    protected final static EntityInternal entity() {
         // rely on implicit ThreadLocal for now
         return (EntityInternal) EffectorTasks.findEntity();
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
index 15df932..fe3762d 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
@@ -37,20 +37,28 @@ public class BrooklynDslCommon {
             // if all args are resolved, apply the format string now
             return String.format(pattern, args);
         }
-        return new BrooklynDslDeferredSupplier<String>() {
-            private static final long serialVersionUID = -4849297712650560863L;
-
-            @Override
-            public Task<String> newTask() {
-                return DependentConfiguration.formatString(pattern, args);
-            }
-            @Override
-            public String toString() {
-                return "$brooklyn:formatString("+pattern+")";
-            }
-        };
+        return new FormatString(pattern, args);
     }
 
+    protected static final class FormatString extends BrooklynDslDeferredSupplier<String> {
+        private static final long serialVersionUID = -4849297712650560863L;
+        private String pattern;
+        private Object[] args;
+
+        public FormatString(String pattern, Object ...args) {
+            this.pattern = pattern;
+            this.args = args;
+        }
+        @Override
+        public Task<String> newTask() {
+            return DependentConfiguration.formatString(pattern, args);
+        }
+        @Override
+        public String toString() {
+            return "$brooklyn:formatString("+pattern+")";
+        }
+    }
+    
     // TODO: Would be nice to have sensor(String sensorName), which would take the sensor from the entity in question, 
     //       but that would require refactoring of Brooklyn DSL
     // TODO: Should use catalog's classloader, rather than Class.forName; how to get that? Should we return a future?!

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
index 69e80b4..c77a416 100644
--- a/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
+++ b/usage/camp/src/main/java/io/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
@@ -44,45 +44,56 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> {
 
     @Override
     public Task<Entity> newTask() {
-        return TaskBuilder.<Entity>builder().name("component("+componentId+")").body(new Callable<Entity>() {
-            @Override
-            public Entity call() throws Exception {
-                Iterable<Entity> entitiesToSearch = null;
-                switch (scope) {
-                    case THIS:
-                        return entity();
-                    case PARENT:
-                        return entity().getParent();
-                    case GLOBAL:
-                        entitiesToSearch = ((EntityManagerInternal)entity().getManagementContext().getEntityManager())
-                            .getAllEntitiesInApplication( entity().getApplication() );
-                        break;
-                    case DESCENDANT:
-                        entitiesToSearch = Sets.newLinkedHashSet();
-                        addDescendants(entity(), (Set<Entity>)entitiesToSearch);
-                        break;
-                    case SIBLING:
-                        entitiesToSearch = entity().getParent().getChildren();
-                        break;
-                    case CHILD:
-                        entitiesToSearch = entity().getChildren();
-                        break;
-                    default:
-                        throw new IllegalStateException("Unexpected scope "+scope);
-                }
-                
-                Optional<Entity> result = Iterables.tryFind(entitiesToSearch, EntityPredicates.configEqualTo(BrooklynCampConstants.PLAN_ID, componentId));
-                
-                if (result.isPresent())
-                    return result.get();
-                
-                // TODO may want to block and repeat on new entities joining?
-                throw new NoSuchElementException("No entity matching id " + componentId);
+        return TaskBuilder.<Entity>builder().name("component("+componentId+")").body(
+            new EntityInScopeFinder(scope, componentId)).build();
+    }
+    
+    protected static class EntityInScopeFinder implements Callable<Entity> {
+        protected Scope scope;
+        protected String componentId;
+
+        public EntityInScopeFinder(Scope scope, String componentId) {
+            this.scope = scope;
+            this.componentId = componentId;
+        }
+
+        @Override
+        public Entity call() throws Exception {
+            Iterable<Entity> entitiesToSearch = null;
+            switch (scope) {
+                case THIS:
+                    return entity();
+                case PARENT:
+                    return entity().getParent();
+                case GLOBAL:
+                    entitiesToSearch = ((EntityManagerInternal)entity().getManagementContext().getEntityManager())
+                        .getAllEntitiesInApplication( entity().getApplication() );
+                    break;
+                case DESCENDANT:
+                    entitiesToSearch = Sets.newLinkedHashSet();
+                    addDescendants(entity(), (Set<Entity>)entitiesToSearch);
+                    break;
+                case SIBLING:
+                    entitiesToSearch = entity().getParent().getChildren();
+                    break;
+                case CHILD:
+                    entitiesToSearch = entity().getChildren();
+                    break;
+                default:
+                    throw new IllegalStateException("Unexpected scope "+scope);
             }
-        }).build();
+            
+            Optional<Entity> result = Iterables.tryFind(entitiesToSearch, EntityPredicates.configEqualTo(BrooklynCampConstants.PLAN_ID, componentId));
+            
+            if (result.isPresent())
+                return result.get();
+            
+            // TODO may want to block and repeat on new entities joining?
+            throw new NoSuchElementException("No entity matching id " + componentId);
+        }        
     }
     
-    private void addDescendants(Entity entity, Set<Entity> entities) {
+    private static void addDescendants(Entity entity, Set<Entity> entities) {
         entities.add(entity);
         for (Entity child : entity.getChildren()) {
             addDescendants(child, entities);
@@ -90,23 +101,29 @@ public class DslComponent extends BrooklynDslDeferredSupplier<Entity> {
     }
     
 	public BrooklynDslDeferredSupplier<?> attributeWhenReady(final String sensorName) {
-		return new BrooklynDslDeferredSupplier<Object>() {
-            private static final long serialVersionUID = 1740899524088902383L;
-            @SuppressWarnings("unchecked")
-            @Override
-			public Task<Object> newTask() {
-				Entity targetEntity = DslComponent.this.get();
-				Sensor<?> targetSensor = targetEntity.getEntityType().getSensor(sensorName);
-				if (!(targetSensor instanceof AttributeSensor<?>)) {
-					targetSensor = Sensors.newSensor(Object.class, sensorName);
-				}
-				return (Task<Object>) DependentConfiguration.attributeWhenReady(targetEntity, (AttributeSensor<?>)targetSensor);
-			}
-			@Override
-			public String toString() {
-			    return DslComponent.this.toString()+"."+"attributeWhenReady("+sensorName+")";
-			}
-		};
+		return new AttributeWhenReady(sensorName);
+	}
+	// class simply makes the memento XML files nicer
+	protected class AttributeWhenReady extends BrooklynDslDeferredSupplier<Object> {
+        private static final long serialVersionUID = 1740899524088902383L;
+        private String sensorName;
+        public AttributeWhenReady(String sensorName) {
+            this.sensorName = sensorName;
+        }
+        @SuppressWarnings("unchecked")
+        @Override
+        public Task<Object> newTask() {
+            Entity targetEntity = DslComponent.this.get();
+            Sensor<?> targetSensor = targetEntity.getEntityType().getSensor(sensorName);
+            if (!(targetSensor instanceof AttributeSensor<?>)) {
+                targetSensor = Sensors.newSensor(Object.class, sensorName);
+            }
+            return (Task<Object>) DependentConfiguration.attributeWhenReady(targetEntity, (AttributeSensor<?>)targetSensor);
+        }
+        @Override
+        public String toString() {
+            return DslComponent.this.toString()+"."+"attributeWhenReady("+sensorName+")";
+        }
 	}
 	
 	public BrooklynDslDeferredSupplier<Object> config(final String keyName) {

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/JavaWebAppWithDslYamlRebindIntegrationTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/JavaWebAppWithDslYamlRebindIntegrationTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/JavaWebAppWithDslYamlRebindIntegrationTest.java
index cbd32bf..8c2fc38 100644
--- a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/JavaWebAppWithDslYamlRebindIntegrationTest.java
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/JavaWebAppWithDslYamlRebindIntegrationTest.java
@@ -39,7 +39,8 @@ public class JavaWebAppWithDslYamlRebindIntegrationTest extends AbstractYamlTest
     protected LocalManagementContext newTestManagementContext() {
         if (mementoDir!=null) throw new IllegalStateException("already created mgmt context");
         mementoDir = Files.createTempDir();
-        LocalManagementContext mgmt = RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader, 1);
+        LocalManagementContext mgmt =
+            RebindTestUtils.newPersistingManagementContext(mementoDir, classLoader, 1);
         mgmtContexts.add(mgmt);
         return mgmt;
     }
@@ -84,8 +85,8 @@ public class JavaWebAppWithDslYamlRebindIntegrationTest extends AbstractYamlTest
         Assert.assertEquals(app2.getChildren().size(), 2);
     }
 
-    // failing test based on https://github.com/brooklyncentral/brooklyn/issues/1422
-    @Test(groups="Integration", enabled=false)
+    // test for https://github.com/brooklyncentral/brooklyn/issues/1422
+    @Test(groups="Integration")
     public void testJavaWebWithMemberSpecRebind() throws Exception {
         Reader input = Streams.reader(new ResourceUtils(this).getResourceFromUrl("test-java-web-app-spec-and-db-with-function.yaml"));
         AssemblyTemplate at = platform.pdp().registerDeploymentPlan(input);
@@ -96,10 +97,9 @@ public class JavaWebAppWithDslYamlRebindIntegrationTest extends AbstractYamlTest
         Set<Task<?>> tasks = BrooklynTaskTags.getTasksInEntityContext(mgmt().getExecutionManager(), app);
         for (Task<?> t: tasks) t.blockUntilEnded();
         Entities.dumpInfo(app);
-
+        
         Application app2 = rebind(app);
         Assert.assertEquals(app2.getChildren().size(), 2);
     }
-    
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/e0591f07/utils/common/src/main/java/brooklyn/util/time/Duration.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/time/Duration.java b/utils/common/src/main/java/brooklyn/util/time/Duration.java
index 34458b8..2b854fa 100644
--- a/utils/common/src/main/java/brooklyn/util/time/Duration.java
+++ b/utils/common/src/main/java/brooklyn/util/time/Duration.java
@@ -2,6 +2,7 @@ package brooklyn.util.time;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import java.io.Serializable;
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
@@ -14,8 +15,10 @@ import com.google.common.base.Preconditions;
 import com.google.common.base.Stopwatch;
 
 /** simple class determines a length of time */
-public class Duration implements Comparable<Duration> {
+public class Duration implements Comparable<Duration>, Serializable {
 
+    private static final long serialVersionUID = -2303909964519279617L;
+    
     public static final Duration ZERO = of(0, null);
     public static final Duration ONE_MILLISECOND = of(1, TimeUnit.MILLISECONDS);
     public static final Duration ONE_SECOND = of(1, TimeUnit.SECONDS);


Mime
View raw message