Add back helper types from alto-commons 42/30542/1
authorKai GAO <gaok12@mails.tsinghua.edu.cn>
Thu, 3 Dec 2015 09:44:35 +0000 (17:44 +0800)
committerKai GAO <gaok12@mails.tsinghua.edu.cn>
Thu, 3 Dec 2015 09:46:49 +0000 (17:46 +0800)
Change-Id: Id0a9cd0f6d9538c346c48e803336b63a947d4a54
Signed-off-by: Kai GAO <gaok12@mails.tsinghua.edu.cn>
14 files changed:
alto-core/northbound/api/pom.xml
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/Extensible.java [new file with mode: 0644]
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/FormatValidator.java [new file with mode: 0644]
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/MediaType.java [new file with mode: 0644]
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285CostMap.java [new file with mode: 0644]
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285CostType.java [new file with mode: 0644]
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285Endpoint.java [new file with mode: 0644]
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285EndpointPropertyMap.java [new file with mode: 0644]
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285IRD.java [new file with mode: 0644]
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285JSONMapper.java [new file with mode: 0644]
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285NetworkMap.java [new file with mode: 0644]
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285QueryPairs.java [new file with mode: 0644]
alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285VersionTag.java [new file with mode: 0644]
alto-core/northbound/api/src/test/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/TestRFC7285Types.java [new file with mode: 0644]

index d6ef1c7fe01eda798e9e043cef8ab17147ddded2..9e5fedd82bf5b5de59f33a8a979bd88c0db0e347 100644 (file)
@@ -34,5 +34,22 @@ and is available at http://www.eclipse.org/legal/epl-v10.html
       <artifactId>servlet-api</artifactId>
       <version>2.5</version>
     </dependency>
