Skip to content

Commit

Permalink
Merge pull request #1545 from FIWARE/attribute/json
Browse files Browse the repository at this point in the history
Add new attribute type JsonProperty
  • Loading branch information
kzangeli authored Feb 22, 2024
2 parents 83a7bdb + e1e2665 commit e0eb126
Show file tree
Hide file tree
Showing 12 changed files with 445 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGES_NEXT_RELEASE
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
* Support for attributes of type VocabularyProperty
* Support for attributes of type JsonProperty
* Support for the new URL parameter "format" for output formats (normalized, concise, simplified)
* Support for JsonProperty attribute type
* Fixed a possible crash for TRoE and attributes of type Vocab/Json/Language

## Notes
* TRoE is still not prepared for attributes of type Vocab/Json/Language, so, attributes of those types are not stored in the historical database
* Modified the @context hosting feature to be according to API spec
* Support for URI param 'kind' for GET Contexts
* Improved counters for GET Context/Contexts
Expand Down
4 changes: 2 additions & 2 deletions src/lib/orionld/dbModel/dbModelFromApiAttribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,13 @@ bool dbModelFromApiAttribute(KjNode* attrP, KjNode* dbAttrsP, KjNode* attrAddedV
//

// Move special fields back to "attrP"
const char* specialV[] = { "type", "value", "object", "languageMap", "vocab", "datasetId" }; // observedAt+unitCode are mds (db-model)
const char* specialV[] = { "type", "value", "object", "languageMap", "vocab", "json", "datasetId" }; // observedAt+unitCode are mds (db-model)
for (unsigned int ix = 0; ix < K_VEC_SIZE(specialV); ix++)
{
KjNode* nodeP = kjLookup(mdP, specialV[ix]);
if (nodeP != NULL)
{
if ((ix == 2) || (ix == 3) || (ix == 4)) // "object", "languageMap", "vocab": change name to "value" (Orion's DB model)
if ((ix >= 2) && (ix <= 5)) // "object", "languageMap", "vocab", "json": change name to "value" (Orion's DB model)
nodeP->name = (char*) "value";

kjChildRemove(mdP, nodeP);
Expand Down
14 changes: 14 additions & 0 deletions src/lib/orionld/dbModel/dbModelToApiAttribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ void dbModelToApiAttribute(KjNode* dbAttrP, bool sysAttrs, bool eqsForDots)
{
if (strcmp(typeP->value.s, "Relationship") == 0) valueP->name = (char*) "object";
else if (strcmp(typeP->value.s, "LanguageProperty") == 0) valueP->name = (char*) "languageMap";
else if (strcmp(typeP->value.s, "JsonProperty") == 0) valueP->name = (char*) "json";
else if (strcmp(typeP->value.s, "VocabularyProperty") == 0)
{
valueP->name = (char*) "vocab";
Expand Down Expand Up @@ -398,6 +399,17 @@ KjNode* dbModelToApiAttribute2(KjNode* dbAttrP, KjNode* datasetP, bool sysAttrs,
valueP->name = (char*) "vocab";
attrP = dbAttrP;
}
else if (strcmp(attrTypeNodeP->value.s, "JsonProperty") == 0)
{
KjNode* valueP = kjLookup(dbAttrP, "value");

// Remove everything except the value, and change its name to "vocab"
dbAttrP->value.firstChildP = valueP;
dbAttrP->lastChild = valueP;
valueP->next = NULL;
valueP->name = (char*) "json";
attrP = dbAttrP;
}
else
{
// "Steal" the value node and rename it to have the attribute's name instead - that's all that's needed for SIMPLIFIED FORMAT
Expand Down Expand Up @@ -435,6 +447,8 @@ KjNode* dbModelToApiAttribute2(KjNode* dbAttrP, KjNode* datasetP, bool sysAttrs,
{
if (attrType == Relationship)
nodeP->name = (char*) "object";
else if (attrType == JsonProperty)
nodeP->name = (char*) "json";
else if (attrType == VocabularyProperty)
{
nodeP->name = (char*) "vocab";
Expand Down
2 changes: 2 additions & 0 deletions src/lib/orionld/dbModel/dbModelToApiSubAttribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ void dbModelToApiSubAttribute(KjNode* dbSubAttrP)
if (strcmp(typeP->value.s, "Relationship") == 0) valueP->name = (char*) "object";
else if (strcmp(typeP->value.s, "LanguageProperty") == 0) valueP->name = (char*) "languageMap";
else if (strcmp(typeP->value.s, "VocabularyProperty") == 0) valueP->name = (char*) "vocab";
else if (strcmp(typeP->value.s, "JsonProperty") == 0) valueP->name = (char*) "json";
}
}

Expand Down Expand Up @@ -144,6 +145,7 @@ KjNode* dbModelToApiSubAttribute2(KjNode* dbSubAttributeP, bool sysAttrs, Orionl
if (subAttrType == Relationship) nodeP->name = (char*) "object";
else if (subAttrType == LanguageProperty) nodeP->name = (char*) "languageMap";
else if (subAttrType == VocabularyProperty) nodeP->name = (char*) "vocab";
else if (subAttrType == JsonProperty) nodeP->name = (char*) "json";

kjChildAdd(subAttrP, nodeP);
}
Expand Down
108 changes: 104 additions & 4 deletions src/lib/orionld/payloadCheck/pCheckAttribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,34 +71,47 @@ static const char* attrTypeChangeTitle(OrionldAttributeType oldType, OrionldAttr
if (oldType == GeoProperty) return "Attempt to transform a GeoProperty into a Property";
if (oldType == LanguageProperty) return "Attempt to transform a LanguageProperty into a Property";
if (oldType == VocabularyProperty) return "Attempt to transform a VocabularyProperty into a Property";
if (oldType == JsonProperty) return "Attempt to transform a JsonProperty into a Property";
}
else if (newType == Relationship)
{
if (oldType == Property) return "Attempt to transform a Property into a Relationship";
if (oldType == GeoProperty) return "Attempt to transform a GeoProperty into a Relationship";
if (oldType == LanguageProperty) return "Attempt to transform a LanguageProperty into a Relationship";
if (oldType == VocabularyProperty) return "Attempt to transform a VocabularyProperty into a Relationship";
if (oldType == JsonProperty) return "Attempt to transform a JsonProperty into a Relationship";
}
else if (newType == GeoProperty)
{
if (oldType == Property) return "Attempt to transform a Property into a GeoProperty";
if (oldType == Relationship) return "Attempt to transform a Relationship into a GeoProperty";
if (oldType == LanguageProperty) return "Attempt to transform a LanguageProperty into a GeoProperty";
if (oldType == VocabularyProperty) return "Attempt to transform a VocabularyProperty into a GeoProperty";
if (oldType == JsonProperty) return "Attempt to transform a JsonProperty into a GeoProperty";
}
else if (newType == LanguageProperty)
{
if (oldType == Property) return "Attempt to transform a Property into a LanguageProperty";
if (oldType == Relationship) return "Attempt to transform a Relationship into a LanguageProperty";
if (oldType == GeoProperty) return "Attempt to transform a GeoProperty into a LanguageProperty";
if (oldType == VocabularyProperty) return "Attempt to transform a VocabularyProperty into a LanguageProperty";
if (oldType == JsonProperty) return "Attempt to transform a JsonProperty into a LanguageProperty";
}
else if (newType == VocabularyProperty)
{
if (oldType == Property) return "Attempt to transform a Property into a VocabularyProperty";
if (oldType == Relationship) return "Attempt to transform a Relationship into a VocabularyProperty";
if (oldType == GeoProperty) return "Attempt to transform a GeoProperty into a VocabularyProperty";
if (oldType == LanguageProperty) return "Attempt to transform a LanguageProperty into a GeoProperty";
if (oldType == LanguageProperty) return "Attempt to transform a LanguageProperty into a VocabularyProperty";
if (oldType == JsonProperty) return "Attempt to transform a JsonProperty into a VocabularyProperty";
}
else if (newType == JsonProperty)
{
if (oldType == Property) return "Attempt to transform a Property into a JsonProperty";
if (oldType == Relationship) return "Attempt to transform a Relationship into a JsonProperty";
if (oldType == GeoProperty) return "Attempt to transform a GeoProperty into a JsonProperty";
if (oldType == LanguageProperty) return "Attempt to transform a LanguageProperty into a JsonProperty";
if (oldType == VocabularyProperty) return "Attempt to transform a VocabularyProperty into a JsonProperty";
}

return "Attribute type inconsistency";
Expand Down Expand Up @@ -413,6 +426,7 @@ bool valueAndTypeCheck(KjNode* attrP, OrionldAttributeType attributeType, bool a
KjNode* objectP = kjLookup(attrP, "object");
KjNode* languageMapP = kjLookup(attrP, "languageMap");
KjNode* vocabP = kjLookup(attrP, "vocab");
KjNode* jsonP = kjLookup(attrP, "json");

if (attributeType == Property)
{
Expand All @@ -431,6 +445,11 @@ bool valueAndTypeCheck(KjNode* attrP, OrionldAttributeType attributeType, bool a
orionldError(OrionldBadRequestData, "Forbidden field for a Property: vocab", attrP->name, 400);
return false;
}
else if (jsonP != NULL)
{
orionldError(OrionldBadRequestData, "Forbidden field for a Property: json", attrP->name, 400);
return false;
}
else if ((valueP == NULL) && (attributeExisted == false)) // Attribute is new but the value is missing
{
orionldError(OrionldBadRequestData, "Missing /value/ field for Property at creation time", attrP->name, 400);
Expand All @@ -454,6 +473,11 @@ bool valueAndTypeCheck(KjNode* attrP, OrionldAttributeType attributeType, bool a
orionldError(OrionldBadRequestData, "Forbidden field for a GeoProperty: vocab", attrP->name, 400);
return false;
}
else if (jsonP != NULL)
{
orionldError(OrionldBadRequestData, "Forbidden field for a GeoProperty: json", attrP->name, 400);
return false;
}
else if ((valueP == NULL) && (attributeExisted == false)) // Attribute is new but the value is missing
{
orionldError(OrionldBadRequestData, "Missing /value/ field for GeoProperty at creation time", attrP->name, 400);
Expand All @@ -477,6 +501,11 @@ bool valueAndTypeCheck(KjNode* attrP, OrionldAttributeType attributeType, bool a
orionldError(OrionldBadRequestData, "Forbidden field for a Relationship: vocab", attrP->name, 400);
return false;
}
else if (jsonP != NULL)
{
orionldError(OrionldBadRequestData, "Forbidden field for a Relationship: json", attrP->name, 400);
return false;
}
else if ((objectP == NULL) && (attributeExisted == false)) // Attribute is new but the value is missing
{
orionldError(OrionldBadRequestData, "Missing /object/ field for Relationship at creation time", attrP->name, 400);
Expand All @@ -500,6 +529,11 @@ bool valueAndTypeCheck(KjNode* attrP, OrionldAttributeType attributeType, bool a
orionldError(OrionldBadRequestData, "Forbidden field for a LanguageProperty: vocab", attrP->name, 400);
return false;
}
else if (jsonP != NULL)
{
orionldError(OrionldBadRequestData, "Forbidden field for a LanguageProperty: json", attrP->name, 400);
return false;
}
else if ((languageMapP == NULL) && (attributeExisted == false)) // Attribute is new but the value is missing
{
orionldError(OrionldBadRequestData, "Missing /languageMap/ field for LanguageProperty at creation time", attrP->name, 400);
Expand All @@ -523,12 +557,45 @@ bool valueAndTypeCheck(KjNode* attrP, OrionldAttributeType attributeType, bool a
orionldError(OrionldBadRequestData, "Forbidden field for a VocabularyProperty: languageMap", attrP->name, 400);
return false;
}
else if (jsonP != NULL)
{
orionldError(OrionldBadRequestData, "Forbidden field for a VocabularyProperty: json", attrP->name, 400);
return false;
}
else if ((vocabP == NULL) && (attributeExisted == false)) // Attribute is new but the value is missing
{
orionldError(OrionldBadRequestData, "Missing /vocab/ field for VocabularyProperty at creation time", attrP->name, 400);
return false;
}
}
else if (attributeType == JsonProperty)
{
if (valueP != NULL)
{
orionldError(OrionldBadRequestData, "Forbidden field for a JsonProperty: value", attrP->name, 400);
return false;
}
else if (objectP != NULL)
{
orionldError(OrionldBadRequestData, "Forbidden field for a JsonProperty: object", attrP->name, 400);
return false;
}
else if (languageMapP != NULL)
{
orionldError(OrionldBadRequestData, "Forbidden field for a JsonProperty: languageMap", attrP->name, 400);
return false;
}
else if (vocabP != NULL)
{
orionldError(OrionldBadRequestData, "Forbidden field for a JsonProperty: vocab", attrP->name, 400);
return false;
}
else if ((jsonP == NULL) && (attributeExisted == false)) // Attribute is new but the value is missing
{
orionldError(OrionldBadRequestData, "Missing /json/ field for JsonProperty at creation time", attrP->name, 400);
return false;
}
}

return true;
}
Expand Down Expand Up @@ -837,6 +904,15 @@ bool deletionWithTypePresent(KjNode* attrP, KjNode* typeP)
}
}
}
else if (strcmp(typeP->value.s, "JsonProperty") == 0)
{
valueP = kjLookup(attrP, "json");
if ((valueP != NULL) && (valueP->type == KjString) && (strcmp(valueP->value.s, "urn:ngsi-ld:null") == 0))
{
attrP->type = KjNull;
return true;
}
}

return false;
}
Expand All @@ -854,7 +930,8 @@ static bool deletionWithoutTypePresent
KjNode* valueP,
KjNode* objectP,
KjNode* languageMapP,
KjNode* vocabP
KjNode* vocabP,
KjNode* jsonP
)
{
if ((attributeType == Property) || (attributeType == GeoProperty))
Expand Down Expand Up @@ -893,6 +970,14 @@ static bool deletionWithoutTypePresent
}
}
}
else if (attributeType == JsonProperty)
{
if ((jsonP != NULL) && (jsonP->type == KjString) && (strcmp(jsonP->value.s, "urn:ngsi-ld:null") == 0))
{
attrP->type = KjNull;
return true;
}
}

return false;
}
Expand Down Expand Up @@ -1057,6 +1142,7 @@ static bool pCheckAttributeObject
KjNode* objectP = kjLookup(attrP, "object");
KjNode* languageMapP = kjLookup(attrP, "languageMap");
KjNode* vocabP = kjLookup(attrP, "vocab");
KjNode* jsonP = kjLookup(attrP, "json");

if (valueP != NULL)
{
Expand All @@ -1080,6 +1166,11 @@ static bool pCheckAttributeObject
attributeType = VocabularyProperty;
arrayReduce(vocabP);
}
else if (jsonP != NULL)
{
attributeType = JsonProperty;
arrayReduce(jsonP);
}
else
{
// If new attribute and no value field at all - error
Expand Down Expand Up @@ -1110,7 +1201,7 @@ static bool pCheckAttributeObject
//
if ((orionldState.serviceP->options & ORIONLD_SERVICE_OPTION_ACCEPT_JSONLD_NULL) != 0)
{
if (deletionWithoutTypePresent(attrP, attributeType, valueP, objectP, languageMapP, vocabP) == true)
if (deletionWithoutTypePresent(attrP, attributeType, valueP, objectP, languageMapP, vocabP, jsonP) == true)
return true;
}
}
Expand Down Expand Up @@ -1157,7 +1248,7 @@ static bool pCheckAttributeObject
fieldP->value = fieldP->value.firstChildP->value;
}
}
if ((attributeType == Relationship) || (attributeType == LanguageProperty) || (attributeType == VocabularyProperty))
if ((attributeType == Relationship) || (attributeType == LanguageProperty) || (attributeType == VocabularyProperty) || (attributeType == JsonProperty))
{
orionldError(OrionldBadRequestData, "Invalid member /value/", "valid for Property/GeoProperty attributes only", 400);
return false;
Expand Down Expand Up @@ -1193,6 +1284,9 @@ static bool pCheckAttributeObject
if (pCheckVocabulary(fieldP, attrP->name) == false)
return false;
}
else if ((attributeType == JsonProperty) && (strcmp(fieldP->name, "json") == 0))
{
}
else if (strcmp(fieldP->name, "observedAt") == 0)
{
//
Expand Down Expand Up @@ -1395,6 +1489,12 @@ static bool validAttrName(const char* attrName, bool isAttribute)
// - observedAt
// - datasetId (sub-attributes don't have datasetId)
//
// - Attributes of type JsonProperty can have the following special attributes:
// - type
// - json
// - observedAt
// - datasetId (sub-attributes don't have datasetId)
//
bool pCheckAttribute
(
const char* entityId,
Expand Down
8 changes: 7 additions & 1 deletion src/lib/orionld/troe/pgAttributeAppend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ void pgAttributeAppend
comma, instanceId, attributeName, opMode, entityId, observedAt, hasSubProperties, unitCode, datasetId, coordsString, orionldState.requestTimeString);
}
}
else // Property
else // Property OR JsonProperty
{
if (valueNodeP->type == KjString)
{
Expand All @@ -242,6 +242,12 @@ void pgAttributeAppend
}
else if ((valueNodeP->type == KjArray) || (valueNodeP->type == KjObject))
{
if (strcmp(type, "JsonProperty") == 0)
{
LM_W(("TRoE for Compound JsonProperty still to be implemented"));
return;
}

// WARNING: If an attribute is HUGE, it may not have room enough in a buffer allocated by kaAlloc (there's a max-size)
int renderedValueSize = kjFastRenderSize(valueNodeP);
char* renderedValue = kaAlloc(&orionldState.kalloc, renderedValueSize);
Expand Down
1 change: 1 addition & 0 deletions src/lib/orionld/troe/pgAttributeBuild.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ bool pgAttributeBuild
else if (strcmp(nodeP->name, "type") == 0) type = nodeP->value.s;
else if (strcmp(nodeP->name, "datasetId") == 0) datasetId = nodeP->value.s;
else if (strcmp(nodeP->name, "value") == 0) valueNodeP = nodeP;
else if (strcmp(nodeP->name, "json") == 0) valueNodeP = nodeP;
else if (strcmp(nodeP->name, "object") == 0)
{
if (nodeP->type == KjString)
Expand Down
2 changes: 2 additions & 0 deletions src/lib/orionld/types/OrionldAttributeType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const char* orionldAttributeTypeName(OrionldAttributeType attributeType)
case GeoProperty: return "GeoProperty";
case LanguageProperty: return "LanguageProperty";
case VocabularyProperty: return "VocabularyProperty";
case JsonProperty: return "JsonProperty";
}

return "InvalidAttributeType";
Expand All @@ -62,6 +63,7 @@ OrionldAttributeType orionldAttributeType(const char* typeString)
else if (strcmp(typeString, "GeoProperty") == 0) return GeoProperty;
else if (strcmp(typeString, "LanguageProperty") == 0) return LanguageProperty;
else if (strcmp(typeString, "VocabularyProperty") == 0) return VocabularyProperty;
else if (strcmp(typeString, "JsonProperty") == 0) return JsonProperty;

return NoAttributeType;
}
3 changes: 2 additions & 1 deletion src/lib/orionld/types/OrionldAttributeType.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ typedef enum OrionldAttributeType
Relationship,
GeoProperty,
LanguageProperty,
VocabularyProperty
VocabularyProperty,
JsonProperty
} OrionldAttributeType;


Expand Down
Loading

0 comments on commit e0eb126

Please sign in to comment.