com.microsoft.azure.sdk.iot.deps.serializer.MethodParser Maven / Gradle / Ivy
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package com.microsoft.azure.sdk.iot.deps.serializer;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import static com.microsoft.azure.sdk.iot.deps.serializer.ParserUtility.getJsonObjectValue;
import static com.microsoft.azure.sdk.iot.deps.serializer.ParserUtility.resolveJsonElement;
/**
* Representation of a single Direct Method Access collection with a Json serializer and deserializer.
*/
public class MethodParser
{
protected enum Operation
{
invoke,
response,
payload,
none
}
@Expose(serialize = false, deserialize = false)
private Operation operation;
/* Codes_SRS_METHODPARSER_21_015: [The toJsonElement shall include name as `methodName` in the json.] */
private static final String METHOD_NAME_TAG = "methodName";
@Expose(serialize = true, deserialize = false)
@SerializedName(METHOD_NAME_TAG)
private String name;
/* Codes_SRS_METHODPARSER_21_016: [The toJsonElement shall include responseTimeout in seconds as `responseTimeoutInSeconds` in the json.] */
private static final String RESPONSE_TIMEOUT_IN_SECONDS_TAG = "responseTimeoutInSeconds";
@Expose(serialize = true, deserialize = false)
@SerializedName(RESPONSE_TIMEOUT_IN_SECONDS_TAG)
private Long responseTimeout;
/* Codes_SRS_METHODPARSER_21_031: [The toJsonElement shall include connectTimeout in seconds as `connectTimeoutInSeconds` in the json.] */
private static final String CONNECT_TIMEOUT_IN_SECONDS_TAG = "connectTimeoutInSeconds";
@Expose(serialize = true, deserialize = false)
@SerializedName(CONNECT_TIMEOUT_IN_SECONDS_TAG)
private Long connectTimeout;
/* Codes_SRS_METHODPARSER_21_024: [The class toJsonElement include status as `status` in the json.] */
private static final String STATUS_TAG = "status";
@Expose(serialize = false, deserialize = true)
@SerializedName(STATUS_TAG)
private Integer status;
/* Codes_SRS_METHODPARSER_21_018: [The class toJsonElement include payload as `payload` in the json.] */
private static final String PAYLOAD_TAG = "payload";
@SerializedName(PAYLOAD_TAG)
private Object payload;
/**
* CONSTRUCTOR
* Create a MethodParser instance with provided values.
*/
public MethodParser()
{
/* Codes_SRS_METHODPARSER_21_029: [The constructor shall create an instance of the MethodParser.] */
/* Codes_SRS_METHODPARSER_21_030: [The constructor shall initialize all data in the collection as null.] */
this.name = null;
this.responseTimeout = null;
this.connectTimeout = null;
this.status = null;
this.payload = null;
/* Codes_SRS_METHODPARSER_21_022: [The constructor shall initialize the method operation as `none`.] */
this.operation = Operation.none;
}
/**
* CONSTRUCTOR
* Create a MethodParser instance with provided values.
*
* @param name - method name [required].
* @param responseTimeout - maximum interval of time, in seconds, that the Direct Method will wait for answer. It can be {@code null}.
* @param connectTimeout - maximum interval of time, in seconds, that the Direct Method will wait for the connection. It can be {@code null}.
* @param payload - Object that contains the payload defined by the user. It can be {@code null}.
* @throws IllegalArgumentException This exception is thrown if the one of the provided information do not fits the requirements.
*/
public MethodParser(String name, Long responseTimeout, Long connectTimeout, Object payload) throws IllegalArgumentException
{
/* Codes_SRS_METHODPARSER_21_001: [The constructor shall create an instance of the MethodParser.] */
this();
/* Codes_SRS_METHODPARSER_21_003: [All Strings are case sensitive.] */
/* Codes_SRS_METHODPARSER_21_004: [If the `name` is null, empty, contains more than 128 chars, or illegal char (`$`, `.`, space), the constructor shall throw IllegalArgumentException.] */
validateKey(name);
if(responseTimeout != null)
{
/* Codes_SRS_METHODPARSER_21_005: [If the responseTimeout is a negative number, the constructor shall throw IllegalArgumentException.] */
validateTimeout(responseTimeout);
}
if(connectTimeout != null)
{
/* Codes_SRS_METHODPARSER_21_033: [If the connectTimeout is a negative number, the constructor shall throw IllegalArgumentException.] */
validateTimeout(connectTimeout);
}
/* Codes_SRS_METHODPARSER_21_002: [The constructor shall update the method collection using the provided information.] */
this.name = name;
this.responseTimeout = responseTimeout;
this.connectTimeout = connectTimeout;
this.payload = payload;
/* Codes_SRS_METHODPARSER_21_023: [The constructor shall initialize the method operation as `invoke`.] */
this.operation = Operation.invoke;
}
/**
* CONSTRUCTOR
* Create a MethodParser instance with provided values.
*
* @param payload - Object that contains the payload defined by the user. It can be {@code null}.
*/
public MethodParser(Object payload)
{
/* Codes_SRS_METHODPARSER_21_020: [The constructor shall create an instance of the MethodParser.] */
this();
/* Codes_SRS_METHODPARSER_21_021: [The constructor shall update the method collection using the provided information.] */
this.payload = payload;
/* Codes_SRS_METHODPARSER_21_034: [The constructor shall set the method operation as `payload`.] */
this.operation = Operation.payload;
}
/**
* Set the Method collection with the provided information in the json.
*
* @param json - Json with the information to change the collection.
* - If contains `methodName`, it is a full method including `methodName`, `responseTimeoutInSeconds`, `connectTimeoutInSeconds`, and `payload`.
* - If contains `status`, it is a response with `status` and `payload`.
* - Otherwise, it is only `payload`.
* @throws IllegalArgumentException This exception is thrown if the one of the provided information do not fits the requirements.
*/
public synchronized void fromJson(String json) throws IllegalArgumentException
{
if((json == null) || json.isEmpty())
{
/* Codes_SRS_METHODPARSER_21_008: [If the provided json is null, empty, or not valid, the fromJson shall throws IllegalArgumentException.] */
throw new IllegalArgumentException("Invalid json.");
}
JsonParser jsonParser = new JsonParser();
try
{
JsonElement jsonElement = jsonParser.parse(json);
if (jsonElement instanceof JsonPrimitive || jsonElement instanceof JsonArray)
{
/**
* Basic JSON String or Array
* Ex:
* "this is payload."
* 1
* true
* 2.0e10
* 2.6
*/
this.operation = Operation.payload;
this.payload = resolveJsonElement(jsonElement);
}
else if (jsonElement instanceof JsonObject)
{
JsonObject jsonObject = (JsonObject) jsonElement;
JsonElement statusTagNode = jsonObject.get(STATUS_TAG);
JsonElement methodNameNode = jsonObject.get(METHOD_NAME_TAG);
if (methodNameNode == null)
{
if (statusTagNode == null)
{
/**
* Codes_SRS_METHODPARSER_21_010: [If the json contains any payload without `methodName` or `status` identification, the fromJson shall parse only the payload, and set the operation as `payload`]
* Ex:
* {
* "input1": "someInput",
* "input2": "anotherInput"
* }
*/
operation = Operation.payload;
payload = getJsonObjectValue(jsonObject);
}
else
{
/**
* Codes_SRS_METHODPARSER_21_011: [If the json contains the `status` identification, the fromJson shall parse both status and payload, and set the operation as `response`.]
* Ex:
* {
* "status": 201,
* "payload": {"AnyValidPayload" : "" }
* }
*/
operation = Operation.response;
if (statusTagNode.isJsonPrimitive())
{
status = statusTagNode.getAsInt();
}
JsonElement payloadNode = jsonObject.get(PAYLOAD_TAG);
if (payloadNode != null)
{
payload = resolveJsonElement(payloadNode);
}
}
}
else
{
if (statusTagNode == null)
{
/**
* Codes_SRS_METHODPARSER_21_009: [If the json contains the `methodName` identification, the fromJson shall parse the full method, and set the operation as `invoke`.]
* Ex:
* {
* "methodName": "reboot",
* "responseTimeoutInSeconds": 200,
* "connectTimeoutInSeconds": 5,
* "payload":
* {
* "input1": "someInput",
* "input2": "anotherInput"
* }
* }
*/
operation = Operation.invoke;
name = methodNameNode.getAsString();
JsonElement responseTimeoutNode = jsonObject.get(RESPONSE_TIMEOUT_IN_SECONDS_TAG);
if (responseTimeoutNode != null)
{
responseTimeout = responseTimeoutNode.getAsLong();
}
JsonElement connetionTimeoutNode = jsonObject.get(CONNECT_TIMEOUT_IN_SECONDS_TAG);
if (connetionTimeoutNode != null)
{
connectTimeout = connetionTimeoutNode.getAsLong();
}
JsonElement payloadNode = jsonObject.get(PAYLOAD_TAG);
if (payloadNode != null)
{
payload = resolveJsonElement(payloadNode);
}
}
else
{
/* Codes_SRS_METHODPARSER_21_008: [If the provided json is null, empty, or not valid, the fromJson shall throws IllegalArgumentException.] */
throw new IllegalArgumentException("Invoke method name and Status reported in the same json");
}
}
}
else
{
// JSON null, since string is not empty, it shouldn't reach here
throw new IllegalArgumentException("Invalid json.");
}
}
catch (Exception ex)
{
/* Codes_SRS_METHODPARSER_21_008: [If the provided json is null, empty, or not valid, the fromJson shall throws IllegalArgumentException.] */
throw new IllegalArgumentException("Malformed json.", ex);
}
}
/**
* Return an Integer with the response status.
*
* @return An integer with the status of the response. It can be {@code null}.
* @throws IllegalArgumentException This exception is thrown if the operation is not type of `response`.
*/
public Integer getStatus() throws IllegalArgumentException
{
/* Codes_SRS_METHODPARSER_21_035: [If the operation is not `response`, the getStatus shall throws IllegalArgumentException.] */
if(operation != Operation.response)
{
throw new IllegalArgumentException("No response to report status");
}
/* Codes_SRS_METHODPARSER_21_012: [The getStatus shall return an Integer with the status in the parsed json.] */
return this.status;
}
/**
* Return an Object with the payload.
*
* @return An Object with the payload. It can be {@code null}.
*/
public Object getPayload()
{
/* Codes_SRS_METHODPARSER_21_013: [The getPayload shall return an Object with the Payload in the parsed json.] */
if (payload instanceof JsonElement && ((JsonElement) payload).isJsonPrimitive() && ((JsonPrimitive) payload).isString())
{
return ((JsonPrimitive) payload).getAsString();
}
return payload;
}
/**
* Create a String with a json content that represents all the information in the method collection.
*
* @return String with the json content.
* @throws IllegalArgumentException This exception is thrown if the one of the provided information do not fits the requirements.
*/
public String toJson() throws IllegalArgumentException
{
/* Codes_SRS_METHODPARSER_21_014: [The toJson shall create a String with the full information in the method collection using json format, by using the toJsonElement.] */
return toJsonElement().toString();
}
@SuppressWarnings("unchecked")
private JsonElement jsonizePayload(Object payload)
{
if (payload == null)
{
return new JsonNull();
}
else if (payload instanceof JsonElement)
{
return (JsonElement) payload;
}
else if (payload instanceof Map)
{
JsonObject jsonObject = new JsonObject();
Set> entrySet = ((Map) payload).entrySet();
for (Entry entry : entrySet)
{
jsonObject.add(entry.getKey(), jsonizePayload(entry.getValue()));
}
return jsonObject;
}
else
{
JsonParser parser = new JsonParser();
try
{
String json = payload.toString();
JsonElement jsonElement = parser.parse(json);
if (jsonElement.isJsonNull())
{
return new JsonPrimitive(json);
}
else
{
return jsonElement;
}
}
catch (JsonSyntaxException e)
{
return new Gson().toJsonTree(payload);
}
}
}
/**
* Create a JsonElement with a content that represents all the information in the method collection.
*
* @return JsonElement with the content.
* @throws IllegalArgumentException This exception is thrown if the one of the provided information do not fits the requirements.
*/
public JsonElement toJsonElement() throws IllegalArgumentException
{
/* Codes_SRS_METHODPARSER_21_015: [The toJsonElement shall include name as `methodName` in the json.] */
/* Codes_SRS_METHODPARSER_21_016: [The toJsonElement shall include responseTimeout in seconds as `responseTimeoutInSeconds` in the json.] */
/* Codes_SRS_METHODPARSER_21_017: [If the responseTimeout is null, the toJsonElement shall not include the `responseTimeoutInSeconds` in the json.] */
/* Codes_SRS_METHODPARSER_21_031: [The toJsonElement shall include connectTimeout in seconds as `connectTimeoutInSeconds` in the json.] */
/* Codes_SRS_METHODPARSER_21_032: [If the connectTimeout is null, the toJsonElement shall not include the `connectTimeoutInSeconds` in the json.] */
/* Codes_SRS_METHODPARSER_21_018: [The class toJsonElement include payload as `payload` in the json.] */
/* Codes_SRS_METHODPARSER_21_019: [If the payload is null, the toJsonElement shall include `payload` with value `null`.] */
/* Codes_SRS_METHODPARSER_21_024: [The class toJsonElement include status as `status` in the json.] */
/* Codes_SRS_METHODPARSER_21_025: [If the status is null, the toJsonElement shall include `status` as `null`.] */
if (operation == Operation.invoke)
{
/**
* Codes_SRS_METHODPARSER_21_026: [If the method operation is `invoke`, the toJsonElement shall include the full method information in the json.]
* Ex:
* {
* "methodName": "reboot",
* "responseTimeoutInSeconds": 200,
* "connectTimeoutInSeconds": 5,
* "payload":
* {
* "input1": "someInput",
* "input2": "anotherInput"
* }
* }
*/
if ((name == null) || name.isEmpty())
{
throw new IllegalArgumentException("cannot invoke method with null name");
}
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(METHOD_NAME_TAG, name);
if (responseTimeout != null)
{
jsonObject.addProperty(RESPONSE_TIMEOUT_IN_SECONDS_TAG, responseTimeout);
}
if (connectTimeout != null)
{
jsonObject.addProperty(CONNECT_TIMEOUT_IN_SECONDS_TAG, connectTimeout);
}
jsonObject.add(PAYLOAD_TAG, jsonizePayload(payload));
return jsonObject;
}
else if (operation == Operation.response)
{
/** Codes_SRS_METHODPARSER_21_027: [If the method operation is `response`, the toJsonElement shall parse both status and payload.]
* Ex:
* {
* "status": 201,
* "payload": {"AnyValidPayload" : "" }
* }
*/
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(STATUS_TAG, status);
jsonObject.add(PAYLOAD_TAG, jsonizePayload(payload));
return jsonObject;
}
else if (operation == Operation.payload)
{
/**
* Codes_SRS_METHODPARSER_21_028: [If the method operation is `payload`, the toJsonElement shall parse only the payload.]
* Ex:
* {
* "input1": "someInput",
* "input2": "anotherInput"
* }
*/
return jsonizePayload(payload);
} else
{
/* Codes_SRS_METHODPARSER_21_036: [If the method operation is `none`, the toJsonElement shall throw IllegalArgumentException.] */
throw new IllegalArgumentException("There is no content to parser");
}
}
/**
* Validation helper, make sure that the key fits the requirements.
*
* @param key is a key string. It can be name or an Json key in a Map.
* @throws IllegalArgumentException This exception is thrown if the provided Key do not fits the requirements.
*/
private void validateKey(String key) throws IllegalArgumentException
{
/* Codes_SRS_METHODPARSER_21_004: [If the `name` is null, empty, contains more than 128 chars, or illegal char (`$`, `.`, space), the constructor shall throw IllegalArgumentException.] */
if((key == null) || (key.isEmpty()))
{
throw new IllegalArgumentException("Key cannot be null or empty");
}
if(key.length()>128)
{
throw new IllegalArgumentException("Key cannot be longer than 128 characters");
}
if(key.contains("$") || key.contains(".") ||key.contains(" "))
{
throw new IllegalArgumentException("Key cannot contain \'$\', \'.\', or space");
}
}
/**
* Validation helper, make sure that timeout values fit the requirements.
*
* @param timeout is the timeout value in seconds.
* @throws IllegalArgumentException This exception is thrown if the provided timeout value do not fits the requirements.
*/
private void validateTimeout(Long timeout) throws IllegalArgumentException
{
/* Codes_SRS_METHODPARSER_21_005: [If the responseTimeout is a negative number, the constructor shall throw IllegalArgumentException.] */
/* Codes_SRS_METHODPARSER_21_033: [If the connectTimeout is a negative number, the constructor shall throw IllegalArgumentException.] */
if(timeout<0)
{
throw new IllegalArgumentException("Negative timeout");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy