Skip to content

Commit

Permalink
#651 : fix JSON content format implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
sbernard31 committed Jan 2, 2020
1 parent feefe02 commit d468294
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,64 +80,62 @@ public static List<TimestampedLwM2mNode> decodeTimestamped(byte[] content, LwM2m
}
}

private static List<TimestampedLwM2mNode> parseJSON(JsonRootObject jsonObject, LwM2mPath path, LwM2mModel model,
Class<? extends LwM2mNode> nodeClass) throws CodecException {
private static List<TimestampedLwM2mNode> parseJSON(JsonRootObject jsonObject, LwM2mPath requestPath,
LwM2mModel model, Class<? extends LwM2mNode> nodeClass) throws CodecException {

LOG.trace("Parsing JSON content for path {}: {}", path, jsonObject);
LOG.trace("Parsing JSON content for path {}: {}", requestPath, jsonObject);

// Group JSON entry by time-stamp
Map<Long, Collection<JsonArrayEntry>> jsonEntryByTimestamp = groupJsonEntryByTimestamp(jsonObject);

// Extract baseName
LwM2mPath baseName = extractAndValidateBaseName(jsonObject, path);
if (baseName == null)
baseName = path; // if no base name, use request path as base name
String baseName = jsonObject.getBaseName() == null ? "" : jsonObject.getBaseName();

// fill time-stamped nodes collection
List<TimestampedLwM2mNode> timestampedNodes = new ArrayList<>();
for (Entry<Long, Collection<JsonArrayEntry>> entryByTimestamp : jsonEntryByTimestamp.entrySet()) {

// Group JSON entry by instance
Map<Integer, Collection<JsonArrayEntry>> jsonEntryByInstanceId = groupJsonEntryByInstanceId(
entryByTimestamp.getValue(), baseName);
entryByTimestamp.getValue(), baseName, requestPath);

// Create lwm2m node
LwM2mNode node;
if (nodeClass == LwM2mObject.class) {
Collection<LwM2mObjectInstance> instances = new ArrayList<>();
for (Entry<Integer, Collection<JsonArrayEntry>> entryByInstanceId : jsonEntryByInstanceId.entrySet()) {
Map<Integer, LwM2mResource> resourcesMap = extractLwM2mResources(entryByInstanceId.getValue(),
baseName, model);
baseName, model, requestPath);

instances.add(new LwM2mObjectInstance(entryByInstanceId.getKey(), resourcesMap.values()));
}

node = new LwM2mObject(baseName.getObjectId(), instances);
node = new LwM2mObject(requestPath.getObjectId(), instances);
} else if (nodeClass == LwM2mObjectInstance.class) {
// validate we have resources for only 1 instance
if (jsonEntryByInstanceId.size() != 1)
throw new CodecException("One instance expected in the payload [path:%s]", path);
throw new CodecException("One instance expected in the payload [path:%s]", requestPath);

// Extract resources
Entry<Integer, Collection<JsonArrayEntry>> instanceEntry = jsonEntryByInstanceId.entrySet().iterator()
.next();
Map<Integer, LwM2mResource> resourcesMap = extractLwM2mResources(instanceEntry.getValue(), baseName,
model);
model, requestPath);

// Create instance
node = new LwM2mObjectInstance(instanceEntry.getKey(), resourcesMap.values());
} else if (nodeClass == LwM2mResource.class) {
// validate we have resources for only 1 instance
if (jsonEntryByInstanceId.size() > 1)
throw new CodecException("Only one instance expected in the payload [path:%s]", path);
throw new CodecException("Only one instance expected in the payload [path:%s]", requestPath);

// Extract resources
Map<Integer, LwM2mResource> resourcesMap = extractLwM2mResources(
jsonEntryByInstanceId.values().iterator().next(), baseName, model);
jsonEntryByInstanceId.values().iterator().next(), baseName, model, requestPath);

// validate there is only 1 resource
if (resourcesMap.size() != 1)
throw new CodecException("One resource should be present in the payload [path:%s]", path);
throw new CodecException("One resource should be present in the payload [path:%s]", requestPath);

