
com.microsoft.azure.sdk.iot.deps.serializer.TwinProperty Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of iot-deps-serializer Show documentation
Show all versions of iot-deps-serializer Show documentation
A serializer to be used with IotHub communication
The newest version!
// 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.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.internal.LinkedTreeMap;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* INNER TWINPARSER CLASS
*
* TwinProperty is a representation of the device twin property collection.
* It can represent `Desired` property as well `Reported` property.
*
*/
public class TwinProperty
{
private static final String VERSION_TAG = "$version";
private static final String METADATA_TAG = "$metadata";
private static final String LAST_UPDATE_TAG = "$lastUpdated";
private static final String LAST_UPDATE_VERSION_TAG = "$lastUpdatedVersion";
private Object lock = new Object();
private class Property
{
private Object value;
private TwinMetadata metadata;
private Property(Object val, Integer propertyVersion)
{
this.value = val;
this.metadata = new TwinMetadata(propertyVersion);
}
}
private ConcurrentMap property = new ConcurrentHashMap<>();;
private Integer version;
private Boolean reportMetadata;
protected TwinProperty()
{
this.reportMetadata = false;
this.version = null;
}
protected void enableMetadata()
{
/* Codes_SRS_TWINPARSER_21_020: [The enableMetadata shall enable report metadata in Json for the Desired and for the Reported Properties.] */
this.reportMetadata = true;
}
protected Boolean addProperty(String key, Object value, Integer propertyVersion) throws IllegalArgumentException
{
/* Codes_SRS_TWINPARSER_21_059: [The updateDesiredProperty shall only change properties in the map, keep the others as is.] */
/* Codes_SRS_TWINPARSER_21_061: [All `key` and `value` in property shall be case sensitive.] */
/* Codes_SRS_TWINPARSER_21_060: [The updateReportedProperty shall only change properties in the map, keep the others as is.] */
/* Codes_SRS_TWINPARSER_21_062: [All `key` and `value` in property shall be case sensitive.] */
Boolean change = false;
if(key == null)
{
/* Codes_SRS_TWINPARSER_21_073: [If any `key` is null, the updateDesiredProperty shall throw IllegalArgumentException.] */
/* Codes_SRS_TWINPARSER_21_079: [If any `key` is null, the updateReportedProperty shall throw IllegalArgumentException.] */
throw new IllegalArgumentException("Property key shall not be null");
}
if(key.isEmpty())
{
/* Codes_SRS_TWINPARSER_21_074: [If any `key` is empty, the updateDesiredProperty shall throw IllegalArgumentException.] */
/* Codes_SRS_TWINPARSER_21_080: [If any `key` is empty, the updateReportedProperty shall throw IllegalArgumentException.] */
throw new IllegalArgumentException("Property key shall not be empty");
}
if(key.length()>128)
{
/* Codes_SRS_TWINPARSER_21_075: [If any `key` is more than 128 characters long, the updateDesiredProperty shall throw IllegalArgumentException.] */
/* Codes_SRS_TWINPARSER_21_081: [If any `key` is more than 128 characters long, the updateReportedProperty shall throw IllegalArgumentException.] */
throw new IllegalArgumentException("Property key is too big for json");
}
if(key.contains(".") || key.contains(" ") || key.contains("$") )
{
/* Codes_SRS_TWINPARSER_21_076: [If any `key` has an illegal character, the updateDesiredProperty shall throw IllegalArgumentException.] */
/* Codes_SRS_TWINPARSER_21_082: [If any `key` has an illegal character, the updateReportedProperty shall throw IllegalArgumentException.] */
throw new IllegalArgumentException("Property key contains illegal character");
}
if(value == null)
{
/* Codes_SRS_TWINPARSER_21_078: [If any `value` is null, the updateDesiredProperty shall delete it from the collection and report on Json.] */
/* Codes_SRS_TWINPARSER_21_084: [If any `value` is null, the updateReportedProperty shall delete it from the collection and report on Json.] */
if(property.containsKey(key))
{
property.remove(key);
}
change = true;
}
else
{
/* Codes_SRS_TWINPARSER_21_077: [If any `key` already exists, the updateDesiredProperty shall replace the existed value by the new one.] */
/* Codes_SRS_TWINPARSER_21_083: [If any `key` already exists, the updateReportedProperty shall replace the existed value by the new one.] */
if ((!property.containsKey(key)) || (!property.get(key).value.equals(value)) || reportMetadata)
{
change = true;
}
property.put(key, new Property(value, propertyVersion));
}
return change;
}
protected JsonElement update(Map property)
{
JsonElement updatedJsonElement;
TwinProperty updated = new TwinProperty();
synchronized (lock)
{
if (property != null)
{
for (Map.Entry entry : property.entrySet())
{
if (addProperty(entry.getKey(), entry.getValue(), null))
{
/* Codes_SRS_TWINPARSER_21_078: [If any `value` is null, the updateDesiredProperty shall delete it from the collection and report on Json.] */
/* Codes_SRS_TWINPARSER_21_084: [If any `value` is null, the updateReportedProperty shall delete it from the collection and report on Json.] */
updated.property.put(entry.getKey(), new Property(entry.getValue(), null));
}
}
if (updated.size() > 0)
{
updatedJsonElement = updated.toJsonElement();
}
else
{
updatedJsonElement = null;
}
}
else
{
updatedJsonElement = null;
}
}
return updatedJsonElement;
}
protected Integer getVersion()
{
/* Codes_SRS_TWINPARSER_21_048: [The getDesiredPropertyVersion shall return the desired property version.] */
/* Codes_SRS_TWINPARSER_21_049: [The getReportedPropertyVersion shall return the reported property version.] */
return this.version;
}
protected TwinMetadata getMetadata(String key)
{
TwinMetadata twinMetadata = null;
synchronized (lock)
{
if (property.containsKey(key))
{
twinMetadata = property.get(key).metadata;
}
else
{
twinMetadata = null;
}
}
return twinMetadata;
}
protected Map getPropertyMap()
{
/* Codes_SRS_TWINPARSER_21_050: [The getDesiredPropertyMap shall return a map with all desired property key value pairs.] */
/* Codes_SRS_TWINPARSER_21_051: [The getReportedPropertyMap shall return a map with all reported property key value pairs.] */
Map propertyMap = null;
synchronized (lock)
{
if (property.isEmpty())
{
propertyMap = null;
}
else
{
propertyMap = new HashMap<>();
for (Map.Entry entry : property.entrySet())
{
Object value = entry.getValue().value;
if (value == null)
{
propertyMap.put(entry.getKey(), null);
}
else
{
propertyMap.put(entry.getKey(), value);
}
}
}
}
return propertyMap;
}
protected int size()
{
return this.property.size();
}
protected Object get(String key)
{
Object result = null;
synchronized (lock)
{
if (property.containsKey(key))
{
result = property.get(key).value;
}
else
{
result = null;
}
}
return result;
}
protected String toJson()
{
return toJsonElement().toString();
}
protected JsonElement toJsonElement()
{
/* Codes_SRS_TWINPARSER_21_017: [The toJsonElement shall return a JsonElement with information in the TwinParser using json format.] */
Map diffMap = new HashMap<>();
Map metadata = new HashMap<>();
synchronized (lock)
{
for (Map.Entry entry : property.entrySet())
{
diffMap.put(entry.getKey(), entry.getValue().value);
metadata.put(entry.getKey(), entry.getValue().metadata.toJsonElement());
}
}
if(reportMetadata)
{
diffMap.put(METADATA_TAG, metadata);
}
if(version != null)
{
diffMap.put(VERSION_TAG, version);
}
return ParserUtility.mapToJsonElement(diffMap);
}
protected void update(LinkedTreeMap jsonTree,
TwinChangedCallback onCallback) throws IllegalArgumentException
{
Map diffField = new HashMap<>();
Map diffMetadata = new HashMap<>();
try
{
/* Codes_SRS_TWINPARSER_21_039: [The updateTwin shall fill the fields the properties in the TwinParser class with the keys and values provided in the json string.] */
/* Codes_SRS_TWINPARSER_21_029: [The updateDesiredProperty shall update the Desired property using the information provided in the json.] */
/* Codes_SRS_TWINPARSER_21_034: [The updateReportedProperty shall update the Reported property using the information provided in the json.] */
updateVersion(jsonTree);
/* Codes_SRS_TWINPARSER_21_041: [The updateTwin shall create a list with all properties that was updated (new key or value) by the new json.] */
/* Codes_SRS_TWINPARSER_21_030: [The updateDesiredProperty shall generate a map with all pairs key value that had its content changed.] */
/* Codes_SRS_TWINPARSER_21_035: [The updateReportedProperty shall generate a map with all pairs key value that had its content changed.] */
diffField = updateFields(jsonTree);
diffMetadata = updateMetadata(jsonTree);
}
catch (Exception e)
{
/* Codes_SRS_TWINPARSER_21_092: [If the provided json is not valid, the updateDesiredProperty shall throws IllegalArgumentException.] */
throw new IllegalArgumentException("Malformed Json:" + e);
}
if(reportMetadata)
{
for(Map.Entry entry : diffMetadata.entrySet())
{
Property val = property.get(entry.getKey());
if (val == null)
{
diffField.put(entry.getKey(), null);
}
else
{
diffField.put(entry.getKey(), val.value.toString());
}
}
}
/* Codes_SRS_TWINPARSER_21_046: [If OnDesiredCallback was not provided, the updateTwin shall not do anything with the list of updated desired properties.] */
/* Codes_SRS_TWINPARSER_21_047: [If OnReportedCallback was not provided, the updateTwin shall not do anything with the list of updated reported properties.] */
/* Codes_SRS_TWINPARSER_21_069: [If there is no change in the Desired property, the updateTwin shall not change the reported collection and not call the OnReportedCallback.] */
/* Codes_SRS_TWINPARSER_21_070: [If there is no change in the Reported property, the updateTwin shall not change the reported collection and not call the OnReportedCallback.] */
/* Codes_SRS_TWINPARSER_21_032: [If the OnDesiredCallback is set as null, the updateDesiredProperty shall discard the map with the changed pairs.] */
/* Codes_SRS_TWINPARSER_21_033: [If there is no change in the Desired property, the updateDesiredProperty shall not change the collection and not call the OnDesiredCallback.] */
/* Codes_SRS_TWINPARSER_21_037: [If the OnReportedCallback is set as null, the updateReportedProperty shall discard the map with the changed pairs.] */
/* Codes_SRS_TWINPARSER_21_038: [If there is no change in the Reported property, the updateReportedProperty shall not change the collection and not call the OnReportedCallback.] */
/* Codes_SRS_TWINPARSER_21_093: [If the provided json is not valid, the updateReportedProperty shall throws IllegalArgumentException.] */
if((diffField.size() != 0) &&(onCallback != null))
{
/* Codes_SRS_TWINPARSER_21_044: [If OnDesiredCallback was provided, the updateTwin shall create a new map with a copy of all pars key values updated by the json in the Desired property, and OnDesiredCallback passing this map as parameter.] */
/* Codes_SRS_TWINPARSER_21_045: [If OnReportedCallback was provided, the updateTwin shall create a new map with a copy of all pars key values updated by the json in the Reported property, and OnReportedCallback passing this map as parameter.] */
/* Codes_SRS_TWINPARSER_21_031: [The updateDesiredProperty shall send the map with all changed pairs to the upper layer calling onDesiredCallback (TwinChangedCallback).] */
/* Codes_SRS_TWINPARSER_21_036: [The updateReportedProperty shall send the map with all changed pairs to the upper layer calling onReportedCallback (TwinChangedCallback).] */
onCallback.execute(diffField);
}
}
protected void update(String json, TwinChangedCallback onCallback) throws IllegalArgumentException
{
LinkedTreeMap newValues;
try
{
/* Codes_SRS_TWINPARSER_21_095: [If the provided json have any duplicated `key`, the updateReportedProperty shall throws IllegalArgumentException.] */
/* Codes_SRS_TWINPARSER_21_096: [If the provided json have any duplicated `key`, the updateDesiredProperty shall throws IllegalArgumentException.] */
Gson gson = new GsonBuilder().create();
newValues = (LinkedTreeMap) gson.fromJson(json, LinkedTreeMap.class);
}
catch (Exception e)
{
throw new IllegalArgumentException("Malformed Json:" + e);
}
update(newValues, onCallback);
}
private void updateVersion(LinkedTreeMap jsonTree)
{
for (Map.Entry entry : jsonTree.entrySet())
{
if (entry.getKey().equals(VERSION_TAG))
{
version = new Integer( (int) ((double) entry.getValue()));
break;
}
}
}
private Map updateMetadata(LinkedTreeMap jsonTree)
{
Map diff = new HashMap<>();
for (Map.Entry entry : jsonTree.entrySet())
{
if(entry.getKey().equals(METADATA_TAG))
{
LinkedTreeMap metadataTree = (LinkedTreeMap)entry.getValue();
for (LinkedTreeMap.Entry item : metadataTree.entrySet())
{
synchronized (lock)
{
if (property.containsKey(item.getKey()))
{
LinkedTreeMap itemTree = (LinkedTreeMap) item.getValue();
String lastUpdated = null;
Integer lastUpdatedVersion = null;
for (LinkedTreeMap.Entry metadataItem : itemTree.entrySet())
{
if (metadataItem.getKey().equals(LAST_UPDATE_TAG))
{
lastUpdated = metadataItem.getValue().toString();
}
else if (metadataItem.getKey().equals(LAST_UPDATE_VERSION_TAG))
{
lastUpdatedVersion = (int) ((double) metadataItem.getValue());
}
}
if (property.get(item.getKey()).metadata.update(lastUpdated, lastUpdatedVersion))
{
diff.put(item.getKey(), item.getValue().toString());
}
}
}
}
break;
}
}
return diff;
}
private Map updateFields(LinkedTreeMap jsonTree) throws IllegalArgumentException
{
Map diff = new HashMap<>();
for (Map.Entry entry : jsonTree.entrySet())
{
if(entry.getKey().isEmpty())
{
throw new IllegalArgumentException("Invalid Key on Json");
}
if(!entry.getKey().contains("$"))
{
synchronized (lock)
{
/* Codes_SRS_TWINPARSER_21_040: [The updateTwin shall not change fields that is not reported in the json string.] */
if (property.containsKey(entry.getKey()))
{
if (entry.getValue() == null)
{
/* Codes_SRS_TWINPARSER_21_042: [If a valid key has a null value, the updateTwin shall delete this property.] */
property.remove(entry.getKey());
diff.put(entry.getKey(), null);
}
else if (!property.get(entry.getKey()).value.equals(entry.getValue()))
{
property.put(entry.getKey(), new Property(entry.getValue(), null));
diff.put(entry.getKey(), entry.getValue().toString());
}
}
else if (entry.getValue() != null)
{
property.put(entry.getKey(), new Property(entry.getValue(), null));
diff.put(entry.getKey(), entry.getValue().toString());
}
}
}
}
return diff;
}
}