Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

read composite on root path #1520

Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*******************************************************************************/
package org.eclipse.leshan.client.resource;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -28,10 +30,12 @@
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.eclipse.leshan.core.node.LwM2mRoot;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.request.ObserveCompositeRequest;
import org.eclipse.leshan.core.request.ObserveRequest;
Expand Down Expand Up @@ -74,9 +78,22 @@ public void removeListener(ObjectsListener listener) {
@Override
public ReadCompositeResponse read(LwM2mServer server, ReadCompositeRequest request) {
List<LwM2mPath> paths = request.getPaths();
// TODO: add checks for failure and log it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep I think some check should be done here.

The spec says :

The Read-Composite operation is treated as non-atomic and handled as best effort by the client. That is, if any of the requested resources do not have a valid value to return, they will not be included in the response.

(http://www.openmobilealliance.org/release/LightweightM2M/V1_1_1-20190617-A/HTML-Version/OMA-TS-LightweightM2M_Core-V1_1_1-20190617-A.html#6-3-8-0-638-Read-Composite-Operation)

So I think on failure, we should not stop the read-composite operation but just log as error.
(so error will not be seen at server side)

if (paths.size() == 1 && paths.get(0).isRoot()) {
// TODO implement read for "/" use case.
return ReadCompositeResponse.internalServerError("Not implemented yet");
Map<Integer, LwM2mObjectEnabler> objectEnablers = tree.getObjectEnablers();
List<LwM2mObject> lwM2mObjects = new ArrayList<>();

for (Map.Entry<Integer, LwM2mObjectEnabler> entry : objectEnablers.entrySet()) {
LwM2mObjectEnabler objectEnabler = entry.getValue();
ReadRequest readRequest = new ReadRequest(request.getRequestContentFormat(),
new LwM2mPath(entry.getKey()), request.getCoapRequest());
ReadResponse readResponse = objectEnabler.read(server, readRequest);
if (readResponse.isSuccess()) {
lwM2mObjects.add((LwM2mObject) readResponse.getContent());
}
}
LwM2mRoot lwM2mRoot = new LwM2mRoot(lwM2mObjects);
return ReadCompositeResponse.success(Collections.singletonMap(paths.get(0), lwM2mRoot));
}

// Read Nodes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ public interface LwM2mNodeVisitor {

void visit(LwM2mResourceInstance instance);

default void visit(LwM2mRoot root) {
}
Comment on lines +31 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we should add a default visit.
Better to add to all visitor the new function and raise exception is not supported.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright (c) 2013-2015 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:
* Orange Polska S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.leshan.core.node;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class LwM2mRoot implements LwM2mNode {
private final Map<Integer, LwM2mObject> objects;

public LwM2mRoot(Collection<LwM2mObject> objects) {
LwM2mNodeUtil.validateNotNull(objects, "objects MUST NOT be null");
HashMap<Integer, LwM2mObject> objectsMap = new HashMap<>(objects.size());
for (LwM2mObject object : objects) {
LwM2mObject previous = objectsMap.put(object.getId(), object);
if (previous != null) {
throw new LwM2mNodeException(
"Unable to create LwM2mRoot : there are several objects with the same id %d", object.getId());
}
}
this.objects = Collections.unmodifiableMap(objectsMap);
}

@Override
public int getId() {
return 0;
}

@Override
public void accept(LwM2mNodeVisitor visitor) {
// TODO: visitors currently don't support a visit from LwM2mRoot
visitor.visit(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure to get this TODO ?

}

@Override
public String toPrettyString(LwM2mPath path) {
return null;
}

@Override
public StringBuilder appendPrettyNode(StringBuilder b, LwM2mPath path) {
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be implemented.


public Map<Integer, LwM2mObject> getObjects() {
return objects;
}

// TODO: add compare and other stuff that a LwM2m node should have
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep this should probably added.

(See #1504 maybe this could help)

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.eclipse.leshan.core.node.LwM2mRoot;
import org.eclipse.leshan.core.node.TimestampedLwM2mNode;
import org.eclipse.leshan.core.node.TimestampedLwM2mNodes;
import org.eclipse.leshan.core.node.codec.cbor.LwM2mNodeCborDecoder;
Expand Down Expand Up @@ -264,6 +265,8 @@ public static Class<? extends LwM2mNode> nodeClassFromPath(LwM2mPath path) {
return LwM2mResource.class;
} else if (path.isResourceInstance()) {
return LwM2mResourceInstance.class;
} else if (path.isRoot()) {
return LwM2mRoot.class;
}
throw new IllegalArgumentException("invalid path level: " + path);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.eclipse.leshan.core.node.LwM2mRoot;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.core.node.TimestampedLwM2mNode;
Expand Down Expand Up @@ -228,7 +229,22 @@ private LwM2mNode parseRecords(Collection<LwM2mResolvedSenMLRecord> records, LwM

// Create lwm2m node
LwM2mNode node = null;
if (nodeClass == LwM2mObject.class) {
if (nodeClass == LwM2mRoot.class) {
Collection<LwM2mObject> objects = new ArrayList<>();
Map<Integer, Collection<LwM2mResolvedSenMLRecord>> recordsByObjectId = groupRecordsByObjectId(records);
for (Entry<Integer, Collection<LwM2mResolvedSenMLRecord>> entryByObjectId : recordsByObjectId.entrySet()) {
Collection<LwM2mObjectInstance> instances = new ArrayList<>();
recordsByInstanceId = groupRecordsByInstanceId(entryByObjectId.getValue());
for (Entry<Integer, Collection<LwM2mResolvedSenMLRecord>> entryByInstanceId : recordsByInstanceId
.entrySet()) {
Map<Integer, LwM2mResource> resourcesMap = extractLwM2mResources(entryByInstanceId.getValue(), path,
model);
instances.add(new LwM2mObjectInstance(entryByInstanceId.getKey(), resourcesMap.values()));
}
objects.add(new LwM2mObject(entryByObjectId.getKey(), instances));
}
node = new LwM2mRoot(objects);
} else if (nodeClass == LwM2mObject.class) {
Collection<LwM2mObjectInstance> instances = new ArrayList<>();
for (Entry<Integer, Collection<LwM2mResolvedSenMLRecord>> entryByInstanceId : recordsByInstanceId
.entrySet()) {
Expand Down Expand Up @@ -431,6 +447,28 @@ private Map<Integer, Collection<LwM2mResolvedSenMLRecord>> groupRecordsByInstanc
return result;
}

/**
* Group all SenML record by objectId
*
* @return a map (objectId => collection of SenML Record)
*/
private Map<Integer, Collection<LwM2mResolvedSenMLRecord>> groupRecordsByObjectId(
Collection<LwM2mResolvedSenMLRecord> records) throws CodecException {
Map<Integer, Collection<LwM2mResolvedSenMLRecord>> result = new HashMap<>();
for (LwM2mResolvedSenMLRecord record : records) {
// Get SenML records for this object
Collection<LwM2mResolvedSenMLRecord> recordForObject = result.get(record.getPath().getObjectId());
if (recordForObject == null) {
recordForObject = new ArrayList<>();
result.put(record.getPath().getObjectId(), recordForObject);
}

// Add it to the list
recordForObject.add(record);
}
return result;
}

private Map<Integer, LwM2mResource> extractLwM2mResources(Collection<LwM2mResolvedSenMLRecord> records,
LwM2mPath requestPath, LwM2mModel model) throws CodecException {
if (records == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.eclipse.leshan.core.node.LwM2mRoot;
import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.core.node.TimestampedLwM2mNode;
import org.eclipse.leshan.core.node.TimestampedLwM2mNodes;
Expand Down Expand Up @@ -200,14 +201,34 @@ public byte[] encodeTimestampedNodes(TimestampedLwM2mNodes timestampedNodes, LwM

private class InternalEncoder implements LwM2mNodeVisitor {
// visitor inputs
private int objectId;
private Integer objectId;
private LwM2mModel model;
private LwM2mPath requestPath;
private LwM2mValueConverter converter;

// visitor output
private ArrayList<SenMLRecord> records = new ArrayList<>();

@Override
public void visit(LwM2mRoot root) {
LOG.trace("Encoding Root into SenML");
// Validate request path
if (!requestPath.isRoot()) {
throw new CodecException("Invalid request path %s for root encoding", requestPath);
}

// Create SenML records
for (LwM2mObject object : root.getObjects().values()) {
for (LwM2mObjectInstance instance : object.getInstances().values()) {
for (LwM2mResource resource : instance.getResources().values()) {
String prefixPath = object.getId() + "/" + instance.getId() + "/" + resource.getId();
this.objectId = object.getId();
lwM2mResourceToSenMLRecord(prefixPath, resource);
}
}
}
}

@Override
public void visit(LwM2mObject object) {
LOG.trace("Encoding Object {} into SenML", object);
Expand Down Expand Up @@ -302,7 +323,7 @@ private void addSenMLRecord(String recordName, Type valueType, Type expectedType
String n = recordName == null ? "" : recordName;

// Add slash if necessary
if (!n.isEmpty()) {
if (!n.isEmpty() && !bn.equals("/")) {
bn += "/";
Comment on lines +328 to 329
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds strange I need to look at this deeper.

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;

import org.eclipse.leshan.core.endpoint.Protocol;
import org.eclipse.leshan.core.model.ResourceModel.Type;
import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.eclipse.leshan.core.node.LwM2mRoot;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.request.ReadCompositeRequest;
Expand Down Expand Up @@ -107,6 +112,110 @@ public void stop() throws InterruptedException {
/*---------------------------------/
* Tests
* -------------------------------*/

@TestAllCases
public void can_read_root(ContentFormat requestContentFormat, ContentFormat responseContentFormat,
Protocol givenProtocol, String givenClientEndpointProvider, String givenServerEndpointProvider)
throws InterruptedException {
// read root object
ReadCompositeResponse response = server.send(currentRegistration,
new ReadCompositeRequest(requestContentFormat, responseContentFormat, "/"));

// verify result
// assertEquals(CONTENT, response.getCode());
// assertContentFormat(responseContentFormat, response);
Comment on lines +132 to +134
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be removed ?


assertThat(response) //
.hasCode(CONTENT) //
.hasCode(response.getCode()).hasContentFormat(responseContentFormat, givenServerEndpointProvider);

LwM2mRoot root = (LwM2mRoot) response.getContent("/");
Set<Integer> objectIdsFromRootObject = root.getObjects().keySet();
Set<Integer> objectIdsFromObjectTree = new HashSet<>(Arrays.asList(1, 3, 3442));

// assertEquals(objectIdsFromObjectTree, objectIdsFromRootObject);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be removed ?

assertThat(objectIdsFromObjectTree).isEqualTo(objectIdsFromRootObject);

}

@TestAllCases
public void can_read_root_single_resource(ContentFormat requestContentFormat, ContentFormat responseContentFormat,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure to understand why there is several test where all do a ReadComposite on / ?
Maybe better to have only 1 test OR I missed something ?

A possible way to test it could be to :

  • do a ReadComposite.
  • Check that all expected object are here. (maybe using Registration.getSupportedObject() ?)
  • (optionally) on instances too (using Registration.getAvailableInstances())
  • Then for each LwM2mObject in ReadComposite response, you do a ReadRequest on this object and you check that LwM2mObject returned is equals of LwM2mObject from ReadComposite response.

(This will work if no value change between 2 call, if some change let me know)

Protocol givenProtocol, String givenClientEndpointProvider, String givenServerEndpointProvider)
throws InterruptedException {
// read device model number
ReadCompositeResponse response = server.send(currentRegistration,
new ReadCompositeRequest(requestContentFormat, responseContentFormat, "/"));

// verify result
assertThat(response) //
.hasCode(CONTENT) //
.hasContentFormat(responseContentFormat, givenServerEndpointProvider);

LwM2mRoot root = (LwM2mRoot) response.getContent("/");
LwM2mResource resource1 = root.getObjects().get(1).getInstance(0).getResource(1);
assertThat(resource1.getId()).isEqualTo(1);
assertThat(resource1.getValue()).isEqualTo(300L);
assertThat(resource1.getType()).isEqualTo(Type.INTEGER);

LwM2mResource resource110 = root.getObjects().get(3442).getInstance(0).getResource(110);
assertThat(resource110.getId()).isEqualTo(110);
assertThat(resource110.getValue()).isEqualTo("initial value");
assertThat(resource110.getType()).isEqualTo(Type.STRING);

LwM2mResource resource0 = root.getObjects().get(3).getInstance(0).getResource(0);
assertThat(resource0.getId()).isEqualTo(0);
assertThat(resource0.getValue()).isEqualTo("Eclipse Leshan");
assertThat(resource0.getType()).isEqualTo(Type.STRING);
}

@TestAllCases
public void can_read_root_multiple_resource(ContentFormat requestContentFormat, ContentFormat responseContentFormat,
Protocol givenProtocol, String givenClientEndpointProvider, String givenServerEndpointProvider)
throws InterruptedException {
// read device model number
ReadCompositeResponse response = server.send(currentRegistration,
new ReadCompositeRequest(requestContentFormat, responseContentFormat, "/"));

// verify result
assertThat(response) //
.hasCode(CONTENT) //
.hasContentFormat(responseContentFormat, givenServerEndpointProvider);

LwM2mRoot root = (LwM2mRoot) response.getContent("/");
LwM2mResource resource = root.getObjects().get(3442).getInstance(0).getResource(1140);

LwM2mResourceInstance instance = resource.getInstance(0);
assertThat(instance.getId()).isEqualTo(0);
assertThat(instance.getValue()).isEqualTo(true);
assertThat(instance.getType()).isEqualTo(Type.BOOLEAN);
}

@TestAllCases
public void can_read_root_single_and_multiple_resource(ContentFormat requestContentFormat,
ContentFormat responseContentFormat, Protocol givenProtocol, String givenClientEndpointProvider,
String givenServerEndpointProvider) throws InterruptedException {
// read device model number
ReadCompositeResponse response = server.send(currentRegistration,
new ReadCompositeRequest(requestContentFormat, responseContentFormat, "/"));

// verify result
assertThat(response) //
.hasCode(CONTENT) //
.hasContentFormat(responseContentFormat, givenServerEndpointProvider);

LwM2mRoot root = (LwM2mRoot) response.getContent("/");

LwM2mResource resource = root.getObjects().get(3442).getInstance(0).getResource(1120);
assertThat(resource.getId()).isEqualTo(1120);
assertThat(resource.getType()).isEqualTo(Type.INTEGER);

LwM2mResourceInstance instance = resource.getInstance(0);
assertThat(instance.getId()).isEqualTo(0);
assertThat(instance.getValue()).isEqualTo(1024L);
assertThat(instance.getType()).isEqualTo(Type.INTEGER);
}


@TestAllCases
public void can_read_resources(ContentFormat requestContentFormat, ContentFormat responseContentFormat,
Protocol givenProtocol, String givenClientEndpointProvider, String givenServerEndpointProvider)
Expand Down
Loading