Skip to content

A library to support annotation based mapping between NGSI-LD entities and Java POJOs.

License

Notifications You must be signed in to change notification settings

wistefan/ngsi-ld-java-mapping

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

NGSI-LD-Java-Mapping

License badge Maven Central Coverage Status Test

In order to ease the integration of NGSI-LD into the application domain, a mapping mechanism between the two worlds is required. The NGSI-LD-java-mapping library is providing this. Similar to other mapping mechanisms like jackson for JSON-Java, mapstruct for Java-Java or Hibernate for Java-Database, it uses an annotation-based approach. A set of specific annotations is provided, that needs to be used for describing the object mappings.

Usage

In order to enable the mappers to translate correctly between both worlds, the domain object has to be annotated properly.

πŸ’‘ All examples have a test running the described scenario. You should look up all the details there. The tests can be found under the test-folder, the DisplayName corresponds with the example name referenced in the πŸ” block.

Simple example

Lets start with a simple example:

πŸ” JavaObjectMapperTest - Simple pojo mapping.

@MappingEnabled(value = "my-pojo")
public class MyPojo {
	private static final String ENTITY_TYPE = "my-pojo";

	private URI id;

	private String myName;
	private List<Integer> numbers;

	public MyPojo(String id) {
		this.id = URI.create(id);
	}

	@EntityId
	public URI getId() {
		return id;
	}

	@EntityType
	public String getType() {
		return ENTITY_TYPE;
	}

	@AttributeGetter(value = AttributeType.PROPERTY, targetName = "name")
	public String getMyName() {
		return myName;
	}

	@AttributeSetter(value = AttributeType.PROPERTY, targetName = "name")
	public void setMyName(String myName) {
		this.myName = myName;
	}

	@AttributeGetter(value = AttributeType.PROPERTY_LIST, targetName = "numbers")
	public List<Integer> getNumbers() {
		return numbers;
	}

	@AttributeSetter(value = AttributeType.PROPERTY_LIST, targetName = "numbers", targetClass = Integer.class)
	public void setNumbers(List<Integer> numbers) {
		this.numbers = numbers;
	}
}

This now can be translated with the JavaObjectMapper to an EntityVO that is compliant with the NGSI-LD data-model.

{
  "@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld",
  "id": "urn:ngsi-ld:my-pojo:the-test-pojo",
  "type": "my-pojo",
  "numbers": {
    "type": "Property",
    "value": [
      1,
      2,
      3
    ]
  },
  "name": {
    "type": "Property",
    "value": "The test pojo."
  }
}

To explain all the bits:

  • The class requires to:
    • be annotated with @MappingEnabled. The annotation has to provide the potential types of entitys is can be translated to.
    • have a single-string constructor. This is used to recreate it after reading out from the broker. The constructor is responsible for setting the entity-id.
    • have a method annotated with @EntityId, that has to return the ID of the entity as a URI.
    • have a method annotated with @EntityType, that has to return the type of the entity in NGSI-LD as a string
  • In order to get properties set to the entity the method-level annotations @AttributeGetter and @AttributeSetter are important:
    • the field needs to be accessible via methods, following the getter/setter-pattern.
    • the property in NGSI-LD can be named different than the field(f.e. to comply with a SmartDataModel), use the targetName for that
    • getter:
      • the annotation needs to specify the type of target attribute in NGSI-LD via AttributeType
      • specify the name of the property in NGSI-LD
    • setter:
      • the annotation needs to specify the type of target attribute in NGSI-LD via AttributeType
      • specify the name of the property in NGSI-LD
      • in case of list-properties, the type of the target-list entries since the generic type is not available on runtime due to type-erasure

This will translate the object to an entity and allows translate it back via the EntityVOMapper.

Advanced topics

The mapping-module intends to support complex data-structures and the usage of most(if not all) features of the NGSI-LD data-model. The following documentation will go through the features and describe it.

πŸ’‘ In order to make the examples more readable, they will not include the full methods anymore. They will use lombok-annotations to generate the actual methods.

Objects as properties

When an individual object is used inside the entity, it can be embedded as a property:

πŸ” JavaObjectMapperTest - Map Pojo with a field that is an object.

@Data
public class MySubProperty {

	private String propertyName;
}

@MappingEnabled(entityType = "complex-pojo")
public class MyPojoWithSubProperty(String id) {

