You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@curator.apache.org by ra...@apache.org on 2017/05/26 09:40:16 UTC

curator git commit: Squashed commit of the following:

Repository: curator
Updated Branches:
  refs/heads/CURATOR-2.0 abaabb5f6 -> ff02a7c2f


Squashed commit of the following:

commit 8cab83b4f7ecf44052eee8a30299f1adc3a50ce1
Merge: 3ca02ea7 abaabb5f
Author: randgalt <ra...@apache.org>
Date:   Fri May 26 11:39:18 2017 +0200

    Merge branch 'CURATOR-2.0' into CURATOR-394

commit 3ca02ea7f076d40333d664dd609962d0fe2e4c5f
Author: randgalt <ra...@apache.org>
Date:   Thu May 18 11:39:20 2017 +0200

    Added testForwardCompatibility

commit e7f55f89056f1447cb2ed73a0cdfd66759e11f91
Author: randgalt <ra...@apache.org>
Date:   Fri Mar 31 13:25:04 2017 -0500

    rename NewServiceInstance to make it clear it's only for testing

commit 6f3b178fdd6cc6ae914d4eead2ee74e4afbd5ec8
Author: randgalt <ra...@apache.org>
Date:   Sun Mar 26 10:25:57 2017 -0500

    updated tests

commit a193ce02c37d3ef5a6b9c4a81beffd4d8deba1d0
Author: randgalt <ra...@apache.org>
Date:   Sun Mar 26 10:23:38 2017 -0500

    Don't serialize enabled by default. This is the most compatible solution

commit 9ac224a3803c6d0ee4ed2081f36871e9d852f75c
Author: randgalt <ra...@apache.org>
Date:   Fri Mar 24 20:04:30 2017 -0500

    for safety check all the fields

commit ee18fd55f71e320050671541c7ed9435f3895a81
Author: randgalt <ra...@apache.org>
Date:   Fri Mar 24 19:28:11 2017 -0500

    bad commit

commit b6b9e1cc48b4713c1ded50c9d4c60a5071908e61
Author: randgalt <ra...@apache.org>
Date:   Fri Mar 24 19:26:53 2017 -0500

    bad commit

commit 070f1c9e4dae947ba21b015ea5027c3d20d170bd
Author: randgalt <ra...@apache.org>
Date:   Fri Mar 24 19:22:48 2017 -0500

    CURATOR-275 introduced a new field into ServiceInstance. This caused a potential UnrecognizedPropertyException in older clients that read newly serialized ServiceInstances. Added an alternate ctor to JsonInstanceSerializer with a compatibleSerializationMode option. when set to true, the new enabled field of ServiceInstance is not serialized.


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

Branch: refs/heads/CURATOR-2.0
Commit: ff02a7c2fe33435df527d321d11964ba55851ad2
Parents: abaabb5
Author: randgalt <ra...@apache.org>
Authored: Fri May 26 11:40:00 2017 +0200
Committer: randgalt <ra...@apache.org>
Committed: Fri May 26 11:40:00 2017 +0200

----------------------------------------------------------------------
 .../curator/x/discovery/ServiceInstance.java    |  15 ++
 .../details/JsonInstanceSerializer.java         |  53 ++++-
 .../x/discovery/details/OldServiceInstance.java | 196 +++++++++++++++++++
 .../x/discovery/TestJsonInstanceSerializer.java |  16 +-
 ...TestJsonInstanceSerializerCompatibility.java | 107 ++++++++++
 .../details/TestNewServiceInstance.java         | 145 ++++++++++++++
 6 files changed, 522 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/curator/blob/ff02a7c2/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/ServiceInstance.java
