Repository: eclipse/leshan
Pull request #430 (2017-11-10 15:20:52)

Enhance observation tests + some enhancements


Patch:
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/observation/Observation.java b/leshan-core/src/main/java/org/eclipse/leshan/core/observation/Observation.java
index 073ac7a1..4c26adde 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/observation/Observation.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/observation/Observation.java
@@ -15,6 +15,9 @@
  *******************************************************************************/
 package org.eclipse.leshan.core.observation;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 
 import org.eclipse.leshan.core.node.LwM2mPath;
@@ -25,10 +28,10 @@ import org.eclipse.leshan.util.Hex;
  */
 public class Observation {
 
-    private byte[] id;
-    private LwM2mPath path;
-    private String registrationId;
-    private Map<String, String> context;
+    private final byte[] id;
+    private final LwM2mPath path;
+    private final String registrationId;
+    private final Map<String, String> context;
 
     /**
      * Instantiates an {@link Observation} for the given node path.
@@ -42,7 +45,10 @@ public class Observation {
         this.id = id;
         this.path = path;
         this.registrationId = registrationId;
-        this.context = context;
+        if (context != null)
+            this.context = Collections.unmodifiableMap(new HashMap<>(context));
+        else
+            this.context = Collections.emptyMap();
     }
 
     /**
@@ -83,4 +89,45 @@ public class Observation {
         return String.format("Observation [id=%s, path=%s, registrationId=%s, context=%s]", Hex.encodeHexString(id),
                 path, registrationId, context);
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((context == null) ? 0 : context.hashCode());
+        result = prime * result + Arrays.hashCode(id);
+        result = prime * result + ((path == null) ? 0 : path.hashCode());
+        result = prime * result + ((registrationId == null) ? 0 : registrationId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        Observation other = (Observation) obj;
+        if (context == null) {
+            if (other.context != null)
+                return false;
+        } else if (!context.equals(other.context))
+            return false;
+        if (!Arrays.equals(id, other.id))
+            return false;
+        if (path == null) {
+            if (other.path != null)
+                return false;
+        } else if (!path.equals(other.path))
+            return false;
+        if (registrationId == null) {
+            if (other.registrationId != null)
+                return false;
+        } else if (!registrationId.equals(other.registrationId))
+            return false;
+        return true;
+    }
+
 }
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/ObserveUtil.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/ObserveUtil.java
new file mode 100644
index 00000000..3460f965
--- /dev/null
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/ObserveUtil.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2017 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.californium;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.californium.core.coap.Request;
+import org.eclipse.leshan.core.node.LwM2mPath;
+import org.eclipse.leshan.core.observation.Observation;
+import org.eclipse.leshan.core.request.ObserveRequest;
+
+/**
+ * Utility functions to help to handle observation in Leshan. Those helper functions are only needed if you're
+ * implementing your own {@link CaliforniumRegistrationStore}.
+ */
+public class ObserveUtil {
+
+    /* keys used to populate the request context */
+    public static final String CTX_ENDPOINT = "leshan-endpoint";
+    public static final String CTX_REGID = "leshan-regId";
+    public static final String CTX_LWM2M_PATH = "leshan-path";
+
+    /**
+     * Create a LWM2M observation from a CoAP request.
+     */
+    public static Observation createLwM2mObservation(Request request) {
+        String regId = null;
+        String lwm2mPath = null;
+        Map<String, String> context = null;
+
+        for (Entry<String, String> ctx : request.getUserContext().entrySet()) {
+            switch (ctx.getKey()) {
+            case CTX_REGID:
+                regId = ctx.getValue();
+                break;
+            case CTX_LWM2M_PATH:
+                lwm2mPath = ctx.getValue();
+                break;
+            case CTX_ENDPOINT:
+                break;
+            default:
+                if (context == null) {
+                    context = new HashMap<>();
+                }
+                context.put(ctx.getKey(), ctx.getValue());
+            }
+        }
+        return new Observation(request.getToken(), regId, new LwM2mPath(lwm2mPath), context);
+    }
+
+    /**
+     * Create a CoAP observe request context with specific keys needed for internal Leshan working.
+     */
+    public static Map<String, String> createCoapObserveRequestContext(String endpoint, String registrationId,
+            ObserveRequest request) {
+        Map<String, String> context = new HashMap<>();
+        context.put(CTX_ENDPOINT, endpoint);
+        context.put(CTX_REGID, registrationId);
+        context.put(CTX_LWM2M_PATH, request.getPath().toString());
+        for (Entry<String, String> ctx : request.getContext().entrySet()) {
+            context.put(ctx.getKey(), ctx.getValue());
+        }
+        return context;
+    }
+
+    public static String extractRegistrationId(org.eclipse.californium.core.observe.Observation observation) {
+        return observation.getRequest().getUserContext().get(CTX_REGID);
+    }
+
+    public static String extractLwm2mPath(org.eclipse.californium.core.observe.Observation observation) {
+        return observation.getRequest().getUserContext().get(CTX_LWM2M_PATH);
+    }
+
+    public static String extractEndpoint(org.eclipse.californium.core.observe.Observation observation) {
+        return observation.getRequest().getUserContext().get(CTX_ENDPOINT);
+    }
+
+    /**
+     * Validate the Californium observation. It is valid if it contains all necessary context for Leshan.
+     */
+    public static String validateCoapObservation(org.eclipse.californium.core.observe.Observation observation) {
+        if (!observation.getRequest().getUserContext().containsKey(CTX_REGID))
+            throw new IllegalStateException("missing registrationId info in the request context");
+        if (!observation.getRequest().getUserContext().containsKey(CTX_LWM2M_PATH))
+            throw new IllegalStateException("missing lwm2m path info in the request context");
+
+        String endpoint = observation.getRequest().getUserContext().get(CTX_ENDPOINT);
+        if (endpoint == null)
+            throw new IllegalStateException("missing endpoint info in the request context");
+
+        return endpoint;
+    }
+}
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java
index 617fb696..2bc8ab2a 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/CoapRequestBuilder.java
@@ -16,9 +16,6 @@
 package org.eclipse.leshan.server.californium.impl;
 
 import java.net.InetSocketAddress;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
 
 import org.eclipse.californium.core.coap.MediaTypeRegistry;
 import org.eclipse.californium.core.coap.Request;
@@ -39,6 +36,7 @@ import org.eclipse.leshan.core.request.ObserveRequest;
 import org.eclipse.leshan.core.request.ReadRequest;
 import org.eclipse.leshan.core.request.WriteAttributesRequest;
 import org.eclipse.leshan.core.request.WriteRequest;
+import org.eclipse.leshan.server.californium.ObserveUtil;
 import org.eclipse.leshan.util.StringUtils;
 
 public class CoapRequestBuilder implements DownlinkRequestVisitor {
@@ -54,11 +52,6 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor {
     private final LwM2mModel model;
     private final LwM2mNodeEncoder encoder;
 
-    /* keys used to populate the request context */
-    public static final String CTX_ENDPOINT = "leshan-endpoint";
-    public static final String CTX_REGID = "leshan-regId";
-    public static final String CTX_LWM2M_PATH = "leshan-path";
-
     public CoapRequestBuilder(InetSocketAddress destination, LwM2mModel model, LwM2mNodeEncoder encoder) {
         this.destination = destination;
         this.rootPath = null;
@@ -145,14 +138,7 @@ public class CoapRequestBuilder implements DownlinkRequestVisitor {
         setTarget(coapRequest, request.getPath());
 
         // add context info to the observe request
-        Map<String, String> context = new HashMap<>();
-        context.put(CTX_ENDPOINT, endpoint);
-        context.put(CTX_REGID, registrationId);
-        context.put(CTX_LWM2M_PATH, request.getPath().toString());
-        for (Entry<String, String> ctx : request.getContext().entrySet()) {
-            context.put(ctx.getKey(), ctx.getValue());
-        }
-        coapRequest.setUserContext(context);
+        coapRequest.setUserContext(ObserveUtil.createCoapObserveRequestContext(endpoint, registrationId, request));
     }
 
     @Override
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStore.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStore.java
index d506c06b..2e2959bf 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStore.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/InMemoryRegistrationStore.java
@@ -20,8 +20,6 @@
  *******************************************************************************/
 package org.eclipse.leshan.server.californium.impl;
 
-import static org.eclipse.leshan.server.californium.impl.CoapRequestBuilder.*;
-
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -30,7 +28,6 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -39,11 +36,11 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import org.eclipse.californium.core.observe.ObservationUtil;
 import org.eclipse.californium.elements.CorrelationContext;
-import org.eclipse.leshan.core.node.LwM2mPath;
 import org.eclipse.leshan.core.observation.Observation;
 import org.eclipse.leshan.server.Startable;
 import org.eclipse.leshan.server.Stoppable;
 import org.eclipse.leshan.server.californium.CaliforniumRegistrationStore;
+import org.eclipse.leshan.server.californium.ObserveUtil;
 import org.eclipse.leshan.server.registration.Deregistration;
 import org.eclipse.leshan.server.registration.ExpirationListener;
 import org.eclipse.leshan.server.registration.Registration;
@@ -284,7 +281,7 @@ public class InMemoryRegistrationStore implements CaliforniumRegistrationStore,
 
                 validateObservation(obs);
 
-                String registrationId = extractRegistrationId(obs);
+                String registrationId = ObserveUtil.extractRegistrationId(obs);
                 Key token = new Key(obs.getRequest().getToken());
                 org.eclipse.californium.core.observe.Observation previousObservation = obsByToken.put(token, obs);
                 if (!tokensByRegId.containsKey(registrationId)) {
@@ -350,7 +347,7 @@ public class InMemoryRegistrationStore implements CaliforniumRegistrationStore,
         org.eclipse.californium.core.observe.Observation removed = obsByToken.remove(kToken);
 
         if (removed != null) {
-            String registrationId = extractRegistrationId(removed);
+            String registrationId = ObserveUtil.extractRegistrationId(removed);
             List<Key> tokens = tokensByRegId.get(registrationId);
             tokens.remove(kToken);
             if (tokens.isEmpty()) {
@@ -388,45 +385,20 @@ public class InMemoryRegistrationStore implements CaliforniumRegistrationStore,
         return result;
     }
 
-    /* Retrieve the registrationId from the request context */
-    private String extractRegistrationId(org.eclipse.californium.core.observe.Observation observation) {
-        return observation.getRequest().getUserContext().get(CoapRequestBuilder.CTX_REGID);
-    }
-
     private Observation build(org.eclipse.californium.core.observe.Observation cfObs) {
         if (cfObs == null)
             return null;
 
-        String regId = null;
-        String lwm2mPath = null;
-        Map<String, String> context = null;
-
-        for (Entry<String, String> ctx : cfObs.getRequest().getUserContext().entrySet()) {
-            switch (ctx.getKey()) {
-            case CTX_REGID:
-                regId = ctx.getValue();
-                break;
-            case CTX_LWM2M_PATH:
-                lwm2mPath = ctx.getValue();
-                break;
-            default:
-                if (context == null) {
-                    context = new HashMap<>();
-                }
-                context.put(ctx.getKey(), ctx.getValue());
-            }
-        }
-        return new Observation(cfObs.getRequest().getToken(), regId, new LwM2mPath(lwm2mPath), context);
+        return ObserveUtil.createLwM2mObservation(cfObs.getRequest());
     }
 
-    private void validateObservation(org.eclipse.californium.core.observe.Observation observation) {
-        if (!observation.getRequest().getUserContext().containsKey(CoapRequestBuilder.CTX_REGID))
-            throw new IllegalStateException("missing registrationId info in the request context");
-        if (!observation.getRequest().getUserContext().containsKey(CoapRequestBuilder.CTX_LWM2M_PATH))
-            throw new IllegalStateException("missing lwm2m path info in the request context");
-        if (getRegistration(observation.getRequest().getUserContext().get(CoapRequestBuilder.CTX_REGID)) == null) {
+    private String validateObservation(org.eclipse.californium.core.observe.Observation observation) {
+        String endpoint = ObserveUtil.validateCoapObservation(observation);
+        if (getRegistration(ObserveUtil.extractRegistrationId(observation)) == null) {
             throw new IllegalStateException("no registration for this Id");
         }
+
+        return endpoint;
     }
 
     /* *************** Expiration handling **************** */
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LwM2mResponseBuilder.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LwM2mResponseBuilder.java
index e9dea9c0..7ad3f0da 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LwM2mResponseBuilder.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/LwM2mResponseBuilder.java
@@ -55,6 +55,7 @@ import org.eclipse.leshan.core.response.ObserveResponse;
 import org.eclipse.leshan.core.response.ReadResponse;
 import org.eclipse.leshan.core.response.WriteAttributesResponse;
 import org.eclipse.leshan.core.response.WriteResponse;
+import org.eclipse.leshan.server.californium.ObserveUtil;
 import org.eclipse.leshan.server.registration.Registration;
 import org.eclipse.leshan.util.Hex;
 import org.slf4j.Logger;
@@ -213,8 +214,7 @@ public class LwM2mResponseBuilder<T extends LwM2mResponse> implements DownlinkRe
                     registration.getEndpoint());
             if (coapResponse.getOptions().hasObserve()) {
                 // observe request successful
-                Observation observation = new Observation(coapRequest.getToken(), registration.getId(),
-                        request.getPath(), request.getContext());
+                Observation observation = ObserveUtil.createLwM2mObservation(coapRequest);
                 observationService.addObservation(registration, observation);
                 // add the observation to an ObserveResponse instance
                 lwM2mresponse = new ObserveResponse(toLwM2mResponseCode(coapResponse.getCode()), content, null,
diff --git a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/ObservationServiceImpl.java b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/ObservationServiceImpl.java
index 217732f4..f3c9c216 100644
--- a/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/ObservationServiceImpl.java
+++ b/leshan-server-cf/src/main/java/org/eclipse/leshan/server/californium/impl/ObservationServiceImpl.java
@@ -16,7 +16,6 @@
 package org.eclipse.leshan.server.californium.impl;
 
 import static org.eclipse.leshan.core.californium.ResponseCodeUtil.toLwM2mResponseCode;
-import static org.eclipse.leshan.server.californium.impl.CoapRequestBuilder.CTX_REGID;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -41,6 +40,7 @@ import org.eclipse.leshan.core.request.ContentFormat;
 import org.eclipse.leshan.core.request.exception.InvalidResponseException;
 import org.eclipse.leshan.core.response.ObserveResponse;
 import org.eclipse.leshan.server.californium.CaliforniumRegistrationStore;
+import org.eclipse.leshan.server.californium.ObserveUtil;
 import org.eclipse.leshan.server.model.LwM2mModelProvider;
 import org.eclipse.leshan.server.observation.ObservationListener;
 import org.eclipse.leshan.server.observation.ObservationService;
@@ -202,7 +202,7 @@ public class ObservationServiceImpl implements ObservationService, NotificationL
             return;
 
         // get registration Id
-        String regid = coapRequest.getUserContext().get(CTX_REGID);
+        String regid = coapRequest.getUserContext().get(ObserveUtil.CTX_REGID);
 
         // get observation for this request
         Observation observation = registrationStore.getObservation(regid, coapResponse.getToken());
diff --git a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/RedisRegistrationStore.java b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/RedisRegistrationStore.java
index 66d51ba2..613e83a8 100644
--- a/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/RedisRegistrationStore.java
+++ b/leshan-server-cluster/src/main/java/org/eclipse/leshan/server/cluster/RedisRegistrationStore.java
@@ -16,30 +16,25 @@
 package org.eclipse.leshan.server.cluster;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.eclipse.leshan.server.californium.impl.CoapRequestBuilder.*;
 
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
 import java.util.NoSuchElementException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
 import org.eclipse.californium.elements.CorrelationContext;
-import org.eclipse.leshan.core.node.LwM2mPath;
 import org.eclipse.leshan.core.observation.Observation;
 import org.eclipse.leshan.server.Startable;
 import org.eclipse.leshan.server.Stoppable;
 import org.eclipse.leshan.server.californium.CaliforniumRegistrationStore;
-import org.eclipse.leshan.server.californium.impl.CoapRequestBuilder;
+import org.eclipse.leshan.server.californium.ObserveUtil;
 import org.eclipse.leshan.server.cluster.serialization.ObservationSerDes;
 import org.eclipse.leshan.server.cluster.serialization.RegistrationSerDes;
 import org.eclipse.leshan.server.registration.Deregistration;
@@ -479,7 +474,7 @@ public class RedisRegistrationStore implements CaliforniumRegistrationStore, Sta
 
     @Override
     public void add(org.eclipse.californium.core.observe.Observation obs) {
-        String endpoint = this.validateObservation(obs);
+        String endpoint = ObserveUtil.validateCoapObservation(obs);
 
         try (Jedis j = pool.getResource()) {
             byte[] lockValue = null;
@@ -487,7 +482,7 @@ public class RedisRegistrationStore implements CaliforniumRegistrationStore, Sta
             try {
                 lockValue = RedisLock.acquire(j, lockKey);
 
-                String registrationId = obs.getRequest().getUserContext().get(CTX_REGID);
+                String registrationId = ObserveUtil.extractRegistrationId(obs);
                 if (!j.exists(toRegIdKey(registrationId)))
                     throw new IllegalStateException("no registration for this Id");
 
@@ -521,7 +516,7 @@ public class RedisRegistrationStore implements CaliforniumRegistrationStore, Sta
                 return;
 
             org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs);
-            String registrationId = obs.getRequest().getUserContext().get(CoapRequestBuilder.CTX_REGID);
+            String registrationId = ObserveUtil.extractRegistrationId(obs);
             Registration registration = getRegistration(j, registrationId);
             if (registration == null) {
                 LOG.warn("Unable to remove observation {}, registration {} does not exist anymore", obs.getRequest(),
@@ -610,39 +605,7 @@ public class RedisRegistrationStore implements CaliforniumRegistrationStore, Sta
         if (cfObs == null)
             return null;
 
-        String regId = null;
-        String lwm2mPath = null;
-        Map<String, String> context = null;
-
-        for (Entry<String, String> ctx : cfObs.getRequest().getUserContext().entrySet()) {
-            switch (ctx.getKey()) {
-            case CTX_REGID:
-                regId = ctx.getValue();
-                break;
-            case CTX_LWM2M_PATH:
-                lwm2mPath = ctx.getValue();
-                break;
-            default:
-                if (context == null) {
-                    context = new HashMap<>();
-                }
-                context.put(ctx.getKey(), ctx.getValue());
-            }
-        }
-        return new Observation(cfObs.getRequest().getToken(), regId, new LwM2mPath(lwm2mPath), context);
-    }
-
-    private String validateObservation(org.eclipse.californium.core.observe.Observation observation) {
-        if (!observation.getRequest().getUserContext().containsKey(CoapRequestBuilder.CTX_REGID))
-            throw new IllegalStateException("missing registrationId info in the request context");
-        if (!observation.getRequest().getUserContext().containsKey(CoapRequestBuilder.CTX_LWM2M_PATH))
-            throw new IllegalStateException("missing lwm2m path info in the request context");
-
-        String endpoint = observation.getRequest().getUserContext().get(CoapRequestBuilder.CTX_ENDPOINT);
-        if (endpoint == null)
-            throw new IllegalStateException("missing endpoint info in the request context");
-
-        return endpoint;
+        return ObserveUtil.createLwM2mObservation(cfObs.getRequest());
     }
 
     /* *************** Expiration handling **************** */


Test patch:
diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
index 44756ff8..c7d73445 100644
--- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
+++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/ObserveTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.*;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -95,6 +96,10 @@ public class ObserveTest {
         Observation observation = observeResponse.getObservation();
         assertEquals("/3/0/15", observation.getPath().toString());
         assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId());
+        Set<Observation> observations = helper.server.getObservationService()
+                .getObservations(helper.getCurrentRegistration());
+        assertTrue("We should have only on observation", observations.size() == 1);
+        assertTrue("New observation is not there", observations.contains(observation));
 
         // write device timezone
         LwM2mResponse writeResponse = helper.server.send(helper.getCurrentRegistration(),
@@ -124,6 +129,10 @@ public class ObserveTest {
         Observation observation = observeResponse.getObservation();
         assertEquals("/3/0", observation.getPath().toString());
         assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId());
+        Set<Observation> observations = helper.server.getObservationService()
+                .getObservations(helper.getCurrentRegistration());
+        assertTrue("We should have only on observation", observations.size() == 1);
+        assertTrue("New observation is not there", observations.contains(observation));
 
         // write device timezone
         LwM2mResponse writeResponse = helper.server.send(helper.getCurrentRegistration(),
@@ -158,6 +167,10 @@ public class ObserveTest {
         Observation observation = observeResponse.getObservation();
         assertEquals("/3", observation.getPath().toString());
         assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId());
+        Set<Observation> observations = helper.server.getObservationService()
+                .getObservations(helper.getCurrentRegistration());
+        assertTrue("We should have only on observation", observations.size() == 1);
+        assertTrue("New observation is not there", observations.contains(observation));
 
         // write device timezone
         LwM2mResponse writeResponse = helper.server.send(helper.getCurrentRegistration(),
@@ -193,6 +206,10 @@ public class ObserveTest {
         Observation observation = observeResponse.getObservation();
         assertEquals("/3/0/15", observation.getPath().toString());
         assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId());
+        Set<Observation> observations = helper.server.getObservationService()
+                .getObservations(helper.getCurrentRegistration());
+        assertTrue("We should have only on observation", observations.size() == 1);
+        assertTrue("New observation is not there", observations.contains(observation));
 
         // *** HACK send a notification with unsupported content format *** //
         byte[] payload = LwM2mNodeJsonEncoder.encode(LwM2mSingleResource.newStringResource(15, "Paris"),
@@ -225,6 +242,10 @@ public class ObserveTest {
         Observation observation = observeResponse.getObservation();
         assertEquals("/3/0/15", observation.getPath().toString());
         assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId());
+        Set<Observation> observations = helper.server.getObservationService()
+                .getObservations(helper.getCurrentRegistration());
+        assertTrue("We should have only on observation", observations.size() == 1);
+        assertTrue("New observation is not there", observations.contains(observation));
 
         // *** HACK send time-stamped notification as Leshan client does not support it *** //
         // create time-stamped nodes
@@ -264,6 +285,10 @@ public class ObserveTest {
         Observation observation = observeResponse.getObservation();
         assertEquals("/3/0", observation.getPath().toString());
         assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId());
+        Set<Observation> observations = helper.server.getObservationService()
+                .getObservations(helper.getCurrentRegistration());
+        assertTrue("We should have only on observation", observations.size() == 1);
+        assertTrue("New observation is not there", observations.contains(observation));
 
         // *** HACK send time-stamped notification as Leshan client does not support it *** //
         // create time-stamped nodes
@@ -303,6 +328,10 @@ public class ObserveTest {
         Observation observation = observeResponse.getObservation();
         assertEquals("/3", observation.getPath().toString());
         assertEquals(helper.getCurrentRegistration().getId(), observation.getRegistrationId());
+        Set<Observation> observations = helper.server.getObservationService()
+                .getObservations(helper.getCurrentRegistration());
+        assertTrue("We should have only on observation", observations.size() == 1);
+        assertTrue("New observation is not there", observations.contains(observation));
 
         // *** HACK send time-stamped notification as Leshan client does not support it *** //
         // create time-stamped nodes
diff --git a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/ObservationServiceTest.java b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/ObservationServiceTest.java
index 8b597a48..7d71ecea 100644
--- a/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/ObservationServiceTest.java
+++ b/leshan-server-cf/src/test/java/org/eclipse/leshan/server/californium/impl/ObservationServiceTest.java
@@ -18,7 +18,6 @@ package org.eclipse.leshan.server.californium.impl;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
@@ -27,7 +26,9 @@ import org.eclipse.leshan.LwM2m;
 import org.eclipse.leshan.core.node.LwM2mPath;
 import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder;
 import org.eclipse.leshan.core.observation.Observation;
+import org.eclipse.leshan.core.request.ObserveRequest;
 import org.eclipse.leshan.server.californium.CaliforniumRegistrationStore;
+import org.eclipse.leshan.server.californium.ObserveUtil;
 import org.eclipse.leshan.server.model.StandardModelProvider;
 import org.eclipse.leshan.server.registration.Registration;
 import org.junit.Assert;
@@ -140,9 +141,8 @@ public class ObservationServiceTest {
         coapRequest.setObserve();
         coapRequest.setDestination(support.registration.getAddress());
         coapRequest.setDestinationPort(support.registration.getPort());
-        Map<String, String> context = new HashMap<>();
-        context.put(CoapRequestBuilder.CTX_REGID, registrationId);
-        context.put(CoapRequestBuilder.CTX_LWM2M_PATH, target.toString());
+        Map<String, String> context = ObserveUtil.createCoapObserveRequestContext(registration.getEndpoint(),
+                registrationId, new ObserveRequest(target.toString()));
         coapRequest.setUserContext(context);
 
         store.add(new org.eclipse.californium.core.observe.Observation(coapRequest, null));