	@Getter(onMethod = @__({@EntityId}))
	private URI id;

	@Getter(onMethod = @__({@EntityType}))
	private String type = "complex-pojo";

	public MyPojoWithSubProperty(String id) {
		this.id = URI.create(id);
	}

	@Getter(onMethod = @__({@AttributeGetter(value = AttributeType.PROPERTY, targetName = "mySubProperty")}))
	@Setter(onMethod = @__({@AttributeSetter(value = AttributeType.PROPERTY, targetName = "mySubProperty")}))
	private MySubProperty mySubProperty;
}

The sub-property will be translated into a plain json-object:

{
  "@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld",
  "id": "urn:ngsi-ld:complex-pojo:the-test-pojo",
  "type": "complex-pojo",
  "mySubProperty": {
    "type": "Property",
    "value": {
      "propertyName": "My property"
    }
  }
}

The sub-property also can be a list of objects, too:

πŸ” JavaObjectMapperTest - Map Pojo with a field that is a list of objects.

@Getter(onMethod = @__({@AttributeGetter(value = AttributeType.PROPERTY_LIST, targetName = "mySubProperty")}))
@Setter(onMethod = @__({@AttributeSetter(value = AttributeType.PROPERTY_LIST, targetName = "mySubProperty", targetClass = MySubProperty.class)}))
private List<MySubProperty> mySubProperties;

Note that the setter-annotation now has to contain the value targetClass to provide the generic-type of the list.

{
	"@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld",
	"id": "urn:ngsi-ld:complex-pojo:the-test-pojo",
	"type": "complex-pojo",
	"mySubProperty": {
		"type": "Property",
		"value": [{
			"propertyName": "My property 1"
		}, {
			"propertyName": "My property 2"
		}]
	}
}

⚠️ One thing that jumps out in terms of NGSI-LD: this is not a real list of properties, but a list of objects of the property. That translation is done, since a real list of properties is only possible if a DatasetId exists. Since this cannot be guaranteed for every datamodel the decision was made to keep it as list of objects.

Relationships on mapping to NGSI-LD

NGSI-LD is a lot about relating entities. For objects, we typically want the same. Therefor the AttributeType's RELATIONSHIP and RELATIONSHIP_LIST exist. In the most simple example, the object defined as a sub-property can be another entity:

πŸ” JavaObjectMapperTest - Map Pojo with a field that is a relationship.

@MappingEnabled(entityType = "sub-entity")
public class MySubPropertyEntity {

	@Getter(onMethod = @__({@EntityId, @RelationshipObject, @DatasetId}))
	private URI id;

	@Getter(onMethod = @__({@EntityType}))
	private String type = "sub-entity";

	public MySubPropertyEntity(String id) {
		this.id = URI.create(id);
	}

	@Getter(onMethod = @__({@AttributeGetter(value = AttributeType.PROPERTY, targetName = "name")}))
	@Setter(onMethod = @__({@AttributeSetter(value = AttributeType.PROPERTY, targetName = "name")}))
	private String name = "myName";
}

The class itself has only two differences to the already known annotations:

  • the @RelationshipObject-annotation. Since a relationship has an object referencing the other entity, it has to specify how to get that object(which most of the time will be the entity-id).
  • the @DatasetId-annotation. In order to allow lists of relationships, we have to provide a datasetId to NGSI-LD. In case of relationships, this can easily be solved by using the id. An additional annotation was provided, to allow different approaches to the dataset. The reference on the parent entity looks as following:
	@Getter(onMethod = @__({@AttributeGetter(value = AttributeType.RELATIONSHIP, targetName = "sub-entity")}))
	@Setter(onMethod = @__({@AttributeSetter(value = AttributeType.RELATIONSHIP, targetName = "sub-entity", targetClass = MySubPropertyEntity.class)}))
	private MySubPropertyEntity mySubProperty;

The only difference to the already known is the AttributeType.RELATIONSHIP. It tells the mapper to create a relationship, instead of a property. The resulting object looks as following:

{
	"@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld",
	"id": "urn:ngsi-ld:complex-pojo:the-test-pojo",
	"type": "complex-pojo",
	"sub-entity": {
		"object": "urn:ngsi-ld:sub-entity:the-sub-entity",
		"type": "Relationship",
		"datasetId": "urn:ngsi-ld:sub-entity:the-sub-entity"
	}
}