----------------------------------------------------------------------
diff --git a/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/ServiceInstance.java b/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/ServiceInstance.java
index ebdd6bc..af2a2c7 100644
--- a/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/ServiceInstance.java
+++ b/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/ServiceInstance.java
@@ -82,6 +82,12 @@ public class ServiceInstance<T>
     }
 
     /**
+     * IMPORTANT: Due to CURATOR-275 the <code>enabled</code> field is <strong>NOT</strong> supported
+     * by default. If you wish to use the enabled field, you must set a {@link org.apache.curator.x.discovery.details.InstanceSerializer}
+     * that serializes this field. The default serializer, {@link org.apache.curator.x.discovery.details.JsonInstanceSerializer} does not
+     * serialize the field by default. You must use the alternate constructor {@link org.apache.curator.x.discovery.details.JsonInstanceSerializer#JsonInstanceSerializer(Class, boolean)}
+     * passing false for <code>compatibleSerializationMode</code>.
+     *
      * @param name name of the service
      * @param id id of this instance (must be unique)
      * @param address address of this instance
@@ -164,6 +170,15 @@ public class ServiceInstance<T>
         return uriSpec;
     }
 
+    /**
+     * IMPORTANT: Due to CURATOR-275 the <code>enabled</code> field is <strong>NOT</strong> supported
+     * by default. If you wish to use the enabled field, you must set a {@link org.apache.curator.x.discovery.details.InstanceSerializer}
+     * that serializes this field. The default serializer, {@link org.apache.curator.x.discovery.details.JsonInstanceSerializer} does not
+     * serialize the field by default. You must use the alternate constructor {@link org.apache.curator.x.discovery.details.JsonInstanceSerializer#JsonInstanceSerializer(Class, boolean)}
+     * passing false for <code>compatibleSerializationMode</code>.
+     *
+     * @return true/false
+     */
     public boolean isEnabled()
     {
         return enabled;

http://git-wip-us.apache.org/repos/asf/curator/blob/ff02a7c2/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/JsonInstanceSerializer.java
----------------------------------------------------------------------
diff --git a/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/JsonInstanceSerializer.java b/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/JsonInstanceSerializer.java
index b7ddbc2..715ec1d 100644
--- a/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/JsonInstanceSerializer.java
+++ b/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/JsonInstanceSerializer.java
@@ -16,12 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 package org.apache.curator.x.discovery.details;
 
+import com.google.common.annotations.VisibleForTesting;
 import org.apache.curator.x.discovery.ServiceInstance;
+import org.codehaus.jackson.map.DeserializationConfig;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.type.JavaType;
-import java.io.ByteArrayOutputStream;
 
 /**
  * A serializer that uses Jackson to serialize/deserialize as JSON. IMPORTANT: The instance
@@ -29,17 +31,51 @@ import java.io.ByteArrayOutputStream;
  */
 public class JsonInstanceSerializer<T> implements InstanceSerializer<T>
 {
-    private final ObjectMapper      mapper;
-    private final Class<T>          payloadClass;
-    private final JavaType          type;
+    private final ObjectMapper mapper;
+    private final Class<T> payloadClass;
+    private final boolean compatibleSerializationMode;
+    private final JavaType type;
 
     /**
+     * CURATOR-275 introduced a new field into {@link org.apache.curator.x.discovery.ServiceInstance}. This caused a potential
+     * {@link org.codehaus.jackson.map.exc.UnrecognizedPropertyException} in older clients that
+     * read newly serialized ServiceInstances. Therefore the default behavior of JsonInstanceSerializer
+     * has been changed to <em>NOT</em> serialize the <code>enabled</code> field. If you wish to use that field, use the
+     * alternate constructor {@link #JsonInstanceSerializer(Class, boolean)} and pass true for
+     * <code>compatibleSerializationMode</code>. Note: future versions of Curator <em>may</em> change this
+     * behavior.
+     *
      * @param payloadClass used to validate payloads when deserializing
      */
     public JsonInstanceSerializer(Class<T> payloadClass)
     {
+        this(payloadClass, true, false);
+    }
+
+    /**
+     * CURATOR-275 introduced a new field into {@link org.apache.curator.x.discovery.ServiceInstance}. This caused a potential
+     * {@link org.codehaus.jackson.map.exc.UnrecognizedPropertyException} in older clients that
+     * read newly serialized ServiceInstances. If you are susceptible to this you should set the
+     * serializer to be an instance of {@link org.apache.curator.x.discovery.details.JsonInstanceSerializer}
+     * with <code>compatibleSerializationMode</code> set to true. IMPORTANT: when this is done, the new <code>enabled</code>
+     * field of ServiceInstance is <strong>not</strong> serialized. If however you <em>do</em> want
+     * to use the <code>enabled</code> field, set <code>compatibleSerializationMode</code> to false.
+     *
+     * @param payloadClass used to validate payloads when deserializing
+     * @param compatibleSerializationMode pass true to serialize in a manner that supports clients pre-CURATOR-275
+     */
+    public JsonInstanceSerializer(Class<T> payloadClass, boolean compatibleSerializationMode)
+    {
+        this(payloadClass, compatibleSerializationMode, false);
+    }
+
+    @VisibleForTesting
+    JsonInstanceSerializer(Class<T> payloadClass, boolean compatibleSerializationMode, boolean failOnUnknownProperties)
+    {
         this.payloadClass = payloadClass;
+        this.compatibleSerializationMode = compatibleSerializationMode;
         mapper = new ObjectMapper();
+        mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties);
         type = mapper.getTypeFactory().constructType(ServiceInstance.class);
     }
 
@@ -55,8 +91,11 @@ public class JsonInstanceSerializer<T> implements InstanceSerializer<T>
     @Override
     public byte[] serialize(ServiceInstance<T> instance) throws Exception
     {
-        ByteArrayOutputStream           out = new ByteArrayOutputStream();
-        mapper.writeValue(out, instance);
-        return out.toByteArray();
+        if ( compatibleSerializationMode )
+        {
+            OldServiceInstance<T> compatible = new OldServiceInstance<T>(instance.getName(), instance.getId(), instance.getAddress(), instance.getPort(), instance.getSslPort(), instance.getPayload(), instance.getRegistrationTimeUTC(), instance.getServiceType(), instance.getUriSpec());
+            return mapper.writeValueAsBytes(compatible);
+        }
+        return mapper.writeValueAsBytes(instance);
     }
 }

http://git-wip-us.apache.org/repos/asf/curator/blob/ff02a7c2/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/OldServiceInstance.java
----------------------------------------------------------------------
diff --git a/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/OldServiceInstance.java b/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/OldServiceInstance.java
new file mode 100644
index 0000000..253b274
--- /dev/null
+++ b/curator-x-discovery/src/main/java/org/apache/curator/x/discovery/details/OldServiceInstance.java
@@ -0,0 +1,196 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.curator.x.discovery.details;
+
+import com.google.common.base.Preconditions;
+import org.apache.curator.x.discovery.ServiceType;
+import org.apache.curator.x.discovery.UriSpec;
+import org.codehaus.jackson.annotate.JsonTypeInfo;
+import org.codehaus.jackson.annotate.JsonTypeInfo.Id;
+
+/**
+ * POJO that represents a service instance
+ */
+class OldServiceInstance<T>
+{
+    private final String name;
+    private final String id;
+    private final String address;
+    private final Integer port;
+    private final Integer sslPort;
+    private final T payload;
+    private final long registrationTimeUTC;
+    private final ServiceType serviceType;
+    private final UriSpec uriSpec;
+
+    /**
+     * @param name name of the service
+     * @param id id of this instance (must be unique)
+     * @param address address of this instance
+     * @param port the port for this instance or null
+     * @param sslPort the SSL port for this instance or null
+     * @param payload the payload for this instance or null
+     * @param registrationTimeUTC the time (in UTC) of the registration
+     * @param serviceType type of the service
+     * @param uriSpec the uri spec or null
+     */
+    OldServiceInstance(String name, String id, String address, Integer port, Integer sslPort, T payload, long registrationTimeUTC, ServiceType serviceType, UriSpec uriSpec)
+    {
+        name = Preconditions.checkNotNull(name, "name cannot be null");
+        id = Preconditions.checkNotNull(id, "id cannot be null");
+
+        this.serviceType = serviceType;
+        this.uriSpec = uriSpec;
+        this.name = name;
+        this.id = id;
+        this.address = address;
+        this.port = port;
+        this.sslPort = sslPort;
+        this.payload = payload;
+        this.registrationTimeUTC = registrationTimeUTC;
+    }
+
+    OldServiceInstance()
+    {
+        this("", "", null, null, null, null, 0, ServiceType.DYNAMIC, null);
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public String getId()
+    {
+        return id;
+    }
+
+    public String getAddress()
+    {
+        return address;
+    }
+
+    public Integer getPort()
+    {
+        return port;
+    }
+
+    public Integer getSslPort()
+    {
+        return sslPort;
+    }
+
+    @JsonTypeInfo(use = Id.CLASS, defaultImpl = Object.class)
+    public T getPayload()
+    {
+        return payload;
+    }
+
+    public long getRegistrationTimeUTC()
+    {
+        return registrationTimeUTC;
+    }
+
+    public ServiceType getServiceType()
+    {
+        return serviceType;
+    }
+
+    public UriSpec getUriSpec()
+    {
+        return uriSpec;
+    }
+
+    @SuppressWarnings("RedundantIfStatement")
+    @Override
+    public boolean equals(Object o)
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        OldServiceInstance that = (OldServiceInstance)o;
+
+        if ( registrationTimeUTC != that.registrationTimeUTC )
+        {
+            return false;
+        }
+        if ( address != null ? !address.equals(that.address) : that.address != null )
+        {
+            return false;
+        }
+        if ( id != null ? !id.equals(that.id) : that.id != null )
+        {
+            return false;
+        }
+        if ( name != null ? !name.equals(that.name) : that.name != null )
+        {
+            return false;
+        }
+        if ( payload != null ? !payload.equals(that.payload) : that.payload != null )
+        {
+            return false;
+        }
+        if ( port != null ? !port.equals(that.port) : that.port != null )
+        {
+            return false;
+        }
+        if ( serviceType != that.serviceType )
+        {
+            return false;
+        }
+        if ( sslPort != null ? !sslPort.equals(that.sslPort) : that.sslPort != null )
+        {
+            return false;
+        }
+        if ( uriSpec != null ? !uriSpec.equals(that.uriSpec) : that.uriSpec != null )
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = name != null ? name.hashCode() : 0;
+        result = 31 * result + (id != null ? id.hashCode() : 0);
+        result = 31 * result + (address != null ? address.hashCode() : 0);
+        result = 31 * result + (port != null ? port.hashCode() : 0);
+        result = 31 * result + (sslPort != null ? sslPort.hashCode() : 0);
+        result = 31 * result + (payload != null ? payload.hashCode() : 0);
+        result = 31 * result + (int)(registrationTimeUTC ^ (registrationTimeUTC >>> 32));
+        result = 31 * result + (serviceType != null ? serviceType.hashCode() : 0);
+        result = 31 * result + (uriSpec != null ? uriSpec.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "ServiceInstance{" + "name='" + name + '\'' + ", id='" + id + '\'' + ", address='" + address + '\'' + ", port=" + port + ", sslPort=" + sslPort + ", payload=" + payload + ", registrationTimeUTC=" + registrationTimeUTC + ", serviceType=" + serviceType + ", uriSpec=" + uriSpec + '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/ff02a7c2/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/TestJsonInstanceSerializer.java
----------------------------------------------------------------------
diff --git a/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/TestJsonInstanceSerializer.java b/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/TestJsonInstanceSerializer.java
index f17919d..0ed6c3d 100644
--- a/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/TestJsonInstanceSerializer.java
+++ b/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/TestJsonInstanceSerializer.java
@@ -98,7 +98,7 @@ public class TestJsonInstanceSerializer
     @Test
     public void		testPayloadAsList() throws Exception
     {
-        JsonInstanceSerializer<Object>    serializer = new JsonInstanceSerializer<Object>(Object.class);
+        JsonInstanceSerializer<Object>    serializer = new JsonInstanceSerializer<Object>(Object.class, false);
         List<String> payload = new ArrayList<String>();
         payload.add("Test value 1");
         payload.add("Test value 2");
@@ -121,7 +121,7 @@ public class TestJsonInstanceSerializer
     @Test
     public void		testPayloadAsMap() throws Exception
     {
-        JsonInstanceSerializer<Object>    serializer = new JsonInstanceSerializer<Object>(Object.class);
+        JsonInstanceSerializer<Object>    serializer = new JsonInstanceSerializer<Object>(Object.class, false);
         Map<String,String> payload = new HashMap<String,String>();
         payload.put("1", "Test value 1");
         payload.put("2", "Test value 2");
@@ -166,7 +166,17 @@ public class TestJsonInstanceSerializer
     	public String getVal() {
     		return val;
     	}
-    	public void setVal(String val) {
+
+        public Payload()
+        {
+        }
+
+        public Payload(String val)
+        {
+            this.val = val;
+        }
+
+        public void setVal(String val) {
     		this.val = val;
     	}
     	@Override

http://git-wip-us.apache.org/repos/asf/curator/blob/ff02a7c2/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/details/TestJsonInstanceSerializerCompatibility.java
----------------------------------------------------------------------
diff --git a/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/details/TestJsonInstanceSerializerCompatibility.java b/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/details/TestJsonInstanceSerializerCompatibility.java
new file mode 100644
index 0000000..6e0e63e
--- /dev/null
+++ b/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/details/TestJsonInstanceSerializerCompatibility.java
@@ -0,0 +1,107 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.curator.x.discovery.details;
+
+import org.apache.curator.x.discovery.ServiceInstance;
+import org.apache.curator.x.discovery.ServiceType;
+import org.apache.curator.x.discovery.TestJsonInstanceSerializer;
+import org.apache.curator.x.discovery.UriSpec;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.JavaType;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import java.net.URI;
+import java.util.Date;
+
+public class TestJsonInstanceSerializerCompatibility
+{
+    @Test
+    public void testCompatibilityMode() throws Exception
+    {
+        JsonInstanceSerializer<TestJsonInstanceSerializer.Payload> serializer = new JsonInstanceSerializer<TestJsonInstanceSerializer.Payload>(TestJsonInstanceSerializer.Payload.class, true, true);
+        ServiceInstance<TestJsonInstanceSerializer.Payload> instance = new ServiceInstance<TestJsonInstanceSerializer.Payload>("name", "id", "address", 10, 20, new TestJsonInstanceSerializer.Payload("test"), 0, ServiceType.DYNAMIC, new UriSpec("{a}/b/{c}"), true);
+        byte[] bytes = serializer.serialize(instance);
+
+        OldServiceInstance<TestJsonInstanceSerializer.Payload> oldInstance = new OldServiceInstance<TestJsonInstanceSerializer.Payload>("name", "id", "address", 10, 20, new TestJsonInstanceSerializer.Payload("test"), 0, ServiceType.DYNAMIC, new UriSpec("{a}/b/{c}"));
+        ObjectMapper mapper = new ObjectMapper();
+        byte[] oldBytes = mapper.writeValueAsBytes(oldInstance);
+        Assert.assertEquals(bytes, oldBytes, String.format("%s vs %s", new String(bytes), new String(oldBytes)));
+    }
+
+    @Test
+    public void testBackwardCompatibility() throws Exception
+    {
+        JsonInstanceSerializer<TestJsonInstanceSerializer.Payload> serializer = new JsonInstanceSerializer<TestJsonInstanceSerializer.Payload>(TestJsonInstanceSerializer.Payload.class, true, true);
+        ServiceInstance<TestJsonInstanceSerializer.Payload> instance = new ServiceInstance<TestJsonInstanceSerializer.Payload>("name", "id", "address", 10, 20, new TestJsonInstanceSerializer.Payload("test"), 0, ServiceType.DYNAMIC, new UriSpec("{a}/b/{c}"), false);
+        byte[] bytes = serializer.serialize(instance);
+
+        instance = serializer.deserialize(bytes);
+        Assert.assertTrue(instance.isEnabled());    // passed false for enabled in the ctor but that is lost with compatibleSerializationMode
+
+        ObjectMapper mapper = new ObjectMapper();
+        JavaType type = mapper.getTypeFactory().constructType(OldServiceInstance.class);
+        OldServiceInstance rawServiceInstance = mapper.readValue(bytes, type);
+        TestJsonInstanceSerializer.Payload.class.cast(rawServiceInstance.getPayload()); // just to verify that it's the correct type
+        //noinspection unchecked
+        OldServiceInstance<TestJsonInstanceSerializer.Payload> check = (OldServiceInstance<TestJsonInstanceSerializer.Payload>)rawServiceInstance;
+        Assert.assertEquals(check.getName(), instance.getName());
+        Assert.assertEquals(check.getId(), instance.getId());
+        Assert.assertEquals(check.getAddress(), instance.getAddress());
+        Assert.assertEquals(check.getPort(), instance.getPort());
+        Assert.assertEquals(check.getSslPort(), instance.getSslPort());
+        Assert.assertEquals(check.getPayload(), instance.getPayload());
+        Assert.assertEquals(check.getRegistrationTimeUTC(), instance.getRegistrationTimeUTC());
+        Assert.assertEquals(check.getServiceType(), instance.getServiceType());
+        Assert.assertEquals(check.getUriSpec(), instance.getUriSpec());
+    }
+
+    @Test
+    public void testForwardCompatibility() throws Exception
+    {
+        OldServiceInstance<TestJsonInstanceSerializer.Payload> oldInstance = new OldServiceInstance<TestJsonInstanceSerializer.Payload>("name", "id", "address", 10, 20, new TestJsonInstanceSerializer.Payload("test"), 0, ServiceType.DYNAMIC, new UriSpec("{a}/b/{c}"));
+        ObjectMapper mapper = new ObjectMapper();
+        byte[] oldJson = mapper.writeValueAsBytes(oldInstance);
+
+        JsonInstanceSerializer<TestJsonInstanceSerializer.Payload> serializer = new JsonInstanceSerializer<TestJsonInstanceSerializer.Payload>(TestJsonInstanceSerializer.Payload.class);
+        ServiceInstance<TestJsonInstanceSerializer.Payload> instance = serializer.deserialize(oldJson);
+        Assert.assertEquals(oldInstance.getName(), instance.getName());
+        Assert.assertEquals(oldInstance.getId(), instance.getId());
+        Assert.assertEquals(oldInstance.getAddress(), instance.getAddress());
+        Assert.assertEquals(oldInstance.getPort(), instance.getPort());
+        Assert.assertEquals(oldInstance.getSslPort(), instance.getSslPort());
+        Assert.assertEquals(oldInstance.getPayload(), instance.getPayload());
+        Assert.assertEquals(oldInstance.getRegistrationTimeUTC(), instance.getRegistrationTimeUTC());
+        Assert.assertEquals(oldInstance.getServiceType(), instance.getServiceType());
+        Assert.assertEquals(oldInstance.getUriSpec(), instance.getUriSpec());
+        Assert.assertTrue(instance.isEnabled());
+    }
+
+    @Test
+    public void testFutureChanges() throws Exception
+    {
+        TestNewServiceInstance<String> newInstance = new TestNewServiceInstance<String>("name", "id", "address", 10, 20, "hey", 0, ServiceType.DYNAMIC, new UriSpec("{a}/b/{c}"), false, "what", 10101L, new Date(), new URI("http://hey"));
+        byte[] newInstanceBytes = new ObjectMapper().writeValueAsBytes(newInstance);
+        JsonInstanceSerializer<String> serializer = new JsonInstanceSerializer<String>(String.class);
+        ServiceInstance<String> instance = serializer.deserialize(newInstanceBytes);
+        Assert.assertEquals(instance.getName(), "name");
+        Assert.assertEquals(instance.getPayload(), "hey");
+        Assert.assertEquals(instance.isEnabled(), false);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/ff02a7c2/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/details/TestNewServiceInstance.java
----------------------------------------------------------------------
diff --git a/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/details/TestNewServiceInstance.java b/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/details/TestNewServiceInstance.java
new file mode 100644
index 0000000..e627474
--- /dev/null
+++ b/curator-x-discovery/src/test/java/org/apache/curator/x/discovery/details/TestNewServiceInstance.java
@@ -0,0 +1,145 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.curator.x.discovery.details;
+
+import com.google.common.base.Preconditions;
+import org.apache.curator.x.discovery.ServiceType;
+import org.apache.curator.x.discovery.UriSpec;
+import org.codehaus.jackson.annotate.JsonTypeInfo;
+import org.codehaus.jackson.annotate.JsonTypeInfo.Id;
+import java.net.URI;
+import java.util.Date;
+
+class TestNewServiceInstance<T>
+{
+    private final String name;
+    private final String id;
+    private final String address;
+    private final Integer port;
+    private final Integer sslPort;
+    private final T payload;
+    private final long registrationTimeUTC;
+    private final ServiceType serviceType;
+    private final UriSpec uriSpec;
+    private final boolean enabled;
+    private final String new1;
+    private final Long new2;
+    private final Date new3;
+    private final URI new4;
+
+    public TestNewServiceInstance(String name, String id, String address, Integer port, Integer sslPort, T payload, long registrationTimeUTC, ServiceType serviceType, UriSpec uriSpec, boolean enabled, String new1, Long new2, Date new3, URI new4)
+    {
+        name = Preconditions.checkNotNull(name, "name cannot be null");
+        id = Preconditions.checkNotNull(id, "id cannot be null");
+
+        this.new1 = new1;
+        this.new2 = new2;
+        this.new3 = new3;
+        this.new4 = new4;
+        this.serviceType = serviceType;
+        this.uriSpec = uriSpec;
+        this.name = name;
+        this.id = id;
+        this.address = address;
+        this.port = port;
+        this.sslPort = sslPort;
+        this.payload = payload;
+        this.registrationTimeUTC = registrationTimeUTC;
+        this.enabled = enabled;
+    }
+
+    /**
+     * Inits to default values. Only exists for deserialization
+     */
+    TestNewServiceInstance()
+    {
+        this("", "", null, null, null, null, 0, ServiceType.DYNAMIC, null, true, null, null, null, null);
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public String getId()
+    {
+        return id;
+    }
+
+    public String getAddress()
+    {
+        return address;
+    }
+
+    public Integer getPort()
+    {
+        return port;
+    }
+
+    public Integer getSslPort()
+    {
+        return sslPort;
+    }
+
+    @JsonTypeInfo(use = Id.CLASS, defaultImpl = Object.class)
+    public T getPayload()
+    {
+        return payload;
+    }
+
+    public long getRegistrationTimeUTC()
+    {
+        return registrationTimeUTC;
+    }
+
+    public ServiceType getServiceType()
+    {
+        return serviceType;
+    }
+
+    public UriSpec getUriSpec()
+    {
+        return uriSpec;
+    }
+
+    public boolean isEnabled()
+    {
+        return enabled;
+    }
+
+    public String getNew1()
+    {
+        return new1;
+    }
+
+    public Long getNew2()
+    {
+        return new2;
+    }
+
+    public Date getNew3()
+    {
+        return new3;
+    }
+
+    public URI getNew4()
+    {
+        return new4;
+    }
+}