Repository: eclipse/leshan
Pull request #649 (2019-02-05 15:33:23)

Make registration "serializable" again ...
... and add a way to ensure it stay `Serializable`.


Patch:
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/ServerIdentity.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/ServerIdentity.java
index 566b9fba..799d8237 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/ServerIdentity.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/request/ServerIdentity.java
@@ -29,6 +29,8 @@ import org.eclipse.leshan.core.request.Identity;
  */
 public class ServerIdentity extends Identity {
 
+    private static final long serialVersionUID = 1L;
+
     enum Role {
         /**
          * Indicate internal call. Enables the "system" to read protected resources (e.g. resources of the security
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/Link.java b/leshan-core/src/main/java/org/eclipse/leshan/Link.java
index 5b420793..26e74aa8 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/Link.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/Link.java
@@ -34,6 +34,8 @@ public class Link implements Serializable {
 
     private final String url;
 
+    // TODO a map from String to Object could be a problem with serialization.
+    // TODO we are not able to make difference between string and quoted string.
     private final Map<String, Object> attributes;
 
     /**
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java
index e0d75053..1bbb99ad 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/Identity.java
@@ -16,6 +16,7 @@
  *******************************************************************************/
 package org.eclipse.leshan.core.request;
 
+import java.io.Serializable;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.security.PublicKey;
@@ -25,7 +26,9 @@ import org.eclipse.leshan.util.Validate;
 /**
  * A request sender identity.
  */
-public class Identity {
+public class Identity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
 
     private final InetSocketAddress peerAddress;
     private final String pskIdentity;


Test patch:
diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationTests.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationTests.java
new file mode 100644
index 00000000..1fb62313
--- /dev/null
+++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationTests.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.server;
+
+import static org.junit.Assert.fail;
+
+import java.util.Map;
+
+import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
+import org.eclipse.leshan.server.registration.Registration;
+import org.eclipse.leshan.server.security.SecurityInfo;
+import org.junit.Test;
+
+public class SerializationTests {
+
+    @Test
+    public void ensure_Registration_is_serializable() {
+        // TODO we excludes "org.eclipse.leshan.Link.attributes" because we can not be sure it is serializable in a
+        // reliable way.
+        assertIsSerializable(Registration.class, "org.eclipse.leshan.Link.attributes");
+    }
+
+    @Test
+    public void ensure_SecurityInfo_is_serializable() {
+        assertIsSerializable(SecurityInfo.class);
+    }
+
+    @Test
+    public void ensure_BootstrapConfig_is_serializable() {
+        assertIsSerializable(BootstrapConfig.class);
+    }
+
+    private static void assertIsSerializable(Class<?> clazz, String... excludes) {
+        Map<Object, String> results = SerializationUtil.isSerializable(clazz, excludes);
+
+        if (!results.isEmpty()) {
+            StringBuilder issues = new StringBuilder();
+            for (String issue : results.values()) {
+                issues.append("\n");
+                issues.append(issue);
+            }
+            fail(issues.toString());
+        }
+    }
+}
diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationUtil.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationUtil.java
new file mode 100644
index 00000000..bd96e858
--- /dev/null
+++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationUtil.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.server;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class SerializationUtil {
+
+    /**
+     * Check is a class is serializable using default java serialization mechanism.
+     * 
+     * serializable means thats the class implements {@link Serializable} and all the fields of this class are
+     * serializable too.
+     * 
+     * For parameterized field we only support {@link collection} and Map {@link Map}
+     * 
+     * @param class to check
+     * @param excludes field or class to exclude of checks
+     * @return null if class is serializable or a map of errors (unserializable class or field => error message).
+     */
+    public static Map<Object, String> isSerializable(Class<?> clazz, String... excludes) {
+        Map<Object, String> notSerializableObject = new LinkedHashMap<>();
+        isSerializable(clazz, notSerializableObject, Arrays.asList(excludes));
+        return notSerializableObject;
+    }
+
+    private static boolean isSerializable(Type type, Map<Object, String> notSerializableObject,
+            Collection<String> excludes) {
+
+        // check generic type : e.g List<String>.
+        if (type instanceof ParameterizedType) {
+            ParameterizedType ptype = (ParameterizedType) type;
+            Class<?> clazz = (Class<?>) ptype.getRawType();
+            if (excludes.contains(clazz.getName())) {
+                return true;
+            }
+            // we support only collection and map and we consider that checking only if parameterized type is
+            // serializable is enough.
+            if (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz)) {
+                for (Type t : ptype.getActualTypeArguments()) {
+                    if (!isSerializable(t, notSerializableObject, excludes)) {
+                        notSerializableObject.put(clazz, String
+                                .format("[%s] is parameterized with not serializable type [%s].", clazz.getName(), t));
+                        return false;
+                    }
+                }
+                return true;
+            } else {
+                notSerializableObject.put(clazz, String.format(
+                        "[%s] is maybe serializable but we only support Collection and Map as parameterized class.",
+                        clazz));
+                return false;
+            }
+        }
+        // check classic class
+        else if (type instanceof Class<?>) {
+            Class<?> clazz = (Class<?>) type;
+            // do not check type excluded
+            if (excludes.contains(clazz.getName())) {
+                return true;
+            }
+            if (clazz.isPrimitive()) {
+                return true;
+            }
+            if (clazz.isArray()) {
+                return isSerializable(clazz.getComponentType(), notSerializableObject, excludes);
+            } else if (Serializable.class.isAssignableFrom(clazz)) {
+                boolean isSerializable = true;
+                for (Field field : clazz.getDeclaredFields()) {
+                    if (!isSerializable(field, notSerializableObject, excludes)) {
+                        notSerializableObject.put(field, String.format("[%s %s] field from [%s] is not serializable",
+                                field.getType().getSimpleName(), field.getName(), field.getDeclaringClass().getName()));
+                        isSerializable = false;
+                    }
+                }
+                return isSerializable;
+            } else {
+                notSerializableObject.put(clazz,
+                        String.format("[%s] is not primitive or does not implement Serializable.", clazz.getName()));
+                return false;
+            }
+        } else {
+            if (excludes.contains(type.toString())) {
+                return true;
+            }
+            notSerializableObject.put(type, String.format("[%s] is maybe serializable but we don't support it.", type));
+            return false;
+        }
+    }
+
+    private static boolean isSerializable(Field field, Map<Object, String> notSerializableObject,
+            Collection<String> excludes) {
+
+        if (excludes.contains(field.getDeclaringClass().getName() + "." + field.getName())) {
+            return true;
+        }
+
+        // we ignore static and transient field
+        if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
+            return true;
+        }
+
+        if (isSerializable(field.getGenericType(), notSerializableObject, excludes)) {
+            return true;
+        }
+        return false;
+    }
+}
\ No newline at end of file