In order to provide properties of a relationship, the embedProperty field of the @AttributeGetter can be used. It adds the field to the object: The sub-entity contains another field:

πŸ” JavaObjectMapperTest - Map Pojo with a field that is a relationship with additional attributes.

@Getter(onMethod = @__({@AttributeGetter(value = AttributeType.PROPERTY, targetName = "role", embedProperty = true)}))
@Setter(onMethod = @__({@AttributeSetter(value = AttributeType.PROPERTY, targetName = "role")}))
private String role = "Sub-Entity";

This role will be added to the relationship:

{
  "@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld",
  "id": "urn:ngsi-ld:complex-pojo:the-test-pojo",
  "type": "complex-pojo",
  "sub-entity": {
    "object": "urn:ngsi-ld:sub-entity:the-sub-entity",
    "type": "Relationship",
    "datasetId": "urn:ngsi-ld:sub-entity:the-sub-entity",
    "role": {
      "type": "Property",
      "value": "Sub-Entity"
    }
  }
}

Relationships on mapping from NGSI-LD

To reading a relationship back from NGSI-LD is also possible. The EntityVOMapper therefor requires an implementation of the EntitiesRepository to retrieve the actual entities related from NGSI-LD.

⚠️ In order to use that feature, referential integrity has to be assured. The referenced entities need to be provided through the repository-interface.

In case of the previous example:

πŸ” EntityVOMapperTest - Map entity containing a relationship.

{
	"@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld",
	"id": "urn:ngsi-ld:complex-pojo:the-test-pojo",
	"type": "complex-pojo",
	"sub-entity": {
		"object": "urn:ngsi-ld:sub-entity:the-sub-entity",
		"type": "Relationship",
		"datasetId": "urn:ngsi-ld:sub-entity:the-sub-entity"
	}
}

The entity urn:ngsi-ld:sub-entity:the-sub-entity also has to exist within the broker:

{
	"@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld",
	"id": "urn:ngsi-ld:sub-entity:the-sub-entity",
	"type": "sub-entity",
	"name": {
		"type": "Property",
		"value": "myName"
	}
}

The mapper will then retrieve all entites referenced as a relationship from the repository and generate the defined objects. Remember the annotation-value targetClass, it will be used to map the referenced entity to the target type:

πŸ” EntityVOMapperTest - Map entity containing a relationship with embedded values.

	@Setter(onMethod = @__({@AttributeSetter(value = AttributeType.RELATIONSHIP, targetName = "sub-entity", targetClass = MySubPropertyEntity.class)}))
	private MySubPropertyEntity mySubProperty;

The resulting object will then look as following(json for better readability):

{
	"id": "urn:ngsi-ld:complex-pojo:the-test-pojo",
	"type": "complex-pojo",
	"mySubProperty": {
		"id": "urn:ngsi-ld:sub-entity:the-sub-entity",
		"type": "sub-entity",
		"name": "myName"
	}
}

Reading out embedded properties(again, same example), instead of the related entity achieved(f.e. if a generic object is used):

{
  "@context": "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld",
  "id": "urn:ngsi-ld:complex-pojo:the-test-pojo",
  "type": "complex-pojo",
  "sub-entity": {
    "object": "urn:ngsi-ld:sub-entity:the-sub-entity",
    "type": "Relationship",
    "datasetId": "urn:ngsi-ld:sub-entity:the-sub-entity",
    "role": {
      "type": "Property",
      "value": "Sub-Entity"
    }
  }
}

With the value fromProperties=true on the @AttributeSetter, the mapper will read out the properties and put them in the object:

@Setter(onMethod = @__({@AttributeSetter(value = AttributeType.RELATIONSHIP, targetName = "sub-entity", fromProperties = true)}))
private MySubPropertyEntityEmbed mySubProperty;

The result is an object, containing values from the parent and the actual entity:

{
	"id": "urn:ngsi-ld:complex-pojo:the-test-pojo",
	"type": "complex-pojo",
	"mySubProperty": {
		"id": "urn:ngsi-ld:sub-entity:the-sub-entity",
		"type": "sub-entity",
		"name": "myName",
		"role": "Sub-Entity"
	}
}

About

A library to support annotation based mapping between NGSI-LD entities and Java POJOs.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages