Repository: hyperledger/quilt (java)

Issue:
InterledgerAddressPrefix needs special Jackson serializer/deserializer to avoid infinite recursion

Pull request:
add module/serializer/deserializer for address prefix #371
Signed-off-by: Ian Simpson <ian.g.simpson@gmail.com>

Patch:
diff --git a/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/InterledgerDeserializers.java b/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/InterledgerDeserializers.java
index 06a813c..8a6fd13 100644
--- a/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/InterledgerDeserializers.java
+++ b/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/InterledgerDeserializers.java
@@ -21,10 +21,12 @@ package org.interledger.quilt.jackson;
  */
 
 import org.interledger.core.InterledgerAddress;
+import org.interledger.core.InterledgerAddressPrefix;
 import org.interledger.core.InterledgerCondition;
 import org.interledger.core.InterledgerFulfillment;
 import org.interledger.core.SharedSecret;
 import org.interledger.quilt.jackson.address.InterledgerAddressDeserializer;
+import org.interledger.quilt.jackson.addressprefix.InterledgerAddressPrefixDeserializer;
 import org.interledger.quilt.jackson.conditions.ConditionDeserializer;
 import org.interledger.quilt.jackson.conditions.Encoding;
 import org.interledger.quilt.jackson.conditions.FulfillmentDeserializer;
@@ -58,6 +60,10 @@ public class InterledgerDeserializers extends Deserializers.Base {
       return new InterledgerAddressDeserializer();
     }
 
+    if (type.hasRawClass(InterledgerAddressPrefix.class)) {
+      return new InterledgerAddressPrefixDeserializer();
+    }
+
     if (type.hasRawClass(InterledgerCondition.class)) {
       return new ConditionDeserializer(cryptoConditionEncoding);
     }
diff --git a/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/InterledgerSerializers.java b/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/InterledgerSerializers.java
index ebeda32..0c40b39 100644
--- a/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/InterledgerSerializers.java
+++ b/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/InterledgerSerializers.java
@@ -21,10 +21,12 @@ package org.interledger.quilt.jackson;
  */
 
 import org.interledger.core.InterledgerAddress;
+import org.interledger.core.InterledgerAddressPrefix;
 import org.interledger.core.InterledgerCondition;
 import org.interledger.core.InterledgerFulfillment;
 import org.interledger.core.SharedSecret;
 import org.interledger.quilt.jackson.address.InterledgerAddressSerializer;
+import org.interledger.quilt.jackson.addressprefix.InterledgerAddressPrefixSerializer;
 import org.interledger.quilt.jackson.conditions.ConditionSerializer;
 import org.interledger.quilt.jackson.conditions.Encoding;
 import org.interledger.quilt.jackson.conditions.FulfillmentSerializer;
@@ -57,6 +59,9 @@ public class InterledgerSerializers extends Serializers.Base {
     if (InterledgerAddress.class.isAssignableFrom(raw)) {
       return InterledgerAddressSerializer.INSTANCE;
     }
+    if (InterledgerAddressPrefix.class.isAssignableFrom(raw)) {
+      return new InterledgerAddressPrefixSerializer();
+    }
     if (InterledgerCondition.class.isAssignableFrom(raw)) {
       return new ConditionSerializer(cryptoConditionEncoding);
     }
diff --git a/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixDeserializer.java b/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixDeserializer.java
new file mode 100644
index 0000000..dbea638
--- /dev/null
+++ b/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixDeserializer.java
@@ -0,0 +1,41 @@
+package org.interledger.quilt.jackson.addressprefix;
+
+/*-
+ * ========================LICENSE_START=================================
+ * Interledger Jackson Datatypes
+ * %%
+ * Copyright (C) 2017 - 2018 Hyperledger and its contributors
+ * %%
+ * Licensed 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.
+ * =========================LICENSE_END==================================
+ */
+
+import org.interledger.core.InterledgerAddressPrefix;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+
+import java.io.IOException;
+
+public class InterledgerAddressPrefixDeserializer extends StdDeserializer<InterledgerAddressPrefix> {
+
+  public InterledgerAddressPrefixDeserializer() {
+    super(InterledgerAddressPrefix.class);
+  }
+
+  @Override
+  public InterledgerAddressPrefix deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+    return InterledgerAddressPrefix.of(p.getText());
+  }
+}
diff --git a/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixModule.java b/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixModule.java
new file mode 100644
index 0000000..0750856
--- /dev/null
+++ b/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixModule.java
@@ -0,0 +1,42 @@
+package org.interledger.quilt.jackson.addressprefix;
+
+/*-
+ * ========================LICENSE_START=================================
+ * Interledger Jackson Datatypes
+ * %%
+ * Copyright (C) 2017 - 2018 Hyperledger and its contributors
+ * %%
+ * Licensed 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.
+ * =========================LICENSE_END==================================
+ */
+
+import org.interledger.core.InterledgerAddressPrefix;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+public class InterledgerAddressPrefixModule extends SimpleModule {
+
+  private static final String NAME = "InterledgerAddressPrefixModule";
+
+  public InterledgerAddressPrefixModule() {
+    super(
+        NAME,
+        new Version(1, 0, 0, null, "org.interledger.connector",
+            "interledger-address-prefix")
+    );
+
+    addSerializer(InterledgerAddressPrefix.class, new InterledgerAddressPrefixSerializer());
+    addDeserializer(InterledgerAddressPrefix.class, new InterledgerAddressPrefixDeserializer());
+  }
+}
diff --git a/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixSerializer.java b/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixSerializer.java
new file mode 100644
index 0000000..911102c
--- /dev/null
+++ b/jackson-datatypes/src/main/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixSerializer.java
@@ -0,0 +1,42 @@
+package org.interledger.quilt.jackson.addressprefix;
+
+/*-
+ * ========================LICENSE_START=================================
+ * Interledger Jackson Datatypes
+ * %%
+ * Copyright (C) 2017 - 2018 Hyperledger and its contributors
+ * %%
+ * Licensed 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.
+ * =========================LICENSE_END==================================
+ */
+
+import org.interledger.core.InterledgerAddressPrefix;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer;
+
+import java.io.IOException;
+
+public class InterledgerAddressPrefixSerializer extends StdScalarSerializer<InterledgerAddressPrefix> {
+
+  public InterledgerAddressPrefixSerializer() {
+    super(InterledgerAddressPrefix.class, false);
+  }
+
+  @Override
+  public void serialize(InterledgerAddressPrefix value, JsonGenerator gen,
+                        SerializerProvider provider) throws IOException {
+    gen.writeString(value.getValue());
+  }
+}


Test patch:
diff --git a/jackson-datatypes/src/test/java/org/interledger/quilt/jackson/InterledgerModuleTest.java b/jackson-datatypes/src/test/java/org/interledger/quilt/jackson/InterledgerModuleTest.java
index 2708b4e..a1c0695 100644
--- a/jackson-datatypes/src/test/java/org/interledger/quilt/jackson/InterledgerModuleTest.java
+++ b/jackson-datatypes/src/test/java/org/interledger/quilt/jackson/InterledgerModuleTest.java
@@ -23,6 +23,7 @@ package org.interledger.quilt.jackson;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import org.interledger.core.InterledgerAddress;
+import org.interledger.core.InterledgerAddressPrefix;
 import org.interledger.core.InterledgerCondition;
 import org.interledger.core.InterledgerFulfillment;
 import org.interledger.core.SharedSecret;
@@ -124,13 +125,16 @@ public class InterledgerModuleTest {
 
     final InterledgerAddress expectedAddress = InterledgerAddress.of("test1.ledger.foo");
 
+    final InterledgerAddressPrefix expectedPrefix = InterledgerAddressPrefix.of("test1.ledger");
+
     final InterledgerContainer expectedContainer
-        = new InterledgerContainer(expectedAddress, condition);
+        = new InterledgerContainer(expectedAddress, expectedPrefix, condition);
 
     final String json = objectMapper.writeValueAsString(expectedContainer);
     assertThat(json).isEqualTo(
-        String.format("{\"ledger_prefix\":\"%s\",\"execution_condition\":\"%s\"}",
+        String.format("{\"ledger_address\":\"%s\",\"ledger_prefix\":\"%s\",\"execution_condition\":\"%s\"}",
             expectedContainer.getInterledgerAddress().getValue(),
+            expectedContainer.getInterledgerAddressPrefix().getValue(),
             expectedEncodedValue)
     );
 
@@ -138,6 +142,7 @@ public class InterledgerModuleTest {
         .readValue(json, InterledgerContainer.class);
 
     assertThat(actualAddressContainer).isEqualTo(expectedContainer);
+    assertThat(actualAddressContainer.getInterledgerAddressPrefix()).isEqualTo(expectedPrefix);
     assertThat(actualAddressContainer.getCondition()).isEqualTo(condition);
   }
 
@@ -166,18 +171,23 @@ public class InterledgerModuleTest {
 
   private static class InterledgerContainer {
 
-    @JsonProperty("ledger_prefix")
+    @JsonProperty("ledger_address")
     private final InterledgerAddress interledgerAddress;
 
+    @JsonProperty("ledger_prefix")
+    private final InterledgerAddressPrefix interledgerAddressPrefix;
+
     @JsonProperty("execution_condition")
     private final InterledgerCondition condition;
 
     @JsonCreator
     public InterledgerContainer(
-        @JsonProperty("ledger_prefix") final InterledgerAddress interledgerAddress,
+        @JsonProperty("ledger_address") final InterledgerAddress interledgerAddress,
+        @JsonProperty("ledger_prefix") final InterledgerAddressPrefix interledgerAddressPrefix,
         @JsonProperty("execution_condition") final InterledgerCondition condition
     ) {
       this.interledgerAddress = Objects.requireNonNull(interledgerAddress);
+      this.interledgerAddressPrefix = Objects.requireNonNull(interledgerAddressPrefix);
       this.condition = Objects.requireNonNull(condition);
     }
 
@@ -185,6 +195,10 @@ public class InterledgerModuleTest {
       return interledgerAddress;
     }
 
+    public InterledgerAddressPrefix getInterledgerAddressPrefix() {
+      return interledgerAddressPrefix;
+    }
+
     public InterledgerCondition getCondition() {
       return condition;
     }
@@ -203,6 +217,9 @@ public class InterledgerModuleTest {
       if (!interledgerAddress.equals(that.interledgerAddress)) {
         return false;
       }
+      if (!interledgerAddressPrefix.equals(that.interledgerAddressPrefix)) {
+        return false;
+      }
       return condition.equals(that.condition);
     }
 
@@ -217,6 +234,7 @@ public class InterledgerModuleTest {
     public String toString() {
       return "InterledgerContainer{"
           + "interledgerAddress=" + interledgerAddress
+          + ", interledgerAddressPrefix=" + interledgerAddressPrefix
           + ", condition=" + condition
           + '}';
     }
diff --git a/jackson-datatypes/src/test/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixModuleTest.java b/jackson-datatypes/src/test/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixModuleTest.java
new file mode 100644
index 0000000..9f3252f
--- /dev/null
+++ b/jackson-datatypes/src/test/java/org/interledger/quilt/jackson/addressprefix/InterledgerAddressPrefixModuleTest.java
@@ -0,0 +1,108 @@
+package org.interledger.quilt.jackson.addressprefix;
+
+/*-
+ * ========================LICENSE_START=================================
+ * Interledger Jackson Datatypes
+ * %%
+ * Copyright (C) 2017 - 2018 Hyperledger and its contributors
+ * %%
+ * Licensed 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.
+ * =========================LICENSE_END==================================
+ */
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.interledger.core.InterledgerAddressPrefix;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Validates the functionality of {@link InterledgerAddressPrefixModule}.
+ */
+public class InterledgerAddressPrefixModuleTest {
+
+  private ObjectMapper objectMapper;
+
+  @Before
+  public void setup() {
+    this.objectMapper = new ObjectMapper()
+        .registerModule(new InterledgerAddressPrefixModule());
+  }
+
+  @Test
+  public void testSerializeDeserialize() throws IOException {
+    final InterledgerAddressPrefix expectedAddress = InterledgerAddressPrefix.of("test1.ledger.foo");
+
+    final InterledgerContainer expectedContainer = new InterledgerContainer(expectedAddress);
+
+    final String json = objectMapper.writeValueAsString(expectedContainer);
+    assertThat(json).isEqualTo(
+        String.format("{\"ledger_prefix\":\"%s\"}", expectedContainer.getInterledgerAddressPrefix().getValue())
+    );
+
+    final InterledgerContainer actualAddressContainer = objectMapper
+        .readValue(json, InterledgerContainer.class);
+
+    assertThat(actualAddressContainer).isEqualTo(expectedContainer);
+    assertThat(actualAddressContainer.getInterledgerAddressPrefix()).isEqualTo(expectedAddress);
+  }
+
+
+  private static class InterledgerContainer {
+
+    @JsonProperty("ledger_prefix")
+    private final InterledgerAddressPrefix interledgerAddressPrefix;
+
+    @JsonCreator
+    public InterledgerContainer(
+        @JsonProperty("ledger_prefix") final InterledgerAddressPrefix interledgerAddress
+    ) {
+      this.interledgerAddressPrefix = Objects.requireNonNull(interledgerAddress);
+    }
+
+    public InterledgerAddressPrefix getInterledgerAddressPrefix() {
+      return interledgerAddressPrefix;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null || getClass() != obj.getClass()) {
+        return false;
+      }
+      InterledgerContainer that = (InterledgerContainer) obj;
+      return Objects.equals(getInterledgerAddressPrefix(), that.getInterledgerAddressPrefix());
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(getInterledgerAddressPrefix());
+    }
+
+    @Override
+    public String toString() {
+      return "InterledgerContainer{"
+          + "interledgerAddressPrefix=" + interledgerAddressPrefix
+          + '}';
+    }
+  }
+}