From e3aea30710e3ad3788b78a80f2a507603a2fe8d0 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Tue, 5 Feb 2019 14:58:14 +0100 Subject: [PATCH] #647: ensure our serializable classes keep serializable. --- .../leshan/server/SerializationTests.java | 56 ++++++++ .../leshan/server/SerializationUtil.java | 129 ++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationTests.java create mode 100644 leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationUtil.java 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 0000000000..4dcfce6315 --- /dev/null +++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/SerializationTests.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * 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() { + assertIsSerializable(Registration.class); + } + + @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 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 0000000000..bd96e8587d --- /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 isSerializable(Class clazz, String... excludes) { + Map notSerializableObject = new LinkedHashMap<>(); + isSerializable(clazz, notSerializableObject, Arrays.asList(excludes)); + return notSerializableObject; + } + + private static boolean isSerializable(Type type, Map notSerializableObject, + Collection excludes) { + + // check generic type : e.g List. + 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 notSerializableObject, + Collection 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