+    <dependency>
+      <artifactId>junit</artifactId>
+      <groupId>junit</groupId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-annotations</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-core</artifactId>
+    </dependency>
   </dependencies>
 </project>
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/Extensible.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/Extensible.java
new file mode 100644 (file)
index 0000000..911615b
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import java.util.Map;
+import java.util.LinkedHashMap;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+public class Extensible {
+
+    @JsonIgnore
+    private Map<String, Object> extra = new LinkedHashMap<String, Object>();
+
+    @JsonAnyGetter
+    public Map<String, Object> any() {
+        return extra;
+    }
+
+    @JsonAnySetter
+    public void set(String name, Object value) {
+        extra.put(name, value);
+    }
+}
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/FormatValidator.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/FormatValidator.java
new file mode 100644 (file)
index 0000000..52900d3
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import java.util.regex.Pattern;
+
+public class FormatValidator {
+
+    /**
+     * RFC 7285  section 10.1
+     * */
+    private static final String VALID_CHARSET = "a-zA-Z_0-9\\-:@";
+    private static final Pattern VALID_ID_PATTERN
+                            = Pattern.compile("^["+VALID_CHARSET+"]{1,64}$");
+    private static final String VALID_CHARSET_WITH_DOT = VALID_CHARSET + "\\.";
+    private static final Pattern VALID_ID_PATTERN_WITH_DOT
+                            = Pattern.compile("^["+VALID_CHARSET_WITH_DOT+"]{1,64}$");
+    private static final String VALID_TAG_CHARSET = "!-~";
+    private static final Pattern VALID_TAG_PATTERN
+                            = Pattern.compile("^["+VALID_TAG_CHARSET+"]{1,64}$");
+    private static final String VALID_ADDR_IPV4 = "^ipv4:(([0-9]|[1-9][0-9]|1[0-9][0-9]|"
+                                                    + "2[0-4][0-9]|25[0-5])\\.){3}"
+                                                    + "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"
+                                                    + "(%[\\p{N}\\p{L}]+)?$";
+    private static final Pattern VALID_ADDR_IPV4_PATTERN
+                            = Pattern.compile(VALID_ADDR_IPV4);
+    private static final String VALID_ADDR_IPV6_1 = "^ipv6:((:|[0-9a-fA-F]{0,4}):)([0-9a-fA-F]{0,4}:){0,5}"
+                                                    + "((([0-9a-fA-F]{0,4}:)?(:|[0-9a-fA-F]{0,4}))|"
+                                                    + "(((25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\\.){3}"
+                                                    + "(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])))"
+                                                    + "(%[\\p{N}\\p{L}]+)?$";
+    private static final Pattern VALID_ADDR_IPV6_1_PATTERN
+                            = Pattern.compile(VALID_ADDR_IPV6_1);
+    private static final String VALID_ADDR_IPV6_2 = "^ipv6:((([^:]+:){6}(([^:]+:[^:]+)|(.*\\..*)))|"
+                                                    + "((([^:]+:)*[^:]+)?::(([^:]+:)*[^:]+)?)"
+                                                    + "(%.+)?)$";
+    private static final Pattern VALID_ADDR_IPV6_2_PATTERN
+                            = Pattern.compile(VALID_ADDR_IPV6_2);
+    private static final String VALID_OPERATORS = "(gt|lt|ge|le|eq)";
+    private static final Pattern VALID_CONSTRAINTS_PATTERN
+                            = Pattern.compile("^"+VALID_OPERATORS+" [0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$");
+
+
+    public static boolean validId(String id) {
+        return VALID_ID_PATTERN.matcher(id).matches();
+    }
+
+    public static boolean validIdWithDots(String id) {
+            return VALID_ID_PATTERN_WITH_DOT.matcher(id).matches();
+    }
+
+    /**
+     * RFC 7285 section 10.1
+     * */
+    public static boolean validPid(String id) {
+        return validId(id);
+    }
+
+    public static boolean validPidWithDots(String id) {
+        return validIdWithDots(id);
+    }
+
+    /**
+     * RFC 7285 section 10.2
+     * */
+    public static boolean validResourceId(String id) {
+        return validId(id);
+    }
+
+    public static boolean validResourceIdWithDots(String id) {
+        return validIdWithDots(id);
+    }
+
+    /**
+     * RFC 7285 section 10.3
+     * */
+    public static boolean validTag(String tag) {
+        return VALID_TAG_PATTERN.matcher(tag).matches();
+    }
+    /**
+     * RFC 7285 section 10.8.1
+     * */
+    public static boolean validSpecificEndpointProperty(String prop) {
+        /* TODO  maybe enhance the performance? */
+        return validId(prop) && (prop.indexOf('@') == -1);
+    }
+
+    public static boolean validSpecificEndpointPropertyWithDots(String prop) {
+        /* TODO  maybe enhance the performance? */
+        return validIdWithDots(prop) && (prop.indexOf('@') == -1);
+    }
+
+    /**
+     * RFC 7285 seciton 10.8.2
+     * */
+    public static boolean validGlobalEndpointProperty(String prop) {
+        return (prop.length() <= 32) && validId(prop);
+    }
+
+    /**
+     * RFC 7285 section 11.3.2
+     * */
+    public static boolean validFilterConstraint(String constrant) {
+        return VALID_CONSTRAINTS_PATTERN.matcher(constrant).matches();
+    }
+
+    public static boolean validAddressIpv4(String address) {
+        return VALID_ADDR_IPV4_PATTERN.matcher(address).matches();
+    }
+
+    public static boolean validAddressIpv6(String address) {
+        return VALID_ADDR_IPV6_1_PATTERN.matcher(address).matches() &&
+                VALID_ADDR_IPV6_2_PATTERN.matcher(address).matches();
+    }
+
+    public static boolean validEndpointAddress(String address) {
+        return validAddressIpv4(address) || validAddressIpv6(address);
+    }
+}
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/MediaType.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/MediaType.java
new file mode 100644 (file)
index 0000000..4a977d0
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+public class MediaType {
+
+    /** The media types defined in [RFC7285]
+     *
+     *  The media types defined here should be identical to those
+     *  generated by alto-model, the main reason we need this is
+     *  the ones defined here are *STRING LITERALS*.
+     * */
+
+    public static final String ALTO_DIRECTORY
+                            = "application/alto-directory+json";
+    public static final String ALTO_NETWORKMAP
+                            = "application/alto-networkmap+json";
+    public static final String ALTO_NETWORKMAP_FILTER
+                            = "application/alto-networkmapfilter+json";
+    public static final String ALTO_COSTMAP
+                            = "application/alto-costmap+json";
+    public static final String ALTO_COSTMAP_FILTER
+                            = "application/alto-costmapfilter+json";
+    public static final String ALTO_ENDPOINT_PROP
+                            = "application/alto-endpointprop+json";
+    public static final String ALTO_ENDPOINT_PROPPARAMS
+                            = "application/alto-endpointpropparams+json";
+    public static final String ALTO_ENDPOINT_COST
+                            = "application/alto-endpointcost+json";
+    public static final String ALTO_ENDPOINT_COSTPARAMS
+                            = "application/alto-endpointcostparams+json";
+    public static final String ALTO_ERROR
+                            = "application/alto-error+json";
+}
+
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285CostMap.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285CostMap.java
new file mode 100644 (file)
index 0000000..3917d31
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import java.util.List;
+import java.util.Map;
+import java.util.LinkedList;
+import java.util.LinkedHashMap;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Cost Map: defined in RFC7285 secion 11.2.3
+ * */
+public class RFC7285CostMap {
+
+    public static class Meta extends Extensible {
+
+        @JsonProperty("dependent-vtags")
+        public List<RFC7285VersionTag> netmap_tags = new LinkedList<RFC7285VersionTag>();
+
+        @JsonProperty("cost-type")
+        public RFC7285CostType costType;
+    }
+
+    /**
+     * for filtered-cost-map service, RFC7285 secion 11.3.2
+     * */
+    public static class Filter {
+
+        @JsonProperty("cost-type")
+        public RFC7285CostType costType;
+
+        @JsonProperty("pids")
+        public RFC7285QueryPairs pids;
+
+        @JsonProperty("constraints")
+        public List<String> constraints;
+    }
+
+    @JsonProperty("meta")
+    public Meta meta = new Meta();
+
+    @JsonProperty("cost-map")
+    public Map<String, Map<String, Object>> map
+                        = new LinkedHashMap<String, Map<String, Object>>();
+
+}
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285CostType.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285CostType.java
new file mode 100644 (file)
index 0000000..677fed0
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Arrays;
+
+public class RFC7285CostType {
+
+    @JsonProperty("cost-mode")
+    public String mode = null;
+
+    @JsonProperty("cost-metric")
+    public String metric = null;
+
+    @JsonProperty("description")
+    public String description = null;
+
+    public RFC7285CostType() {
+    }
+
+    public RFC7285CostType(String mode, String metric) {
+        this.mode = mode;
+        this.metric = metric;
+    }
+
+    public RFC7285CostType(String mode, String metric, String description) {
+        this.mode = mode;
+        this.metric = metric;
+        this.description = description;
+    }
+
+    @Override
+    public int hashCode() {
+        String[] members = { metric, mode };
+        return Arrays.hashCode(members);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!(obj != null ? (obj instanceof RFC7285CostType) : false))
+            return false;
+
+        RFC7285CostType other = (RFC7285CostType)obj;
+        String[] lhs = { metric, mode };
+        String[] rhs = { other.metric, other.mode };
+        return Arrays.equals(lhs, rhs);
+    }
+}
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285Endpoint.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285Endpoint.java
new file mode 100644 (file)
index 0000000..348e089
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.LinkedHashMap;
+
+
+public class RFC7285Endpoint {
+
+    public static class AddressGroup extends Extensible {
+
+        @JsonIgnore
+        public static final String IPV4_LABEL = "ipv4";
+
+        @JsonIgnore
+        public static final String IPV6_LABEL = "ipv6";
+
+        @JsonProperty(IPV4_LABEL)
+        public List<String> ipv4 = new ArrayList<String>();
+
+        @JsonProperty(IPV6_LABEL)
+        public List<String> ipv6 = new ArrayList<String>();
+
+    }
+
+    public static class PropertyRequest {
+
+        @JsonProperty(value="properties")
+        public List<String> properties;
+
+        @JsonProperty(value="endpoints")
+        public List<String> endpoints;
+    }
+
+    public static class PropertyResponse {
+
+        public static class Meta extends Extensible {
+
+            @JsonProperty("dependent-vtags")
+            public List<RFC7285VersionTag> netmap_tags = new ArrayList<RFC7285VersionTag>();
+
+        }
+
+        @JsonProperty("meta")
+        public Meta meta = new Meta();
+
+        @JsonProperty("endpoint-properties")
+        public Map<String, Map<String, Object>> answer
+                            = new LinkedHashMap<String, Map<String, Object>>();
+    }
+
+    public static class CostRequest {
+
+        @JsonProperty("cost-type")
+        public RFC7285CostType costType;
+
+        @JsonProperty("constraints")
+        public List<String> constraints = new ArrayList<String>();
+
+        @JsonProperty("endpoints")
+        public RFC7285QueryPairs endpoints;
+    }
+
+    public static class CostResponse {
+
+        public static class Meta extends Extensible {
+
+            @JsonProperty("cost-type")
+            public RFC7285CostType costType = new RFC7285CostType();
+
+        }
+
+        @JsonProperty("meta")
+        public Meta meta = new Meta();
+
+        @JsonProperty("endpoint-cost-map")
+        public Map<String, Map<String, Object>> answer = null;
+    }
+}
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285EndpointPropertyMap.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285EndpointPropertyMap.java
new file mode 100644 (file)
index 0000000..9fe231b
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RFC7285EndpointPropertyMap {
+
+  public static class Meta extends Extensible {
+    @JsonProperty("dependent-vtags")
+    public List<RFC7285VersionTag> netmap_tags;
+  }
+
+  @JsonProperty("meta")
+  public Meta meta;
+
+  @JsonProperty("endpoint-properties")
+  public Map<String, Map<String, String>> map
+    = new LinkedHashMap<String, Map<String, String>>();
+}
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285IRD.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285IRD.java
new file mode 100644 (file)
index 0000000..ea7885f
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import java.util.List;
+import java.util.Map;
+import java.util.LinkedHashMap;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class RFC7285IRD {
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    public class Meta {
+        @JsonProperty("default-alto-network-map")
+        public String defaultAltoNetworkMap;
+
+        @JsonProperty("cost-types")
+        public Map<String, RFC7285CostType> costTypes = new LinkedHashMap<String, RFC7285CostType>();
+
+    }
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    public class Capability {
+        @JsonProperty("cost-constraints")
+        public Boolean costConstraints;
+
+        @JsonProperty("cost-type-names")
+        public List<String> costTypeNames;
+
+        @JsonProperty("prop-types")
+        public List<String> propTypes;
+    }
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    public class Entry {
+        @JsonProperty("uri")
+        public String uri;
+
+        @JsonProperty("media-type")
+        public String mediaType;
+
+        @JsonProperty("accepts")
+        public String accepts;
+
+        @JsonProperty("capabilities")
+        public Capability capabilities;
+
+        @JsonProperty("uses")
+        public List<String> uses;
+    }
+
+    @JsonProperty("meta")
+    public Meta meta = new Meta();
+
+    @JsonProperty("resources")
+    public Map<String, Entry> resources = new LinkedHashMap<String, Entry>();
+}
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285JSONMapper.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285JSONMapper.java
new file mode 100644 (file)
index 0000000..c3689a5
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.JsonMappingException;
+
+public class RFC7285JSONMapper {
+
+    private ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(Include.NON_DEFAULT)
+            .disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
+
+    public RFC7285Endpoint.AddressGroup asAddressGroup(String json) throws Exception {
+        return mapper.readValue(json, RFC7285Endpoint.AddressGroup.class);
+    }
+
+    public RFC7285Endpoint.PropertyRequest asPropertyRequest(String json) throws Exception {
+        RFC7285Endpoint.PropertyRequest ret = mapper.readValue(json, RFC7285Endpoint.PropertyRequest.class);
+
+        if (ret.properties == null) {
+            throw new JsonMappingException("Missing field:properties");
+        }
+        if (ret.endpoints == null) {
+            throw new JsonMappingException("Missing field:endpoints");
+        }
+        return ret;
+    }
+
+    public RFC7285Endpoint.PropertyResponse asPropertyResponse(String json) throws Exception {
+        return mapper.readValue(json, RFC7285Endpoint.PropertyResponse.class);
+    }
+
+    public RFC7285Endpoint.CostRequest asCostRequest(String json) throws Exception {
+        RFC7285Endpoint.CostRequest ret = mapper.readValue(json, RFC7285Endpoint.CostRequest.class);
+        if (ret.costType == null) {
+            throw new JsonMappingException("Missing field:cost-type");
+        }
+        if (ret.endpoints == null) {
+            throw new JsonMappingException("Missing field:endpoints");
+        }
+        return ret;
+    }
+
+    public RFC7285Endpoint.CostResponse asCostResponse(String json) throws Exception {
+        return mapper.readValue(json, RFC7285Endpoint.CostResponse.class);
+    }
+
+    public RFC7285CostMap asCostMap(String json) throws Exception {
+        return mapper.readValue(json, RFC7285CostMap.class);
+    }
+
+    public List<RFC7285CostMap> asCostMapList(String json) throws Exception {
+        return Arrays.asList(mapper.readValue(json, RFC7285CostMap[].class));
+    }
+
+    public RFC7285CostType asCostType(String json) throws Exception {
+        return mapper.readValue(json, RFC7285CostType.class);
+    }
+
+    public RFC7285Endpoint asEndpoint(String json) throws Exception {
+        return mapper.readValue(json, RFC7285Endpoint.class);
+    }
+
+    public Extensible asExtensible(String json) throws Exception {
+        return mapper.readValue(json, Extensible.class);
+    }
+
+    public RFC7285IRD asIRD(String json) throws Exception {
+        return mapper.readValue(json, RFC7285IRD.class);
+    }
+
+    public RFC7285NetworkMap asNetworkMap(String json) throws Exception {
+        return mapper.readValue(json, RFC7285NetworkMap.class);
+    }
+
+    public List<RFC7285NetworkMap> asNetworkMapList(String json) throws Exception {
+        return Arrays.asList(mapper.readValue(json, RFC7285NetworkMap[].class));
+    }
+
+    public RFC7285NetworkMap.Filter asNetworkMapFilter(String json) throws Exception {
+        RFC7285NetworkMap.Filter ret = mapper.readValue(json, RFC7285NetworkMap.Filter.class);
+        if (ret.pids == null) {
+            throw new JsonMappingException("Missing field:pids");
+        }
+        return ret;
+    }
+
+    public RFC7285CostMap.Filter asCostMapFilter(String json) throws Exception {
+        RFC7285CostMap.Filter ret = mapper.readValue(json, RFC7285CostMap.Filter.class);
+        if (ret.costType == null) {
+            throw new JsonMappingException("Missing field:cost-type");
+        }
+        if (ret.pids == null) {
+            throw new JsonMappingException("Missing field:pids");
+        }
+        return ret;
+    }
+
+    public RFC7285VersionTag asVersionTag(String json) throws Exception {
+        return mapper.readValue(json, RFC7285VersionTag.class);
+    }
+
+    public RFC7285EndpointPropertyMap asEndpointPropMap(String json) throws Exception {
+        return mapper.readValue(json, RFC7285EndpointPropertyMap.class);
+    }
+
+    public String asJSON(Object obj) throws Exception {
+        return mapper.writeValueAsString(obj);
+    }
+}
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285NetworkMap.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285NetworkMap.java
new file mode 100644 (file)
index 0000000..3e111cf
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Network Map: defined in RFC 7285 section 11.2.1
+ * */
+public class RFC7285NetworkMap {
+
+    public static class Meta extends Extensible {
+
+        @JsonProperty("vtag")
+        public RFC7285VersionTag vtag = new RFC7285VersionTag();
+
+    }
+
+    /**
+     * used for filtered-network-map, RFC7285 secion 11.3.1
+     * */
+    public static class Filter {
+
+        @JsonProperty("pids")
+        public List<String> pids;
+        @JsonProperty("address-types")
+        public List<String> addressTypes;
+    }
+
+    @JsonProperty("meta")
+    public Meta meta = new Meta();
+
+    @JsonProperty("network-map")
+    public Map<String, RFC7285Endpoint.AddressGroup> map
+                    = new LinkedHashMap<String, RFC7285Endpoint.AddressGroup>();
+}
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285QueryPairs.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285QueryPairs.java
new file mode 100644 (file)
index 0000000..61b8c44
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import java.util.List;
+import java.util.LinkedList;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RFC7285QueryPairs {
+
+    @JsonProperty("srcs")
+    public List<String> src = new LinkedList<String>();
+
+    @JsonProperty("dsts")
+    public List<String> dst = new LinkedList<String>();
+
+}
diff --git a/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285VersionTag.java b/alto-core/northbound/api/src/main/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/RFC7285VersionTag.java
new file mode 100644 (file)
index 0000000..9f52a7e
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class RFC7285VersionTag {
+
+    @JsonProperty("resource-id")
+    public String rid;
+
+    @JsonProperty("tag")
+    public String tag;
+
+    public RFC7285VersionTag() {
+        rid = "";
+        tag = "";
+    }
+
+    public RFC7285VersionTag(String rid, String tag) {
+        this.rid = (rid != null ? rid : "");
+        this.tag = (tag != null ? tag : "");
+    }
+
+    public boolean incomplete() {
+        return (rid == null) || (tag == null) || (rid == "") || (tag == "");
+    }
+
+    private static char ILLEGAL = '$';
+
+    @Override
+    public int hashCode() {
+        return (rid + ILLEGAL + tag).hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+
+        RFC7285VersionTag other = (RFC7285VersionTag)obj;
+        boolean _rid = (rid == null ? (other.rid == null) : rid.equals(other.rid));
+        boolean _tag = (tag == null ? (other.tag == null) : tag.equals(other.tag));
+        return (_rid && _tag);
+    }
+}
diff --git a/alto-core/northbound/api/src/test/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/TestRFC7285Types.java b/alto-core/northbound/api/src/test/java/org/opendaylight/alto/core/northbound/api/utils/rfc7285/TestRFC7285Types.java
new file mode 100644 (file)
index 0000000..6ec070a
--- /dev/null
@@ -0,0 +1,397 @@
+/*
+ * Copyright (c) 2015 Yale University and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.alto.core.northbound.api.utils.rfc7285;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.ArrayList;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TestRFC7285Types {
+
+    public RFC7285NetworkMap makeNetworkMap() {
+        /*
+         *
+         *      {
+         *          "meta" : {
+         *              "vtag": {
+         *                  "resource-id": "my-default-network-map",
+         *                  "tag": "da65eca2eb7a10ce8b059740b0b2e3f8eb1d4785"
+         *              }
+         *          },
+         *          "network-map": {
+         *              "PID1" : {
+         *                  "ipv4" : [
+         *                      "192.0.2.0/24",
+         *                      "198.51.100.0/25"
+         *                  ]
+         *              },
+         *              "PID2" : {
+         *                  "ipv4" : [
+         *                      "198.51.100.128/25"
+         *                  ]
+         *              },
+         *              "PID3" : {
+         *                  "ipv4" : [
+         *                      "0.0.0.0/0"
+         *                  ],
+         *                  "ipv6" : [
+         *                      "::/0"
+         *                  ]
+         *              }
+         *          }
+         *      }
+         *
+         * */
+        RFC7285NetworkMap nm = new RFC7285NetworkMap();
+        nm.meta.vtag = new RFC7285VersionTag("my-default-network-map",
+                                             "da65eca2eb7a10ce8b059740b0b2e3f8eb1d4785");
+
+        nm.map.put("PID1", new RFC7285Endpoint.AddressGroup());
+        nm.map.get("PID1").ipv4.add("192.0.2.0/24");
+        nm.map.get("PID1").ipv4.add("198.51.100.0/25");
+
+        nm.map.put("PID2", new RFC7285Endpoint.AddressGroup());
+        nm.map.get("PID2").ipv4.add("198.51.100.128/25");
+
+        nm.map.put("PID3", new RFC7285Endpoint.AddressGroup());
+        nm.map.get("PID3").ipv4.add("0.0.0.0/0");
+        nm.map.get("PID3").ipv6.add("::/0");
+
+        return nm;
+    }
+
+    @Test
+    public void test() {
+    }
+
+    public <T> void assertCollectionEquals(Collection<T> lhs, Collection<T> rhs) {
+        Set<T> _lhs = new HashSet<T>(lhs);
+        Set<T> _rhs = new HashSet<T>(rhs);
+        assertEquals(lhs.size(), rhs.size());
+
+        for (T obj: lhs) {
+            assertTrue(_rhs.contains(obj));
+        }
+    }
+
+    @Test
+    public void testNetworkMap() throws Exception {
+        RFC7285JSONMapper mapper = new RFC7285JSONMapper();
+
+        RFC7285NetworkMap nm = makeNetworkMap();
+        String nmText = mapper.asJSON(nm);
+        RFC7285NetworkMap _nm = mapper.asNetworkMap(nmText);
+
+        assertEquals(nm.meta.vtag, _nm.meta.vtag);
+        assertEquals(nm.map.size(), _nm.map.size());
+        assertCollectionEquals(nm.map.get("PID1").ipv4, _nm.map.get("PID1").ipv4);
+        assertCollectionEquals(nm.map.get("PID2").ipv4, _nm.map.get("PID2").ipv4);
+        assertCollectionEquals(nm.map.get("PID3").ipv4, _nm.map.get("PID3").ipv4);
+        assertCollectionEquals(nm.map.get("PID3").ipv6, _nm.map.get("PID3").ipv6);
+
+        String addrGroupString = mapper.asJSON(nm.map.get("PID3"));
+        RFC7285Endpoint.AddressGroup _ag = mapper.asAddressGroup(addrGroupString);
+        assertCollectionEquals(nm.map.get("PID3").ipv4, _ag.ipv4);
+        assertCollectionEquals(nm.map.get("PID3").ipv6, _ag.ipv6);
+    }
+
+    @Test
+    public void testNetworkMapFilter() throws Exception {
+        /*
+         *
+         *      {
+         *          "pids": [ "PID1", "PID2" ]
+         *      }
+         * */
+
+        RFC7285JSONMapper mapper = new RFC7285JSONMapper();
+
+        RFC7285NetworkMap.Filter filter = new RFC7285NetworkMap.Filter();
+        filter.pids = new ArrayList<String>();
+        filter.pids.add("PID1");
+        filter.pids.add("PID2");
+
+        String nmfString = mapper.asJSON(filter);
+        RFC7285NetworkMap.Filter _filter = mapper.asNetworkMapFilter(nmfString);
+        assertCollectionEquals(filter.pids, _filter.pids);
+    }
+
+    public RFC7285CostMap makeCostMap() {
+        /*
+         *  {
+         *      "meta": {
+         *          "dependent-vtags" : [
+         *              {
+         *                  "resource-id": "my-default-network-map",
+         *                  "tag": "3ee2cb7e8d63d9fab71b9b34cbf764436315542e"
+         *              }
+         *          ],
+         *          "cost-type" : {
+         *              "cost-mode": "numerical",
+         *              "cost-metric": "routingcost"
+         *          }
+         *      },
+         *      "cost-map" : {
+         *          "PID1": { "PID1": 1,  "PID2": 5,  "PID3": 10 },
+         *          "PID2": { "PID1": 5,  "PID2": 1,  "PID3": 15 },
+         *          "PID3": { "PID1": 20, "PID2": 15  }
+         *      }
+         *  }
+         * */
+
+        RFC7285CostMap cm = new RFC7285CostMap();
+        cm.meta.costType = new RFC7285CostType("numerical", "routingcost");
+        cm.meta.netmap_tags.add(new RFC7285VersionTag("my-default-network-map",
+                                                      "3ee2cb7e8d63d9fab71b9b34cbf764436315542e"));
+        cm.map.put("PID1", new LinkedHashMap<String, Object>());
+        cm.map.get("PID1").put("PID1", new Integer(1));
+        cm.map.get("PID1").put("PID2", new Integer(5));
+        cm.map.get("PID1").put("PID3", new Integer(10));
+        cm.map.put("PID2", new LinkedHashMap<String, Object>());
+        cm.map.get("PID2").put("PID1", new Integer(5));
+        cm.map.get("PID2").put("PID2", new Integer(1));
+        cm.map.get("PID2").put("PID3", new Integer(15));
+        cm.map.put("PID3", new LinkedHashMap<String, Object>());
+        cm.map.get("PID3").put("PID1", new Integer(20));
+        cm.map.get("PID3").put("PID2", new Integer(15));
+
+        return cm;
+    }
+
+    @Test
+    public void testCostMap() throws Exception {
+        RFC7285JSONMapper mapper = new RFC7285JSONMapper();
+
+        RFC7285CostMap cm = makeCostMap();
+
+        String cmString = mapper.asJSON(cm);
+        RFC7285CostMap _cm = mapper.asCostMap(cmString);
+
+        assertCollectionEquals(cm.meta.netmap_tags, _cm.meta.netmap_tags);
+        assertEquals(cm.meta.costType, _cm.meta.costType);
+
+        String pids[] = { "PID1", "PID2", "PID3" };
+        for (String pid: pids) {
+            assertCollectionEquals(cm.map.get(pid).entrySet(), _cm.map.get(pid).entrySet());
+        }
+    }
+
+    @Test
+    public void testCostMapFilter() throws Exception {
+        /*
+         *  {
+         *      "cost-type" : {
+         *          "cost-mode": "numerical",
+         *          "cost-metric": "routingcost"
+         *      },
+         *      "pids" : {
+         *          "srcs" : [ "PID1" ],
+         *          "dsts" : [ "PID1", "PID2", "PID3" ]
+         *      }
+         *  }
+         * */
+
+        RFC7285JSONMapper mapper = new RFC7285JSONMapper();
+
+        RFC7285CostMap.Filter filter = new RFC7285CostMap.Filter();
+        filter.costType = new RFC7285CostType("numerical", "routingcost", "test");
+        filter.pids = new RFC7285QueryPairs();
+        filter.pids.src.add("PID1");
+        filter.pids.dst.add("PID1");
+        filter.pids.dst.add("PID2");
+        filter.pids.dst.add("PID3");
+
+        String cmfString = mapper.asJSON(filter);
+        RFC7285CostMap.Filter _filter = mapper.asCostMapFilter(cmfString);
+
+        assertEquals(filter.costType, _filter.costType);
+        assertCollectionEquals(filter.pids.src, _filter.pids.src);
+        assertCollectionEquals(filter.pids.dst, _filter.pids.dst);
+    }
+
+    @Test
+    public void testECSRequest() throws Exception {
+        /*
+         *  {
+         *      "cost-type" : {
+         *          "cost-mode": "ordinal",
+         *          "cost-metric": "routingcost"
+         *      },
+         *      "endpoints": {
+         *          "srcs": [ "ipv4:192.0.2.2" ],
+         *          "dsts": [
+         *              "ipv4:192.0.2.89",
+         *              "ipv4:198.51.100.34",
+         *              "ipv4:203.0.113.45"
+         *          ]
+         *      }
+         *  }
+         * */
+
+        RFC7285JSONMapper mapper = new RFC7285JSONMapper();
+
+        RFC7285Endpoint.CostRequest req = new RFC7285Endpoint.CostRequest();
+        req.costType = new RFC7285CostType("ordinal", "routingcost", "test");
+        req.endpoints = new RFC7285QueryPairs();
+        req.endpoints.src.add("ipv4:192.0.2.2");
+        req.endpoints.dst.add("ipv4:192.0.2.89");
+        req.endpoints.dst.add("ipv4:198.51.100.34");
+        req.endpoints.dst.add("ipv4:203.0.113.45");
+
+        String ecsrString = mapper.asJSON(req);
+        RFC7285Endpoint.CostRequest _req = mapper.asCostRequest(ecsrString);
+
+        assertEquals(req.costType, _req.costType);
+        assertCollectionEquals(req.endpoints.src, _req.endpoints.src);
+        assertCollectionEquals(req.endpoints.dst, _req.endpoints.dst);
+    }
+
+    public RFC7285Endpoint.CostResponse makeECSResponse() {
+        /*
+         *  {
+         *      "meta": {
+         *          "cost-type" : {
+         *              "cost-mode": "ordinal",
+         *              "cost-metric": "routingcost"
+         *          }
+         *      },
+         *      "endpoint-cost-map" : {
+         *          "ipv4:192.0.2.2": {
+         *              "ipv4:192.0.2.89": 1,
+         *              "ipv4:198.51.100.34": 2,
+         *              "ipv4:203.0.113.45": 3
+         *          },
+         *      }
+         *  }
+         * */
+
+        String src[] = { "ipv4:192.0.2.2" };
+        String dst[] = { "ipv4:192.0.2.89", "ipv4:198.51.100.34", "ipv4:203.0.113.45" };
+
+        RFC7285Endpoint.CostResponse ecsr = new RFC7285Endpoint.CostResponse();
+        ecsr.meta.costType = new RFC7285CostType("ordinal", "routingcost");
+        ecsr.answer = new LinkedHashMap<String, Map<String, Object>>();
+        ecsr.answer.put(src[0], new LinkedHashMap<String, Object>());
+        ecsr.answer.get(src[0]).put(dst[0], new Integer(1));
+        ecsr.answer.get(src[0]).put(dst[1], new Integer(2));
+        ecsr.answer.get(src[0]).put(dst[2], new Integer(3));
+
+        return ecsr;
+    }
+
+    @Test
+    public void testECSAnswer() throws Exception {
+        RFC7285JSONMapper mapper = new RFC7285JSONMapper();
+
+        RFC7285Endpoint.CostResponse ecsr = makeECSResponse();
+
+        String ecsrString = mapper.asJSON(ecsr);
+        RFC7285Endpoint.CostResponse _ecsr = mapper.asCostResponse(ecsrString);
+
+        assertEquals(ecsr.meta.costType, _ecsr.meta.costType);
+
+        String endpoints[] = { "ipv4:192.0.2.2" };
+        for (String endpoint: endpoints) {
+            assertCollectionEquals(ecsr.answer.get(endpoint).entrySet(),
+                                   _ecsr.answer.get(endpoint).entrySet());
+        }
+    }
+
+    @Test
+    public void testEPSRequest() throws Exception {
+        /*
+         *  {
+         *      "properties" : [
+         *          "my-default-networkmap.pid",
+         *          "priv:ietf-example-prop"
+         *      ],
+         *      "endpoints"  : [
+         *          "ipv4:192.0.2.34",
+         *          "ipv4:203.0.113.129"
+         *      ]
+         *  }
+        */
+        RFC7285JSONMapper mapper = new RFC7285JSONMapper();
+
+        RFC7285Endpoint.PropertyRequest req = new RFC7285Endpoint.PropertyRequest();
+        if (req.properties == null)
+            req.properties = new ArrayList<String>();
+        req.properties.add("my-default-networkmap.pid");
+        req.properties.add("priv:ietf-example-prop");
+
+        if (req.endpoints == null)
+            req.endpoints = new ArrayList<String>();
+
+        req.endpoints.add("ipv4:192.0.2.34");
+        req.endpoints.add("ipv4:203.0.113.129");
+
+        String epsrString = mapper.asJSON(req);
+        RFC7285Endpoint.PropertyRequest _req = mapper.asPropertyRequest(epsrString);
+
+        assertCollectionEquals(req.properties, _req.properties);
+        assertCollectionEquals(req.endpoints, _req.endpoints);
+    }
+
+    @Test
+    public void testEPSResponse() throws Exception {
+        /*
+         *  {
+         *      "meta" : {
+         *          "dependent-vtags" : [
+         *              {
+         *                  "resource-id": "my-default-network-map",
+         *                  "tag": "7915dc0290c2705481c491a2b4ffbec482b3cf62"
+         *              }
+         *          ]
+         *      },
+         *      "endpoint-properties": {
+         *          "ipv4:192.0.2.34": {
+         *              "my-default-network-map.pid": "PID1",
+         *              "priv:ietf-example-prop": "1"
+         *          },
+         *          "ipv4:203.0.113.129": {
+         *              "my-default-network-map.pid": "PID3"
+         *          }
+         *      }
+         *  }
+         * */
+        RFC7285JSONMapper mapper = new RFC7285JSONMapper();
+
+        RFC7285Endpoint.PropertyResponse res = new RFC7285Endpoint.PropertyResponse();
+        RFC7285VersionTag vtag = new RFC7285VersionTag("my-default-network-map",
+                                                       "7915dc0290c2705481c491a2b4ffbec482b3cf62");
+        res.meta.netmap_tags.add(vtag);
+
+        String endpoints[] = { "ipv4:192.0.2.34", "ipv4:203.0.113.129" };
+        String properties[] = { "my-default-network-map", "priv:itef-example-prop" };
+        for (String endpoint: endpoints) {
+            res.answer.put(endpoint, new LinkedHashMap<String, Object>());
+        }
+        res.answer.get(endpoints[0]).put(properties[0], "PID1");
+        res.answer.get(endpoints[0]).put(properties[1], new Integer(1));
+        res.answer.get(endpoints[1]).put(properties[0], "PID3");
+
+        String epsrString = mapper.asJSON(res);
+        RFC7285Endpoint.PropertyResponse _res = mapper.asPropertyResponse(epsrString);
+
+        assertCollectionEquals(res.meta.netmap_tags, _res.meta.netmap_tags);
+        for (String endpoint: endpoints) {
+            assertCollectionEquals(res.answer.get(endpoint).entrySet(),
+                                   _res.answer.get(endpoint).entrySet());
+        }
+    }
+}