Repository: eclipse/leshan
Pull request #1010 (2021-05-10 14:31:33)

Add getAvailableInstances method to Registration.
Object link at registration contains supported object and available instance.
This PR add an API to easily access to available instances. 


Patch:
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mPath.java b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mPath.java
index 0173311d..18ca4f36 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mPath.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/node/LwM2mPath.java
@@ -20,7 +20,7 @@ import org.eclipse.leshan.core.util.Validate;
 /**
  * A path pointing to a LwM2M node (root, object, object instance, resource or resource instance).
  */
-public class LwM2mPath {
+public class LwM2mPath implements Comparable<LwM2mPath> {
 
     public static final byte ROOT_DEPTH = 1;
     public static final byte OBJECT_DEPTH = 2;
@@ -368,6 +368,35 @@ public class LwM2mPath {
         return true;
     }
 
+    @Override
+    public int compareTo(LwM2mPath o) {
+        int res = compareInteger(this.objectId, o.objectId);
+        if (res != 0 || this.objectId == null)
+            return res;
+
+        res = compareInteger(this.objectInstanceId, o.objectInstanceId);
+        if (res != 0 || this.objectInstanceId == null)
+            return res;
+
+        res = compareInteger(this.resourceId, o.resourceId);
+        if (res != 0 || this.resourceId == null)
+            return res;
+
+        return compareInteger(this.resourceInstanceId, o.resourceInstanceId);
+    }
+
+    private int compareInteger(Integer i1, Integer i2) {
+        if (i1 == i2) {
+            return 0;
+        } else if (i1 == null) {
+            return -1;
+        } else if (i2 == null) {
+            return 1;
+        } else {
+            return i1.compareTo(i2);
+        }
+    }
+
     /**
      * Parse a string containing a full LWM2M path containing rootpath (rt="oma.lwm2m").
      * <p>
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 23dbeb64..3a268739 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
@@ -27,6 +27,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 import org.eclipse.leshan.core.Link;
 import org.eclipse.leshan.core.LwM2m.Version;
@@ -82,6 +83,9 @@ public class Registration {
     // All supported object (object id => version)
     private final Map<Integer, String> supportedObjects;
 
+    // All available instances
+    private final Set<LwM2mPath> availableInstances;
+
     private final Date lastUpdate;
 
     protected Registration(Builder builder) {
@@ -100,6 +104,7 @@ public class Registration {
         rootPath = builder.rootPath;
         supportedContentFormats = builder.supportedContentFormats;
         supportedObjects = builder.supportedObjects;
+        availableInstances = builder.availableInstances;
 
         // other params
         lifeTimeInSec = builder.lifeTimeInSec;
@@ -246,6 +251,13 @@ public class Registration {
         return supportedContentFormats;
     }
 
+    /**
+     * @return all available object instance by the client
+     */
+    public Set<LwM2mPath> getAvailableInstances() {
+        return availableInstances;
+    }
+
     /**
      * Gets the unique name the client has registered with.
      * 
@@ -323,10 +335,10 @@ public class Registration {
     @Override
     public String toString() {
         return String.format(
-                "Registration [registrationDate=%s, identity=%s, lifeTimeInSec=%s, smsNumber=%s, lwM2mVersion=%s, bindingMode=%s, queueMode=%s, endpoint=%s, id=%s, objectLinks=%s, supportedObjects=%s, additionalRegistrationAttributes=%s, rootPath=%s, supportedContentFormats=%s, lastUpdate=%s]",
+                "Registration [registrationDate=%s, identity=%s, lifeTimeInSec=%s, smsNumber=%s, lwM2mVersion=%s, bindingMode=%s, queueMode=%s, endpoint=%s, id=%s, objectLinks=%s, additionalRegistrationAttributes=%s, rootPath=%s, supportedContentFormats=%s, supportedObjects=%s, availableInstances=%s, lastUpdate=%s]",
                 registrationDate, identity, lifeTimeInSec, smsNumber, lwM2mVersion, bindingMode, queueMode, endpoint,
-                id, Arrays.toString(objectLinks), supportedObjects, additionalRegistrationAttributes, rootPath,
-                supportedContentFormats, lastUpdate);
+                id, Arrays.toString(objectLinks), additionalRegistrationAttributes, rootPath, supportedContentFormats,
+                supportedObjects, availableInstances, lastUpdate);
     }
 
     @Override
@@ -335,6 +347,7 @@ public class Registration {
         int result = 1;
         result = prime * result
                 + ((additionalRegistrationAttributes == null) ? 0 : additionalRegistrationAttributes.hashCode());
+        result = prime * result + ((availableInstances == null) ? 0 : availableInstances.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());
@@ -366,6 +379,11 @@ public class Registration {
                 return false;
         } else if (!additionalRegistrationAttributes.equals(other.additionalRegistrationAttributes))
             return false;
+        if (availableInstances == null) {
+            if (other.availableInstances != null)
+                return false;
+        } else if (!availableInstances.equals(other.availableInstances))
+            return false;
         if (bindingMode == null) {
             if (other.bindingMode != null)
                 return false;
@@ -449,6 +467,7 @@ public class Registration {
         private String rootPath;
         private Set<ContentFormat> supportedContentFormats;
         private Map<Integer, String> supportedObjects;
+        private Set<LwM2mPath> availableInstances;
         private Map<String, String> additionalRegistrationAttributes;
 
         // builder setting
@@ -532,6 +551,11 @@ public class Registration {
             return this;
         }
 
+        public Builder availableInstances(Set<LwM2mPath> availableInstances) {
+            this.availableInstances = availableInstances;
+            return this;
+        }
+
         public Builder additionalRegistrationAttributes(Map<String, String> additionalRegistrationAttributes) {
             this.additionalRegistrationAttributes = additionalRegistrationAttributes;
             return this;
@@ -555,9 +579,8 @@ public class Registration {
 
                 // Extract data from link object
                 supportedObjects = new HashMap<>();
+                availableInstances = new HashSet<>();
                 for (Link link : objectLinks) {
-                    // TODO extract available instances
-
                     if (link != null) {
                         // search supported Content format in root link
                         if (rootPath.equals(link.getUrl())) {
@@ -569,8 +592,11 @@ public class Registration {
                             LwM2mPath path = LwM2mPath.parse(link.getUrl(), rootPath);
                             if (path != null) {
                                 // add supported objects
-                                if (path.isObject() || path.isObjectInstance()) {
+                                if (path.isObject()) {
                                     addSupportedObject(link, path);
+                                } else if (path.isObjectInstance()) {
+                                    addSupportedObject(link, path);
+                                    availableInstances.add(path);
                                 }
                             }
                         }
@@ -659,6 +685,11 @@ public class Registration {
             } else {
                 supportedObjects = Collections.unmodifiableMap(new HashMap<>(supportedObjects));
             }
+            if (availableInstances == null || availableInstances.isEmpty()) {
+                availableInstances = Collections.emptySet();
+            } else {
+                availableInstances = Collections.unmodifiableSet(new TreeSet<>(availableInstances));
+            }
             if (additionalRegistrationAttributes == null || additionalRegistrationAttributes.isEmpty()) {
                 additionalRegistrationAttributes = Collections.emptyMap();
             } else {
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 6a7208ee..cfd504d3 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
@@ -89,7 +89,8 @@ public class RegistrationUpdate {
                 .registrationDate(registration.getRegistrationDate()).lastUpdate(lastUpdate)
                 .additionalRegistrationAttributes(additionalAttributes).rootPath(registration.getRootPath())
                 .supportedContentFormats(registration.getSupportedContentFormats())
-                .supportedObjects(registration.getSupportedObject());
+                .supportedObjects(registration.getSupportedObject())
+                .availableInstances(registration.getAvailableInstances());
 
         return builder.build();
 


Test patch:
diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/node/LwM2mParhTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/node/LwM2mParhTest.java
new file mode 100644
index 00000000..6f556e76
--- /dev/null
+++ b/leshan-core/src/test/java/org/eclipse/leshan/core/node/LwM2mParhTest.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v20.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.core.node;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class LwM2mParhTest {
+
+    @Test
+    public void test_compare_path() {
+        assertEquals("/", "/");
+        assertEquals("/1", "/1");
+        assertEquals("/1/2", "/1/2");
+        assertEquals("/1/2/3", "/1/2/3");
+        assertEquals("/1/2/3/4", "/1/2/3/4");
+
+        assertFirstSmaller("/", "/1");
+        assertFirstSmaller("/", "/1/1");
+        assertFirstSmaller("/", "/1/1/1");
+        assertFirstSmaller("/", "/1/1/1/1");
+
+        assertFirstSmaller("/1", "/1/1");
+        assertFirstSmaller("/1", "/1/1/1");
+        assertFirstSmaller("/1", "/1/1/1/1");
+
+        assertFirstSmaller("/1/1", "/1/1/1");
+        assertFirstSmaller("/1/1", "/1/1/1/1");
+
+        assertFirstSmaller("/1/1/1", "/1/1/1/1");
+
+        assertFirstSmaller("/1", "/2");
+        assertFirstSmaller("/1/1", "/2");
+        assertFirstSmaller("/1/1/1", "/2");
+        assertFirstSmaller("/1/1/1/1", "/2");
+
+        assertFirstSmaller("/1", "/2/1");
+        assertFirstSmaller("/1/1", "/2/1");
+        assertFirstSmaller("/1/1/1", "/2/1");
+        assertFirstSmaller("/1/1/1/1", "/2/1");
+
+        assertFirstSmaller("/1", "/2/1/1");
+        assertFirstSmaller("/1/1", "/2/1/1");
+        assertFirstSmaller("/1/1/1", "/2/1/1");
+        assertFirstSmaller("/1/1/1/1", "/2/1/1");
+
+        assertFirstSmaller("/1", "/2/1/1/1");
+        assertFirstSmaller("/1/1", "/2/1/1/1");
+        assertFirstSmaller("/1/1/1", "/2/1/1/1");
+        assertFirstSmaller("/1/1/1/1", "/2/1/1/1");
+    }
+
+    private void assertEquals(String path1, String path2) {
+        assertTrue(new LwM2mPath(path1).compareTo(new LwM2mPath(path2)) == 0);
+    }
+
+    private void assertFirstSmaller(String path1, String path2) {
+        assertTrue(new LwM2mPath(path1).compareTo(new LwM2mPath(path2)) == -1);
+        assertTrue(new LwM2mPath(path2).compareTo(new LwM2mPath(path1)) == 1);
+
+    }
+}
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 c66d2514..136f338a 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
@@ -24,6 +24,7 @@ import java.util.Set;
 
 import org.eclipse.leshan.core.Link;
 import org.eclipse.leshan.core.model.ObjectModel;
+import org.eclipse.leshan.core.node.LwM2mPath;
 import org.eclipse.leshan.core.request.ContentFormat;
 import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.server.registration.Registration.Builder;
@@ -43,6 +44,11 @@ public class RegistrationTest {
         assertEquals(2, supportedObject.size());
         assertEquals(ObjectModel.DEFAULT_VERSION, supportedObject.get(1));
         assertEquals(ObjectModel.DEFAULT_VERSION, supportedObject.get(3));
+
+        // ensure available instances are correct
+        Set<LwM2mPath> availableInstances = reg.getAvailableInstances();
+        assertEquals(2, availableInstances.size());
+        assertTrue(availableInstances.containsAll(Arrays.asList(new LwM2mPath(1, 0), new LwM2mPath(3, 0))));
     }
 
     @Test
@@ -63,6 +69,11 @@ public class RegistrationTest {
         assertEquals(4, supportedContentFormats.size()); // 3 + 1 mandatory TLV content format for LWM2M v1.0
         assertTrue(supportedContentFormats.containsAll(
                 Arrays.asList(ContentFormat.TLV, ContentFormat.TEXT, ContentFormat.OPAQUE, ContentFormat.JSON)));
+
+        // ensure available instances are correct
+        Set<LwM2mPath> availableInstances = reg.getAvailableInstances();
+        assertEquals(2, availableInstances.size());
+        assertTrue(availableInstances.containsAll(Arrays.asList(new LwM2mPath(1, 0), new LwM2mPath(3, 0))));
     }
 
     @Test
@@ -84,6 +95,11 @@ public class RegistrationTest {
         assertEquals(4, supportedContentFormats.size()); // 3 + 1 mandatory TLV content format for LWM2M v1.0
         assertTrue(supportedContentFormats.containsAll(
                 Arrays.asList(ContentFormat.TLV, ContentFormat.TEXT, ContentFormat.OPAQUE, ContentFormat.JSON)));
+
+        // ensure available instances are correct
+        Set<LwM2mPath> availableInstances = reg.getAvailableInstances();
+        assertEquals(2, availableInstances.size());
+        assertTrue(availableInstances.containsAll(Arrays.asList(new LwM2mPath(1, 0), new LwM2mPath(3, 0))));
     }
 
     @Test
@@ -98,6 +114,11 @@ public class RegistrationTest {
         assertEquals(1, supportedObject.size());
         assertEquals(ObjectModel.DEFAULT_VERSION, supportedObject.get(1));
         assertNull(supportedObject.get(3));
+
+        // ensure available instances are correct
+        Set<LwM2mPath> availableInstances = reg.getAvailableInstances();
+        assertEquals(1, availableInstances.size());
+        assertTrue(availableInstances.containsAll(Arrays.asList(new LwM2mPath(1, 0))));
     }
 
     @Test
@@ -112,6 +133,11 @@ public class RegistrationTest {
         assertEquals(1, supportedObject.size());
         assertEquals(ObjectModel.DEFAULT_VERSION, supportedObject.get(1));
         assertNull(supportedObject.get(3));
+
+        // ensure available instances are correct
+        Set<LwM2mPath> availableInstances = reg.getAvailableInstances();
+        assertEquals(1, availableInstances.size());
+        assertTrue(availableInstances.containsAll(Arrays.asList(new LwM2mPath(1, 0))));
     }
 
     @Test
@@ -127,6 +153,11 @@ public class RegistrationTest {
         assertEquals(1, supportedObject.size());
         assertEquals(ObjectModel.DEFAULT_VERSION, supportedObject.get(1));
         assertNull(supportedObject.get(3));
+
+        // ensure available instances are correct
+        Set<LwM2mPath> availableInstances = reg.getAvailableInstances();
+        assertEquals(1, availableInstances.size());
+        assertTrue(availableInstances.containsAll(Arrays.asList(new LwM2mPath(1, 0))));
     }
 
     @Test
@@ -141,6 +172,11 @@ public class RegistrationTest {
         assertEquals(2, supportedObject.size());
         assertEquals(ObjectModel.DEFAULT_VERSION, supportedObject.get(1));
         assertEquals("1.1", supportedObject.get(3));
+
+        // ensure available instances are correct
+        Set<LwM2mPath> availableInstances = reg.getAvailableInstances();
+        assertEquals(2, availableInstances.size());
+        assertTrue(availableInstances.containsAll(Arrays.asList(new LwM2mPath(1, 0), new LwM2mPath(3, 0))));
     }
 
     @Test
@@ -155,6 +191,11 @@ public class RegistrationTest {
         Map<Integer, String> supportedObject = reg.getSupportedObject();
         assertEquals(1, supportedObject.size());
         assertEquals("1.1", supportedObject.get(3));
+
+        // ensure available instances are correct
+        Set<LwM2mPath> availableInstances = reg.getAvailableInstances();
+        assertEquals(1, availableInstances.size());
+        assertTrue(availableInstances.containsAll(Arrays.asList(new LwM2mPath(3, 0))));
     }
 
     @Test(expected = IllegalArgumentException.class)