node = resourcesMap.values().iterator().next();
} else {
Expand Down Expand Up @@ -221,12 +219,12 @@ public int compare(Long o1, Long o2) {
* @return a map (instanceId => collection of JsonArrayEntry)
*/
private static Map<Integer, Collection<JsonArrayEntry>> groupJsonEntryByInstanceId(
Collection<JsonArrayEntry> jsonEntries, LwM2mPath baseName) throws CodecException {
Collection<JsonArrayEntry> jsonEntries, String baseName, LwM2mPath requestPath) throws CodecException {
Map<Integer, Collection<JsonArrayEntry>> result = new HashMap<>();

for (JsonArrayEntry e : jsonEntries) {
// Build resource path
LwM2mPath nodePath = baseName.append(e.getName());
LwM2mPath nodePath = extractAndValidatePath(baseName, e.getName() == null ? "" : e.getName(), requestPath);

// Validate path
if (!nodePath.isResourceInstance() && !nodePath.isResource()) {
Expand All @@ -247,45 +245,49 @@ private static Map<Integer, Collection<JsonArrayEntry>> groupJsonEntryByInstance
}

// Create an entry for an empty instance if possible
if (result.isEmpty() && baseName.getObjectInstanceId() != null) {
result.put(baseName.getObjectInstanceId(), new ArrayList<JsonArrayEntry>());
if (result.isEmpty()) {
if (baseName.isEmpty()) {
// search object instance id in request path
if (requestPath.getObjectInstanceId() != null) {
result.put(requestPath.getObjectInstanceId(), new ArrayList<JsonArrayEntry>());
}
} else {
// search object instance id in basename
LwM2mPath basePath = extractAndValidatePath(baseName, "", requestPath);
if (basePath.getObjectInstanceId() != null)
result.put(basePath.getObjectInstanceId(), new ArrayList<JsonArrayEntry>());
}
}
return result;
}

private static LwM2mPath extractAndValidateBaseName(JsonRootObject jsonObject, LwM2mPath requestPath)
private static LwM2mPath extractAndValidatePath(String baseName, String name, LwM2mPath requestPath)
throws CodecException {
// Check baseName is valid
if (jsonObject.getBaseName() != null && !jsonObject.getBaseName().isEmpty()) {
LwM2mPath bnPath = new LwM2mPath(jsonObject.getBaseName());

// check returned base name path is under requested path
if (requestPath.getObjectId() != null && bnPath.getObjectId() != null) {
if (!bnPath.getObjectId().equals(requestPath.getObjectId())) {
throw new CodecException("Basename path [%s] does not match requested path [%s].", bnPath,
LwM2mPath path = new LwM2mPath(baseName + name);

// check returned path is under requested path
if (requestPath.getObjectId() != null && path.getObjectId() != null) {
if (!path.getObjectId().equals(requestPath.getObjectId())) {
throw new CodecException("resource path [%s] does not match requested path [%s].", path, requestPath);
}
if (requestPath.getObjectInstanceId() != null && path.getObjectInstanceId() != null) {
if (!path.getObjectInstanceId().equals(requestPath.getObjectInstanceId())) {
throw new CodecException("Basename path [%s] does not match requested path [%s].", path,
requestPath);
}
if (requestPath.getObjectInstanceId() != null && bnPath.getObjectInstanceId() != null) {
if (!bnPath.getObjectInstanceId().equals(requestPath.getObjectInstanceId())) {
throw new CodecException("Basename path [%s] does not match requested path [%s].", bnPath,
if (requestPath.getResourceId() != null && path.getResourceId() != null) {
if (!path.getResourceId().equals(requestPath.getResourceId())) {
throw new CodecException("Basename path [%s] does not match requested path [%s].", path,
requestPath);
}
if (requestPath.getResourceId() != null && bnPath.getResourceId() != null) {
if (!bnPath.getResourceId().equals(requestPath.getResourceId())) {
throw new CodecException("Basename path [%s] does not match requested path [%s].", bnPath,
requestPath);
}
}
}
}
return bnPath;
}
return null;

return path;
}

private static Map<Integer, LwM2mResource> extractLwM2mResources(Collection<JsonArrayEntry> jsonArrayEntries,
LwM2mPath baseName, LwM2mModel model) throws CodecException {
String baseName, LwM2mModel model, LwM2mPath requestPath) throws CodecException {
if (jsonArrayEntries == null)
return Collections.emptyMap();

Expand All @@ -294,8 +296,13 @@ private static Map<Integer, LwM2mResource> extractLwM2mResources(Collection<Json
Map<LwM2mPath, Map<Integer, JsonArrayEntry>> multiResourceMap = new HashMap<>();
for (JsonArrayEntry resourceElt : jsonArrayEntries) {

// Build resource path
LwM2mPath nodePath = baseName.append(resourceElt.getName());
// Build resource path (path validation was already done in groupJsonEntryByInstanceId
LwM2mPath nodePath;
if (resourceElt.getName() == null) {
nodePath = new LwM2mPath(baseName);
} else {
nodePath = new LwM2mPath(baseName + resourceElt.getName());
}

// handle LWM2M resources
if (nodePath.isResourceInstance()) {
Expand Down Expand Up @@ -356,13 +363,21 @@ private static Map<Integer, LwM2mResource> extractLwM2mResources(Collection<Json
}

// If we found nothing, we try to create an empty multi-instance resource
if (lwM2mResourceMap.isEmpty() && baseName.isResource()) {
ResourceModel resourceModel = model.getResourceModel(baseName.getObjectId(), baseName.getResourceId());
// We create it only if this respect the model
if (resourceModel == null || resourceModel.multiple) {
Type resourceType = getResourceType(baseName, model, null);
lwM2mResourceMap.put(baseName.getResourceId(), LwM2mMultipleResource
.newResource(baseName.getResourceId(), new HashMap<Integer, Object>(), resourceType));
if (lwM2mResourceMap.isEmpty()) {
LwM2mPath path;
if (baseName.isEmpty()) {
path = requestPath;
} else {
path = extractAndValidatePath(baseName, "", requestPath);
}
if (path.getObjectId() != null && path.getResourceId() != null) {
ResourceModel resourceModel = model.getResourceModel(path.getObjectId(), path.getResourceId());
// We create it only if this respect the model
if (resourceModel == null || resourceModel.multiple) {
Type resourceType = getResourceType(path, model, null);
lwM2mResourceMap.put(path.getResourceId(), LwM2mMultipleResource.newResource(path.getResourceId(),
new HashMap<Integer, Object>(), resourceType));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static byte[] encode(LwM2mNode node, LwM2mPath path, LwM2mModel model, Lw
node.accept(internalEncoder);
JsonRootObject jsonObject = new JsonRootObject();
jsonObject.setResourceList(internalEncoder.resourceList);
jsonObject.setBaseName(path.toString());
jsonObject.setBaseName(internalEncoder.baseName);
return LwM2mJson.toJsonLwM2m(jsonObject).getBytes();
}

Expand All @@ -70,6 +70,7 @@ public static byte[] encodeTimestampedData(List<TimestampedLwM2mNode> timestampe

InternalEncoder internalEncoder = new InternalEncoder();
ArrayList<JsonArrayEntry> entries = new ArrayList<>();
String baseName = null;
for (TimestampedLwM2mNode timestampedLwM2mNode : timestampedNodes) {
internalEncoder.objectId = path.getObjectId();
internalEncoder.model = model;
Expand All @@ -79,10 +80,19 @@ public static byte[] encodeTimestampedData(List<TimestampedLwM2mNode> timestampe
internalEncoder.timestamp = timestampedLwM2mNode.getTimestamp();
timestampedLwM2mNode.getNode().accept(internalEncoder);
entries.addAll(internalEncoder.resourceList);
if (baseName != null) {
if (!baseName.equals(internalEncoder.baseName)) {
throw new CodecException(
"Unexpected baseName %s (%s expected) when encoding timestamped nodes for request %s",
internalEncoder.baseName, baseName, path);
}
} else {
baseName = internalEncoder.baseName;
}
}
JsonRootObject jsonObject = new JsonRootObject();
jsonObject.setResourceList(entries);
jsonObject.setBaseName(path.toString());
jsonObject.setBaseName(internalEncoder.baseName);
return LwM2mJson.toJsonLwM2m(jsonObject).getBytes();
}

Expand All @@ -96,6 +106,7 @@ private static class InternalEncoder implements LwM2mNodeVisitor {

// visitor output
private ArrayList<JsonArrayEntry> resourceList = null;
private String baseName = null;

@Override
public void visit(LwM2mObject object) {
Expand All @@ -104,6 +115,7 @@ public void visit(LwM2mObject object) {
if (!requestPath.isObject()) {
throw new CodecException("Invalid request path %s for JSON object encoding", requestPath);
}
baseName = requestPath.toString() + "/";

// Create resources
resourceList = new ArrayList<>();
Expand All @@ -129,6 +141,7 @@ public void visit(LwM2mObjectInstance instance) {
} else {
throw new CodecException("Invalid request path %s for JSON instance encoding", requestPath);
}
baseName = requestPath + "/";
// Create resources
resourceList.addAll(lwM2mResourceToJsonArrayEntry(prefixPath, timestamp, resource));
}
Expand All @@ -140,8 +153,12 @@ public void visit(LwM2mResource resource) {
if (!requestPath.isResource()) {
throw new CodecException("Invalid request path %s for JSON resource encoding", requestPath);
}

resourceList = lwM2mResourceToJsonArrayEntry("", timestamp, resource);
if (resource.isMultiInstances()) {
baseName = requestPath.toString() + "/";
} else {
baseName = requestPath.toString();
}
resourceList = lwM2mResourceToJsonArrayEntry(null, timestamp, resource);
}

private ArrayList<JsonArrayEntry> lwM2mResourceToJsonArrayEntry(String resourcePath, Long timestamp,
Expand Down Expand Up @@ -183,7 +200,7 @@ private ArrayList<JsonArrayEntry> lwM2mResourceToJsonArrayEntry(String resourceP
jsonResourceElt.setTime(timestamp);

// Convert value using expected type
LwM2mPath lwM2mResourcePath = new LwM2mPath(resourcePath);
LwM2mPath lwM2mResourcePath = resourcePath != null ? new LwM2mPath(resourcePath) : null;
this.setResourceValue(converter.convertValue(resource.getValue(), resource.getType(), expectedType,
lwM2mResourcePath), expectedType, jsonResourceElt, lwM2mResourcePath);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public class JsonArrayEntrySerDes extends JsonSerDes<JsonArrayEntry> {
@Override
public JsonObject jSerialize(JsonArrayEntry jae) {
JsonObject o = new JsonObject();
o.add("n", jae.getName());
if (jae.getName() != null)
o.add("n", jae.getName());
Type type = jae.getType();
if (type != null) {
switch (jae.getType()) {
Expand All @@ -43,7 +44,7 @@ public JsonObject jSerialize(JsonArrayEntry jae) {
o.add("sv", jae.getStringValue());
break;
default:
break;
throw new LwM2mJsonException("JsonArrayEntry MUST have a value : %s", jae);
}
}
if (jae.getTime() != null)
Expand Down Expand Up @@ -79,6 +80,10 @@ public JsonArrayEntry deserialize(JsonObject o) {
if (ov != null && ov.isString())
jae.setObjectLinkValue(ov.asString());

if (jae.getType() == null) {
throw new LwM2mJsonException("Missing value(v,bv,ov,sv) field for entry %s", o.toString());
}

return jae;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public JsonRootObject deserialize(JsonObject o) {
JsonValue e = o.get("e");
if (e != null)
jro.setResourceList(serDes.deserialize(e.asArray()));
else
throw new LwM2mJsonException("'e' field is missing for %s", o.toString());

JsonValue bn = o.get("bn");
if (bn != null && bn.isString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,22 @@
/**
* Exception thrown in case of JSON parsing error
*/
public class LwM2mJsonException extends Exception {
public class LwM2mJsonException extends RuntimeException {

private static final long serialVersionUID = 1L;

public LwM2mJsonException(String message) {
super(message);
}

public LwM2mJsonException(String message, Object... args) {
super(String.format(message, args));
}

public LwM2mJsonException(Exception e, String message, Object... args) {
super(String.format(message, args), e);
}

public LwM2mJsonException(String message, Exception cause) {
super(message, cause);
}
Expand Down
Loading

0 comments on commit d468294

Please sign in to comment.