Repository: eclipse/leshan
Pull request #1007 (2021-05-05 17:06:15)

Refactoring in Registration class
Some refactoring needed to prepare the support of "ct=" attribute at server Side.

Notable behavior breaks : 
 - equals/hashcode
 - Version used instead of String in Registration
 - Registration is no more Serializable
 - Registration Constructor takes the builder in argument instead of a too long list of arguments. 
 
 About the last point, we have many constructors with too many constructor argument in the Leshan code base  maybe we should generalized this approach ? :thinking: 


Patch:
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/Link.java b/leshan-core/src/main/java/org/eclipse/leshan/core/Link.java
index eefc9f98..5076886f 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/Link.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/Link.java
@@ -15,7 +15,6 @@
  *******************************************************************************/
 package org.eclipse.leshan.core;
 
-import java.io.Serializable;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
@@ -41,9 +40,7 @@ import org.eclipse.leshan.core.util.Validate;
 // https://github.com/google/coapblaster/blob/master/src/main/java/com/google/iot/coap/LinkFormat.java
 // https://github.com/eclipse/californium/blob/2.0.x/californium-core/src/main/java/org/eclipse/californium/core/coap/LinkFormat.java
 // https://github.com/ARMmbed/java-coap/blob/master/coap-core/src/main/java/com/mbed/coap/linkformat/LinkFormat.java
-public class Link implements Serializable {
-
-    private static final long serialVersionUID = 1L;
+public class Link {
 
     private final String url;
 
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java
index 3ad78c12..136b2f47 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java
@@ -21,6 +21,7 @@ public interface LwM2m {
      * Version of LWM2M specification.
      */
     public class Version implements Comparable<Version> {
+
         public static Version V1_0 = new Version("1.0", true);
         public static Version V1_1 = new Version("1.1", true);
         private static Version[] supportedVersions = new Version[] { V1_0, V1_1 };
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 601cc34a..86b9acd3 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,7 +16,6 @@
  *******************************************************************************/
 package org.eclipse.leshan.core.request;
 
-import java.io.Serializable;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.security.PublicKey;
@@ -27,9 +26,7 @@ import org.eclipse.leshan.core.util.Validate;
  * Contains all data which could identify a peer like peer address, PSK identity, Raw Public Key or Certificate Common
  * Name.
  */
-public class Identity implements Serializable {
-
-    private static final long serialVersionUID = 1L;
+public class Identity {
 
     private final InetSocketAddress peerAddress;
     private final String pskIdentity;
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java
index 1813f54c..50e7da83 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java
@@ -108,9 +108,9 @@ public class UpdateRequest extends AbstractLwM2mRequest<UpdateResponse> implemen
         return additionalAttributes;
     }
 
-    public void validate(String targetedVersion) {
+    public void validate(Version targetedVersion) {
         if (bindingMode != null) {
-            String err = BindingMode.isValidFor(bindingMode, Version.get(targetedVersion));
+            String err = BindingMode.isValidFor(bindingMode, targetedVersion);
             if (err != null) {
                 throw new InvalidRequestException("Invalid Binding mode: %s", err);
             }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java
index c172a7c0..86b2953b 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java
@@ -16,7 +16,6 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.registration;
 
-import java.io.Serializable;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.util.Arrays;
@@ -42,9 +41,7 @@ import org.eclipse.leshan.core.util.Validate;
 /**
  * An immutable structure which represent a LW-M2M client registration on the server
  */
-public class Registration implements Serializable {
-
-    private static final long serialVersionUID = 1L;
+public class Registration {
 
     private static final long DEFAULT_LIFETIME_IN_SEC = 86400L;
 
@@ -56,15 +53,13 @@ public class Registration implements Serializable {
 
     private final String smsNumber;
 
-    private final String lwM2mVersion;
+    private final Version lwM2mVersion;
 
     private final EnumSet<BindingMode> bindingMode;
 
     private final Boolean queueMode; // since LWM2M 1.1
 
-    /**
-     * The LWM2M Client's unique end point name.
-     */
+    // The LWM2M Client's unique end point name.
     private final String endpoint;
 
     private final String id;
@@ -77,54 +72,36 @@ public class Registration implements Serializable {
 
     private final Map<String, String> additionalRegistrationAttributes;
 
-    /** The location where LWM2M objects are hosted on the device */
+    // The location where LWM2M objects are hosted on the device
     private final String rootPath;
 
     private final Date lastUpdate;
 
-    protected Registration(String id, String endpoint, Identity identity, String lwM2mVersion, Long lifetimeInSec,
-            String smsNumber, EnumSet<BindingMode> bindingMode, Boolean queueMode, Link[] objectLinks,
-            Date registrationDate, Date lastUpdate, Map<String, String> additionalRegistrationAttributes,
-            Map<Integer, String> supportedObjects) {
-
-        Validate.notNull(id);
-        Validate.notEmpty(endpoint);
-        Validate.notNull(identity);
-
-        this.id = id;
-        this.identity = identity;
-        this.endpoint = endpoint;
-        this.smsNumber = smsNumber;
-
-        this.objectLinks = objectLinks;
-        // Parse object link to extract root path.
-        String rootPath = "/";
-        if (objectLinks != null) {
-            for (Link link : objectLinks) {
-                if (link != null && "oma.lwm2m".equals(Link.unquote(link.getAttributes().get("rt")))) {
-                    rootPath = link.getUrl();
-                    break;
-                }
-            }
-        }
-        if (!rootPath.endsWith("/"))
-            rootPath = rootPath + "/";
-        this.rootPath = rootPath;
-        this.supportedObjects = new AtomicReference<Map<Integer, String>>(supportedObjects);
-        this.lifeTimeInSec = lifetimeInSec == null ? DEFAULT_LIFETIME_IN_SEC : lifetimeInSec;
-        this.lwM2mVersion = lwM2mVersion == null ? Version.getDefault().toString() : lwM2mVersion;
-        this.bindingMode = bindingMode == null ? EnumSet.of(BindingMode.U) : bindingMode;
-        this.queueMode = queueMode == null && Version.get(this.lwM2mVersion).newerThan(Version.V1_0) ? Boolean.FALSE
-                : queueMode;
-        this.registrationDate = registrationDate == null ? new Date() : registrationDate;
-        this.lastUpdate = lastUpdate == null ? new Date() : lastUpdate;
-        if (additionalRegistrationAttributes == null || additionalRegistrationAttributes.isEmpty()) {
-            this.additionalRegistrationAttributes = Collections.emptyMap();
-        } else {
-            // We create a new HashMap to have a real immutable map and to avoid "unmodifiableMap" encapsulation.
-            this.additionalRegistrationAttributes = Collections
-                    .unmodifiableMap(new HashMap<>(additionalRegistrationAttributes));
-        }
+    protected Registration(Builder builder) {
+
+        Validate.notNull(builder.registrationId);
+        Validate.notEmpty(builder.endpoint);
+        Validate.notNull(builder.identity);
+
+        // mandatory params
+        id = builder.registrationId;
+        identity = builder.identity;
+        endpoint = builder.endpoint;
+
+        // object links related params
+        objectLinks = builder.objectLinks;
+        rootPath = builder.rootPath;
+        supportedObjects = new AtomicReference<Map<Integer, String>>(builder.supportedObjects);
+
+        // other params
+        lifeTimeInSec = builder.lifeTimeInSec;
+        lwM2mVersion = builder.lwM2mVersion;
+        bindingMode = builder.bindingMode;
+        queueMode = builder.queueMode;
+        registrationDate = builder.registrationDate;
+        lastUpdate = builder.lastUpdate;
+        smsNumber = builder.smsNumber;
+        additionalRegistrationAttributes = builder.additionalRegistrationAttributes;
 
     }
 
@@ -235,7 +212,7 @@ public class Registration implements Serializable {
         return smsNumber;
     }
 
-    public String getLwM2mVersion() {
+    public Version getLwM2mVersion() {
         return lwM2mVersion;
     }
 
@@ -306,7 +283,7 @@ public class Registration implements Serializable {
     }
 
     public boolean usesQueueMode() {
-        if (Version.get(lwM2mVersion).olderThan(Version.V1_1))
+        if (lwM2mVersion.olderThan(Version.V1_1))
             return bindingMode.contains(BindingMode.Q);
         else
             return queueMode;
@@ -341,30 +318,96 @@ public class Registration implements Serializable {
                 Arrays.toString(objectLinks), lastUpdate);
     }
 
-    /**
-     * Computes a hash code for this client.
-     * 
-     * @return the hash code based on the <em>endpoint</em> property
-     */
     @Override
     public int hashCode() {
-        return getEndpoint().hashCode();
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((additionalRegistrationAttributes == null) ? 0 : additionalRegistrationAttributes.hashCode());
+        result = prime * result + ((bindingMode == null) ? 0 : bindingMode.hashCode());
+        result = prime * result + ((endpoint == null) ? 0 : endpoint.hashCode());
+        result = prime * result + ((id == null) ? 0 : id.hashCode());
+        result = prime * result + ((identity == null) ? 0 : identity.hashCode());
+        result = prime * result + ((lastUpdate == null) ? 0 : lastUpdate.hashCode());
+        result = prime * result + (int) (lifeTimeInSec ^ (lifeTimeInSec >>> 32));
+        result = prime * result + ((lwM2mVersion == null) ? 0 : lwM2mVersion.hashCode());
+        result = prime * result + Arrays.hashCode(objectLinks);
+        result = prime * result + ((queueMode == null) ? 0 : queueMode.hashCode());
+        result = prime * result + ((registrationDate == null) ? 0 : registrationDate.hashCode());
+        result = prime * result + ((rootPath == null) ? 0 : rootPath.hashCode());
+        result = prime * result + ((smsNumber == null) ? 0 : smsNumber.hashCode());
+        return result;
     }
 
-    /**
-     * Compares this Client to another object.
-     * 
-     * @return <code>true</code> if the other object is a Client instance and its <em>endpoint</em> property has the
-     *         same value as this Client
-     */
     @Override
     public boolean equals(Object obj) {
-        if (obj instanceof Registration) {
-            Registration other = (Registration) obj;
-            return this.getEndpoint().equals(other.getEndpoint());
-        } else {
+        if (this == obj)
+            return true;
+        if (obj == null)
             return false;
-        }
+        if (getClass() != obj.getClass())
+            return false;
+        Registration other = (Registration) obj;
+        if (additionalRegistrationAttributes == null) {
+            if (other.additionalRegistrationAttributes != null)
+                return false;
+        } else if (!additionalRegistrationAttributes.equals(other.additionalRegistrationAttributes))
+            return false;
+        if (bindingMode == null) {
+            if (other.bindingMode != null)
+                return false;
+        } else if (!bindingMode.equals(other.bindingMode))
+            return false;
+        if (endpoint == null) {
+            if (other.endpoint != null)
+                return false;
+        } else if (!endpoint.equals(other.endpoint))
+            return false;
+        if (id == null) {
+            if (other.id != null)
+                return false;
+        } else if (!id.equals(other.id))
+            return false;
+        if (identity == null) {
+            if (other.identity != null)
+                return false;
+        } else if (!identity.equals(other.identity))
+            return false;
+        if (lastUpdate == null) {
+            if (other.lastUpdate != null)
+                return false;
+        } else if (!lastUpdate.equals(other.lastUpdate))
+            return false;
+        if (lifeTimeInSec != other.lifeTimeInSec)
+            return false;
+        if (lwM2mVersion == null) {
+            if (other.lwM2mVersion != null)
+                return false;
+        } else if (!lwM2mVersion.equals(other.lwM2mVersion))
+            return false;
+        if (!Arrays.equals(objectLinks, other.objectLinks))
+            return false;
+        if (queueMode == null) {
+            if (other.queueMode != null)
+                return false;
+        } else if (!queueMode.equals(other.queueMode))
+            return false;
+        if (registrationDate == null) {
+            if (other.registrationDate != null)
+                return false;
+        } else if (!registrationDate.equals(other.registrationDate))
+            return false;
+        if (rootPath == null) {
+            if (other.rootPath != null)
+                return false;
+        } else if (!rootPath.equals(other.rootPath))
+            return false;
+        if (smsNumber == null) {
+            if (other.smsNumber != null)
+                return false;
+        } else if (!smsNumber.equals(other.smsNumber))
+            return false;
+        return true;
     }
 
     /**
@@ -424,11 +467,15 @@ public class Registration implements Serializable {
         private String smsNumber;
         private EnumSet<BindingMode> bindingMode;
         private Boolean queueMode;
-        private String lwM2mVersion;
+        private Version lwM2mVersion;
         private Link[] objectLinks;
+        private String rootPath;
         private Map<Integer, String> supportedObjects;
         private Map<String, String> additionalRegistrationAttributes;
 
+        // builder setting
+        private boolean extractData; // if true extract data from objectLinks
+
         public Builder(String registrationId, String endpoint, Identity identity) {
 
             Validate.notNull(registrationId);
@@ -439,6 +486,11 @@ public class Registration implements Serializable {
             this.identity = identity;
         }
 
+        public Builder extractDataFromObjectLink(boolean extract) {
+            this.extractData = extract;
+            return this;
+        }
+
         public Builder registrationDate(Date registrationDate) {
             this.registrationDate = registrationDate;
             return this;
@@ -469,7 +521,7 @@ public class Registration implements Serializable {
             return this;
         }
 
-        public Builder lwM2mVersion(String lwM2mVersion) {
+        public Builder lwM2mVersion(Version lwM2mVersion) {
             this.lwM2mVersion = lwM2mVersion;
             return this;
         }
@@ -479,8 +531,8 @@ public class Registration implements Serializable {
             return this;
         }
 
-        public Builder supportedObjects(Map<Integer, String> supportedObjects) {
-            this.supportedObjects = Collections.unmodifiableMap(supportedObjects);
+        public Builder rootPath(String rootPath) {
+            this.rootPath = rootPath;
             return this;
         }
 
@@ -489,12 +541,54 @@ public class Registration implements Serializable {
             return this;
         }
 
-        public Registration build() {
-            return new Registration(Builder.this.registrationId, Builder.this.endpoint, Builder.this.identity,
-                    Builder.this.lwM2mVersion, Builder.this.lifeTimeInSec, Builder.this.smsNumber, this.bindingMode,
-                    this.queueMode, this.objectLinks, this.registrationDate, this.lastUpdate,
-                    this.additionalRegistrationAttributes, this.supportedObjects);
+        private void extractDataFromObjectLinks() {
+            if (objectLinks != null) {
+                // Define default RootPath;
+                rootPath = "/";
+
+                // Parse object link to extract root path
+                for (Link link : objectLinks) {
+                    if (link != null && "oma.lwm2m".equals(Link.unquote(link.getAttributes().get("rt")))) {
+                        rootPath = link.getUrl();
+                        if (!rootPath.endsWith("/")) {
+                            rootPath = rootPath + "/";
+                        }
+                        break;
+                    }
+                }
+
+                // TODO extract supported Content format
+                // TODO extract object supported
+                // TODO extract available instances
+            }
         }
 
+        public Registration build() {
+            // Define Default value
+            rootPath = rootPath == null ? "/" : rootPath;
+            lifeTimeInSec = lifeTimeInSec == null ? DEFAULT_LIFETIME_IN_SEC : lifeTimeInSec;
+            lwM2mVersion = lwM2mVersion == null ? Version.getDefault() : lwM2mVersion;
+            bindingMode = bindingMode == null ? EnumSet.of(BindingMode.U) : bindingMode;
+            queueMode = queueMode == null && lwM2mVersion.newerThan(Version.V1_0) ? Boolean.FALSE : queueMode;
+            registrationDate = registrationDate == null ? new Date() : registrationDate;
+            lastUpdate = lastUpdate == null ? new Date() : lastUpdate;
+
+            // Make collection immutable
+            if (additionalRegistrationAttributes == null || additionalRegistrationAttributes.isEmpty()) {
+                additionalRegistrationAttributes = Collections.emptyMap();
+            } else {
+                // We create a new HashMap to have a real immutable map and to avoid "unmodifiableMap" encapsulation.
+                additionalRegistrationAttributes = Collections
+                        .unmodifiableMap(new HashMap<>(additionalRegistrationAttributes));
+            }
+
+            // Extract data from object links if wanted
+            if (extractData) {
+                extractDataFromObjectLinks();
+            }
+
+            // Create Registration
+            return new Registration(this);
+        }
     }
 }
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java
index 25393653..edbdfc30 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java
@@ -19,6 +19,7 @@ package org.eclipse.leshan.server.registration;
 
 import java.util.Date;
 
+import org.eclipse.leshan.core.LwM2m.Version;
 import org.eclipse.leshan.core.request.DeregisterRequest;
 import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.request.RegisterRequest;
@@ -54,8 +55,9 @@ public class RegistrationHandler {
 
         Registration.Builder builder = new Registration.Builder(
                 registrationIdProvider.getRegistrationId(registerRequest), registerRequest.getEndpointName(), sender);
+        builder.extractDataFromObjectLink(true);
 
-        builder.lwM2mVersion(registerRequest.getLwVersion()).lifeTimeInSec(registerRequest.getLifetime())
+        builder.lwM2mVersion(Version.get(registerRequest.getLwVersion())).lifeTimeInSec(registerRequest.getLifetime())
                 .bindingMode(registerRequest.getBindingMode()).queueMode(registerRequest.getQueueMode())
                 .objectLinks(registerRequest.getObjectLinks()).smsNumber(registerRequest.getSmsNumber())
                 .registrationDate(new Date()).lastUpdate(new Date())
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java
index 431b45c5..2a5c7d9d 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationUpdate.java
@@ -82,11 +82,12 @@ public class RegistrationUpdate {
 
         Registration.Builder builder = new Registration.Builder(registration.getId(), registration.getEndpoint(),
                 identity);
+        builder.extractDataFromObjectLink(this.objectLinks != null); // we parse object link only if there was updated.
 
         builder.lwM2mVersion(registration.getLwM2mVersion()).lifeTimeInSec(lifeTimeInSec).smsNumber(smsNumber)
                 .bindingMode(bindingMode).queueMode(registration.getQueueMode()).objectLinks(linkObject)
                 .registrationDate(registration.getRegistrationDate()).lastUpdate(lastUpdate)
-                .additionalRegistrationAttributes(additionalAttributes);
+                .additionalRegistrationAttributes(additionalAttributes).rootPath(registration.getRootPath());
 
         return builder.build();
 
diff --git a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/RegistrationSerializer.java b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/RegistrationSerializer.java
index 1171e51d..506fd2c8 100644
--- a/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/RegistrationSerializer.java
+++ b/leshan-server-demo/src/main/java/org/eclipse/leshan/server/demo/servlet/json/RegistrationSerializer.java
@@ -44,7 +44,7 @@ public class RegistrationSerializer implements JsonSerializer<Registration> {
         element.add("lastUpdate", context.serialize(src.getLastUpdate()));
         element.addProperty("address", src.getAddress().getHostAddress() + ":" + src.getPort());
         element.addProperty("smsNumber", src.getSmsNumber());
-        element.addProperty("lwM2mVersion", src.getLwM2mVersion());
+        element.addProperty("lwM2mVersion", src.getLwM2mVersion().toString());
         element.addProperty("lifetime", src.getLifeTimeInSec());
         element.addProperty("bindingMode", BindingMode.toString(src.getBindingMode()));
         element.add("rootPath", context.serialize(src.getRootPath()));
diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java
index 2967f0bf..ddc20f55 100644
--- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java
+++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java
@@ -42,7 +42,7 @@ public class RegistrationSerDes {
         if (r.getSmsNumber() != null) {
             o.add("sms", r.getSmsNumber());
         }
-        o.add("ver", r.getLwM2mVersion());
+        o.add("ver", r.getLwM2mVersion().toString());
         o.add("bnd", BindingMode.toString(r.getBindingMode()));
         if (r.getQueueMode() != null)
             o.add("qm", r.getQueueMode());
@@ -91,12 +91,19 @@ public class RegistrationSerDes {
             b.queueMode(jObj.getBoolean("qm", false));
         b.lastUpdate(new Date(jObj.getLong("lastUp", 0)));
         b.lifeTimeInSec(jObj.getLong("lt", 0));
-        b.lwM2mVersion(jObj.getString("ver", Version.getDefault().toString()));
+        String versionAsString = jObj.getString("ver", null);
+        if (versionAsString == null) {
+            b.lwM2mVersion(Version.getDefault());
+        } else {
+            b.lwM2mVersion(Version.get(versionAsString));
+        }
         b.registrationDate(new Date(jObj.getLong("regDate", 0)));
         if (jObj.get("sms") != null) {
             b.smsNumber(jObj.getString("sms", ""));
         }
 
+        b.rootPath(jObj.getString("root", "/"));
+
         JsonArray links = (JsonArray) jObj.get("objLink");
         Link[] linkObjs = new Link[links.size()];
         for (int i = 0; i < links.size(); i++) {


Test patch:
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java
index d41e556e..7079c8e5 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/request/CoapRequestBuilderTest.java
@@ -76,6 +76,7 @@ public class CoapRequestBuilderTest {
     private Registration newRegistration(String rootpath) throws UnknownHostException {
         Builder b = new Registration.Builder("regid", "endpoint",
                 Identity.unsecure(Inet4Address.getLoopbackAddress(), 12354));
+        b.extractDataFromObjectLink(true);
         if (rootpath != null) {
             Map<String, String> attr = new HashMap<>();
             attr.put("rt", "\"oma.lwm2m\"");
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
index 408db55a..31183f09 100644
--- 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
@@ -20,19 +20,11 @@ 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() {
-        // We exclude "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);
diff --git a/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationTest.java b/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationTest.java
index 4dc7cc8b..ffce1b5b 100644
--- a/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationTest.java
+++ b/leshan-server-core/src/test/java/org/eclipse/leshan/server/registration/RegistrationTest.java
@@ -28,12 +28,27 @@ import org.junit.Test;
 
 public class RegistrationTest {
 
-    String tto = "</>;rt=\"oma.lwm2m\";ct=100, </1/101>,</1/102>, </2/0>, </2/1> ;empty";
-
     @Test
-    public void test_supported_object_given_an_object_link_without_version() {
+    public void test_object_links_without_version_nor_rootpath() {
         Registration reg = given_a_registration_with_object_link_like("</1/0>,</3/0>");
 
+        // check root path
+        assertEquals("/", reg.getRootPath());
+
+        // Ensure supported objects are correct
+        Map<Integer, String> supportedObject = reg.getSupportedObject();
+        assertEquals(2, supportedObject.size());
+        assertEquals(ObjectModel.DEFAULT_VERSION, supportedObject.get(1));
+        assertEquals(ObjectModel.DEFAULT_VERSION, supportedObject.get(3));
+    }
+
+    @Test
+    public void test_object_links_with_default_rootpath() {
+        Registration reg = given_a_registration_with_object_link_like("</>;rt=\"oma.lwm2m\", </1/0>,</3/0>");
+
+        // check root path
+        assertEquals("/", reg.getRootPath());
+
         // Ensure supported objects are correct
         Map<Integer, String> supportedObject = reg.getSupportedObject();
         assertEquals(2, supportedObject.size());
@@ -42,9 +57,12 @@ public class RegistrationTest {
     }
 
     @Test
-    public void test_supported_object_given_an_object_link_with_rootpath() {
+    public void test_object_links_with_rootpath() {
         Registration reg = given_a_registration_with_object_link_like("</root>;rt=\"oma.lwm2m\", </root/1/0>,</3/0>");
 
+        // check root path
+        assertEquals("/root/", reg.getRootPath());
+
         // Ensure supported objects are correct
         Map<Integer, String> supportedObject = reg.getSupportedObject();
         assertEquals(1, supportedObject.size());
@@ -53,9 +71,12 @@ public class RegistrationTest {
     }
 
     @Test
-    public void test_supported_object_given_an_object_link_with_unquoted_rootpath() {
+    public void test_object_links_with_unquoted_rootpath() {
         Registration reg = given_a_registration_with_object_link_like("</root>;rt=oma.lwm2m, </root/1/0>,</3/0>");
 
+        // check root path
+        assertEquals("/root/", reg.getRootPath());
+
         // Ensure supported objects are correct
         Map<Integer, String> supportedObject = reg.getSupportedObject();
         assertEquals(1, supportedObject.size());
@@ -64,10 +85,13 @@ public class RegistrationTest {
     }
 
     @Test
-    public void test_supported_object_given_an_object_link_with_regexp_rootpath() {
+    public void test_object_links_with_regexp_rootpath() {
         Registration reg = given_a_registration_with_object_link_like(
                 "</r(\\d+)oot>;rt=\"oma.lwm2m\", </r(\\d+)oot/1/0>,</3/0>");
 
+        // check root path
+        assertEquals("/r(\\d+)oot/", reg.getRootPath());
+
         // Ensure supported objects are correct
         Map<Integer, String> supportedObject = reg.getSupportedObject();
         assertEquals(1, supportedObject.size());
@@ -76,9 +100,12 @@ public class RegistrationTest {
     }
 
     @Test
-    public void test_supported_object_given_an_object_link_with_version() {
+    public void test_object_links_with_version() {
         Registration reg = given_a_registration_with_object_link_like("</1/0>,</3>;ver=\"1.1\",</3/0>");
 
+        // check root path
+        assertEquals("/", reg.getRootPath());
+
         // Ensure supported objects are correct
         Map<Integer, String> supportedObject = reg.getSupportedObject();
         assertEquals(2, supportedObject.size());
@@ -87,9 +114,26 @@ public class RegistrationTest {
     }
 
     @Test
-    public void test_supported_object_given_an_object_link_with_not_lwm2m_url() {
+    public void test_object_links_with_text_in_not_lwm2m_path() {
+        Registration reg = given_a_registration_with_object_link_like(
+                "</root>;rt=\"oma.lwm2m\",<text>,</1/text/0/in/path>,empty,</2/O/test/in/path>,</root/3/0>;ver=\"1.1\",</root/4/0/0/>");
+
+        // check root path
+        assertEquals("/root/", reg.getRootPath());
+
+        // Ensure supported objects are correct
+        Map<Integer, String> supportedObject = reg.getSupportedObject();
+        assertEquals(1, supportedObject.size());
+        assertEquals("1.1", supportedObject.get(3));
+    }
+
+    @Test
+    public void test_object_links_with_text_in_lwm2m_path() {
         Registration reg = given_a_registration_with_object_link_like(
-                "<text>,</1/text/0/in/path>,empty,</2/O/test/in/path>,</3/0>;ver=\"1.1\",<4/0/0/>");
+                "<text>,</1/text/0/in/path>,empty,</2/O/test/in/path>,</3/0>;ver=\"1.1\",</4/0/0/>");
+
+        // check root path
+        assertEquals("/", reg.getRootPath());
 
         // Ensure supported objects are correct
         Map<Integer, String> supportedObject = reg.getSupportedObject();
@@ -98,9 +142,9 @@ public class RegistrationTest {
     }
 
     private Registration given_a_registration_with_object_link_like(String objectLinks) {
-        Builder builder = new Registration.Builder("id", "endpoin",
+        Builder builder = new Registration.Builder("id", "endpoint",
                 Identity.unsecure(InetSocketAddress.createUnresolved("localhost", 0)));
-
+        builder.extractDataFromObjectLink(true);
         builder.objectLinks(Link.parse(objectLinks.getBytes()));
         return builder.build();
     }
diff --git a/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java b/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java
index de7c62cb..de868ba7 100644
--- a/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java
+++ b/leshan-server-redis/src/test/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDesTest.java
@@ -40,7 +40,7 @@ public class RegistrationSerDesTest {
         objs[1] = new Link("/0/2");
 
         Registration.Builder builder = new Registration.Builder("registrationId", "endpoint",
-                Identity.unsecure(Inet4Address.getLoopbackAddress(), 1)).objectLinks(objs);
+                Identity.unsecure(Inet4Address.getLoopbackAddress(), 1)).objectLinks(objs).rootPath("/");
 
         builder.registrationDate(new Date(100L));
         builder.lastUpdate(new Date(101L));