Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.github.wistefan.mapping.EntityVOMapper Maven / Gradle / Ivy
package io.github.wistefan.mapping;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import io.github.wistefan.mapping.annotations.AttributeSetter;
import io.github.wistefan.mapping.annotations.AttributeType;
import io.github.wistefan.mapping.annotations.MappingEnabled;
import lombok.extern.slf4j.Slf4j;
import org.fiware.ngsi.model.*;
import reactor.core.publisher.Mono;
import javax.inject.Singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Mapper to handle translation from NGSI-LD entities to Java-Objects, based on annotations added to the target class
*/
@Slf4j
@Singleton
public class EntityVOMapper extends Mapper {
private final MappingProperties mappingProperties;
private final ObjectMapper objectMapper;
private final EntitiesRepository entitiesRepository;
public EntityVOMapper(MappingProperties mappingProperties, ObjectMapper objectMapper, EntitiesRepository entitiesRepository) {
this.mappingProperties = mappingProperties;
this.objectMapper = objectMapper;
this.entitiesRepository = entitiesRepository;
this.objectMapper
.addMixIn(AdditionalPropertyVO.class, AdditionalPropertyMixin.class);
this.objectMapper.registerModule(new SimpleModule().addDeserializer(GeoQueryVO.class,
new GeoQueryDeserializer()));
this.objectMapper.findAndRegisterModules();
}
/**
* Method to convert a Java-Object to Map representation
*
* @param entity the entity to be converted
* @return the converted map
*/
public Map convertEntityToMap(T entity) {
return objectMapper.convertValue(entity, new TypeReference<>() {
});
}
/**
* Translate the given object into a Subscription.
*
* @param subscription the object representing the subscription
* @param class of the subscription
* @return the NGSI-LD subscription object
*/
public SubscriptionVO toSubscriptionVO(T subscription) {
isMappingEnabled(subscription.getClass())
.orElseThrow(() -> new UnsupportedOperationException(
String.format("Generic mapping to NGSI-LD subscriptions is not supported for object %s",
subscription)));
SubscriptionVO subscriptionVO = objectMapper.convertValue(subscription, SubscriptionVO.class);
subscriptionVO.setAtContext(mappingProperties.getContextUrl());
return subscriptionVO;
}
/**
* Method to map an NGSI-LD Entity into a Java-Object of class targetClass. The class has to provide a string constructor to receive the entity id
*
* @param entityVO the NGSI-LD entity to be mapped
* @param targetClass class of the target object
* @param generic type of the target object, has to extend provide a string-constructor to receive the entity id
* @return the mapped object
*/
public Mono fromEntityVO(EntityVO entityVO, Class targetClass) {
Optional optionalMappingEnabled = isMappingEnabled(targetClass);
if (!optionalMappingEnabled.isPresent()) {
return Mono.error(new MappingException(String.format("Mapping is not enabled for class %s", targetClass)));
}
MappingEnabled mappingEnabled = optionalMappingEnabled.get();
if (!Arrays.stream(mappingEnabled.entityType()).toList().contains(entityVO.getType())) {
return Mono.error(new MappingException(String.format("Entity and Class type do not match - %s vs %s.", entityVO.getType(), Arrays.asList(mappingEnabled.entityType()))));
}
Map additionalPropertyVOMap = Optional.ofNullable(entityVO.getAdditionalProperties()).orElse(Map.of());
return getRelationshipMap(additionalPropertyVOMap, targetClass)
.flatMap(relationshipMap -> fromEntityVO(entityVO, targetClass, relationshipMap));
}
/**
* Return a single, emitting the entities associated with relationships in the given properties maps
*
* @param propertiesMap properties map to evaluate
* @param targetClass class of the target object
* @param the class
* @return a single, emitting the map of related entities
*/
private Mono> getRelationshipMap(Map propertiesMap, Class targetClass) {
return Optional.ofNullable(entitiesRepository.getEntities(getRelationshipObjects(propertiesMap, targetClass)))
.orElse(Mono.just(List.of()))
.switchIfEmpty(Mono.just(List.of()))
.map(relationshipsList -> relationshipsList.stream().map(EntityVO.class::cast).collect(Collectors.toMap(e -> e.getId().toString(), e -> e)))
.defaultIfEmpty(Map.of());
}
/**
* Create the actual object from the entity, after its relations are evaluated.
*
* @param entityVO entity to create the object from
* @param targetClass class of the object to be created
* @param relationShipMap all entities (directly) related to the objects. Sub relationships(e.g. relationships of properties) will be evaluated downstream.
* @param the class
* @return a single, emitting the actual object.
*/
private Mono fromEntityVO(EntityVO entityVO, Class targetClass, Map relationShipMap) {
try {
Constructor objectConstructor = targetClass.getDeclaredConstructor(String.class);
T constructedObject = objectConstructor.newInstance(entityVO.getId().toString());
// handle "well-known" properties
Map propertiesMap = new LinkedHashMap<>();
propertiesMap.put(EntityVO.JSON_PROPERTY_LOCATION, entityVO.getLocation());
propertiesMap.put(EntityVO.JSON_PROPERTY_OBSERVATION_SPACE, entityVO.getObservationSpace());
propertiesMap.put(EntityVO.JSON_PROPERTY_OPERATION_SPACE, entityVO.getOperationSpace());
propertiesMap.put(EntityVO.JSON_PROPERTY_CREATED_AT, propertyVOFromValue(entityVO.getCreatedAt()));
propertiesMap.put(EntityVO.JSON_PROPERTY_MODIFIED_AT, propertyVOFromValue(entityVO.getModifiedAt()));
Optional.ofNullable(entityVO.getAdditionalProperties()).ifPresent(propertiesMap::putAll);
List> singleInvocations = propertiesMap.entrySet().stream()
.map(entry -> getObjectInvocation(entry, constructedObject, relationShipMap, entityVO.getId().toString()))
.toList();
return Mono.zip(singleInvocations, constructedObjects -> constructedObject);
} catch (NoSuchMethodException e) {
return Mono.error(new MappingException(String.format("The class %s does not declare the required String id constructor.", targetClass)));
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
return Mono.error(new MappingException(String.format("Was not able to create instance of %s.", targetClass), e));
}
}
public NotificationVO readNotificationFromJSON(String json) throws JsonProcessingException {
return objectMapper.readValue(json, NotificationVO.class);
}
/**
* Helper method to create a propertyVO for well-known(thus flat) properties
*
* @param value the value to wrap
* @return a propertyVO containing the value
*/
private PropertyVO propertyVOFromValue(Object value) {
PropertyVO propertyVO = new PropertyVO();
propertyVO.setValue(value);
return propertyVO;
}
/**
* Get the invocation on the object to be constructed.
*
* @param entry additional properties entry
* @param objectUnderConstruction the new object, to be filled with the values
* @param relationShipMap map of pre-evaluated relations
* @param entityId id of the entity
* @param class of the constructed object
* @return single, emmiting the constructed object
*/
private Mono getObjectInvocation(Map.Entry entry, T objectUnderConstruction, Map relationShipMap, String entityId) {
Optional optionalSetter = getCorrespondingSetterMethod(objectUnderConstruction, entry.getKey());
if (optionalSetter.isEmpty()) {
log.warn("Ignoring property {} for entity {} since there is no mapping configured.", entry.getKey(), entityId);
return Mono.just(objectUnderConstruction);
}
Method setterMethod = optionalSetter.get();
Optional optionalAttributeSetter = getAttributeSetterAnnotation(setterMethod);
if (optionalAttributeSetter.isEmpty()) {
log.warn("Ignoring property {} for entity {} since there is no attribute setter configured.", entry.getKey(), entityId);
return Mono.just(objectUnderConstruction);
}
AttributeSetter setterAnnotation = optionalAttributeSetter.get();
Class> parameterType = getParameterType(setterMethod.getParameterTypes());
return switch (setterAnnotation.value()) {
case PROPERTY, GEO_PROPERTY ->
handleProperty(entry.getValue(), objectUnderConstruction, optionalSetter.get(), parameterType);
case PROPERTY_LIST ->
handlePropertyList(entry.getValue(), objectUnderConstruction, optionalSetter.get(), setterAnnotation);
case RELATIONSHIP ->
handleRelationship(entry.getValue(), objectUnderConstruction, relationShipMap, optionalSetter.get(), setterAnnotation);
case RELATIONSHIP_LIST ->
handleRelationshipList(entry.getValue(), objectUnderConstruction, relationShipMap, optionalSetter.get(), setterAnnotation);
default ->
Mono.error(new MappingException(String.format("Received type %s is not supported.", setterAnnotation.value())));
};
}
/**
* Handle the evaluation of a property entry. Returns a single, emitting the target object, while invoking the property setting method.
*
* @param propertyValue the value of the property
* @param objectUnderConstruction the object under construction
* @param setter the setter to be used for the property
* @param parameterType type of the property in the target object
* @param class of the object under construction
* @return the single, emitting the objectUnderConstruction
*/
private Mono handleProperty(AdditionalPropertyVO propertyValue, T objectUnderConstruction, Method setter, Class> parameterType) {
if (propertyValue instanceof PropertyVO propertyVO)
return invokeWithExceptionHandling(setter, objectUnderConstruction, objectMapper.convertValue(propertyVO.getValue(), parameterType));
else {
log.error("Mapping exception");
return Mono.error(new MappingException(String.format("The attribute is not a valid property: %s ", propertyValue)));
}
}
/**
* Handle the evaluation of a property-list entry. Returns a single, emitting the target object, while invoking the property setting method.
*
* @param propertyListObject the object containing the property-list
* @param objectUnderConstruction the object under construction
* @param setter the setter to be used for the property
* @param class of the object under construction
* @return the single, emitting the objectUnderConstruction
*/
private Mono handlePropertyList(AdditionalPropertyVO propertyListObject, T objectUnderConstruction, Method setter, AttributeSetter setterAnnotation) {
if (propertyListObject instanceof PropertyListVO propertyVOS) {
return invokeWithExceptionHandling(setter, objectUnderConstruction, propertyListToTargetClass(propertyVOS, setterAnnotation.targetClass()));
} else if (propertyListObject instanceof PropertyVO propertyVO) {
//we need special handling here, since we have no real property lists(see NGSI-LD issue)
// TODO: remove as soon as ngsi-ld does properly support that.
if (propertyVO.getValue() instanceof List propertyList) {
return invokeWithExceptionHandling(setter, objectUnderConstruction, propertyList.stream()
.map(listValue -> objectMapper.convertValue(listValue, setterAnnotation.targetClass()))
.toList());
}
PropertyListVO propertyVOS = new PropertyListVO();
propertyVOS.add(propertyVO);
// in case of single element lists, they are returned as a flat property
return invokeWithExceptionHandling(setter, objectUnderConstruction, propertyListToTargetClass(propertyVOS, setterAnnotation.targetClass()));
} else {
return Mono.error(new MappingException(String.format("The attribute is not a valid property list: %v ", propertyListObject)));
}
}
/**
* Handle the evaluation of a relationship-list entry. Returns a single, emitting the target object, while invoking the property setting method.
*
* @param attributeValue the entry containing the relationship-list
* @param objectUnderConstruction the object under construction
* @param relationShipMap a map containing the pre-evaluated relationships
* @param setter the setter to be used for the property
* @param setterAnnotation attribute setter annotation on the method
* @param class of the objectUnderConstruction
* @return the single, emitting the objectUnderConstruction
*/
private Mono handleRelationshipList(AdditionalPropertyVO attributeValue, T objectUnderConstruction, Map relationShipMap, Method setter, AttributeSetter setterAnnotation) {
Class> targetClass = setterAnnotation.targetClass();
if (setterAnnotation.fromProperties()) {
Optional optionalRelationshipVO = getRelationshipFromProperty(attributeValue);
Optional optionalRelationshipListVO = getRelationshipListFromProperty(attributeValue);
if (optionalRelationshipVO.isPresent()) {
return relationshipFromProperties(optionalRelationshipVO.get(), targetClass)
.flatMap(relationship -> {
// we return the constructed object, since invoke most likely returns null, which is not allowed on mapper functions
// a list is created, since we have a relationship-list defined by the annotation
return invokeWithExceptionHandling(setter, objectUnderConstruction, List.of(relationship));
});
} else if (optionalRelationshipListVO.isPresent()) {
return Mono.zip(optionalRelationshipListVO.get().stream().map(relationshipVO -> relationshipFromProperties(relationshipVO, targetClass)).toList(),
oList -> Arrays.asList(oList).stream().map(targetClass::cast).toList()).flatMap(relationshipList -> {
// we return the constructed object, since invoke most likely returns null, which is not allowed on mapper functions
return invokeWithExceptionHandling(setter, objectUnderConstruction, relationshipList);
});
} else {
return Mono.error(new MappingException(String.format("Value of the relationship %s is invalid.", attributeValue)));
}
} else {
return relationshipListToTargetClass(attributeValue, targetClass, relationShipMap)
.flatMap(relatedEntities -> {
// we return the constructed object, since invoke most likely returns null, which is not allowed on mapper functions
return invokeWithExceptionHandling(setter, objectUnderConstruction, relatedEntities);
});
}
}
/**
* Handle the evaluation of a relationship entry. Returns a single, emitting the target object, while invoking the property setting method.
*
* @param relationShip the object containing the relationship
* @param objectUnderConstruction the object under construction
* @param relationShipMap a map containing the pre-evaluated relationships
* @param setter the setter to be used for the property
* @param setterAnnotation attribute setter annotation on the method
* @param class of the objectUnderConstruction
* @return the single, emitting the objectUnderConstruction
*/
private Mono handleRelationship(AdditionalPropertyVO relationShip, T objectUnderConstruction, Map relationShipMap, Method setter, AttributeSetter setterAnnotation) {
Class> targetClass = setterAnnotation.targetClass();
if (relationShip instanceof RelationshipVO relationshipVO) {
if (setterAnnotation.fromProperties()) {
return relationshipFromProperties(relationshipVO, targetClass)
.flatMap(relatedEntity -> {
// we return the constructed object, since invoke most likely returns null, which is not allowed on mapper functions
return invokeWithExceptionHandling(setter, objectUnderConstruction, relatedEntity);
});
} else {
return getObjectFromRelationship(relationshipVO, targetClass, relationShipMap, relationshipVO.getAdditionalProperties())
.flatMap(relatedEntity -> {
// we return the constructed object, since invoke most likely returns null, which is not allowed on mapper functions
return invokeWithExceptionHandling(setter, objectUnderConstruction, relatedEntity);
});
// handle overwrites from property
}
} else {
return Mono.error(new MappingException(String.format("Did not receive a valid relationship: %s", relationShip)));
}
}
/**
* Invoke the given method and handle potential exceptions.
*/
private Mono invokeWithExceptionHandling(Method invocationMethod, T objectUnderConstruction, Object... invocationArgs) {
try {
invocationMethod.invoke(objectUnderConstruction, invocationArgs);
return Mono.just(objectUnderConstruction);
} catch (IllegalAccessException | InvocationTargetException | RuntimeException e) {
return Mono.error(new MappingException(String.format("Was not able to invoke method %s.", invocationMethod.getName()), e));
}
}
/**
* Create the target object of a relationship from its properties(instead of entities additionally retrieved)
*
* @param relationshipVO representation of the current relationship(as provided by the original entitiy)
* @param targetClass class of the target object to be created(e.g. the object representing the relationship)
* @param the class
* @return a single emitting the object representing the relationship
*/
private Mono relationshipFromProperties(RelationshipVO relationshipVO, Class targetClass) {
try {
String entityID = relationshipVO.getObject().toString();
Constructor objectConstructor = targetClass.getDeclaredConstructor(String.class);
T constructedObject = objectConstructor.newInstance(entityID);
Map attributeSetters = getAttributeSetterMethodMap(constructedObject);
return Mono.zip(attributeSetters.entrySet().stream()
.map(methodEntry -> {
String field = methodEntry.getKey();
Method setterMethod = methodEntry.getValue();
Optional optionalAttributeSetterAnnotation = getAttributeSetterAnnotation(setterMethod);
if (optionalAttributeSetterAnnotation.isEmpty()) {
// no setter for the field, can be ignored
log.debug("No setter defined for field {}", field);
return Mono.just(constructedObject);
}
AttributeSetter setterAnnotation = optionalAttributeSetterAnnotation.get();
Optional optionalProperty = switch (methodEntry.getKey()) {
case RelationshipVO.JSON_PROPERTY_OBSERVED_AT ->
Optional.ofNullable(relationshipVO.getObservedAt()).map(this::propertyVOFromValue);
case RelationshipVO.JSON_PROPERTY_CREATED_AT ->
Optional.ofNullable(relationshipVO.getCreatedAt()).map(this::propertyVOFromValue);
case RelationshipVO.JSON_PROPERTY_MODIFIED_AT ->
Optional.ofNullable(relationshipVO.getModifiedAt()).map(this::propertyVOFromValue);
case RelationshipVO.JSON_PROPERTY_DATASET_ID ->
Optional.ofNullable(relationshipVO.getDatasetId()).map(this::propertyVOFromValue);
case RelationshipVO.JSON_PROPERTY_INSTANCE_ID ->
Optional.ofNullable(relationshipVO.getInstanceId()).map(this::propertyVOFromValue);
default -> Optional.empty();
};
// try to find the attribute from the additional properties
if (optionalProperty.isEmpty() && relationshipVO.getAdditionalProperties() != null && relationshipVO.getAdditionalProperties().containsKey(field)) {
optionalProperty = Optional.ofNullable(relationshipVO.getAdditionalProperties().get(field));
}
return optionalProperty.map(attributeValue -> {
return switch (setterAnnotation.value()) {
case PROPERTY, GEO_PROPERTY ->
handleProperty(attributeValue, constructedObject, setterMethod, setterAnnotation.targetClass());
case RELATIONSHIP ->
getRelationshipMap(relationshipVO.getAdditionalProperties(), targetClass)
.map(rm -> handleRelationship(attributeValue, constructedObject, rm, setterMethod, setterAnnotation));
//resolve objects;
case RELATIONSHIP_LIST ->
getRelationshipMap(relationshipVO.getAdditionalProperties(), targetClass)
.map(rm -> handleRelationshipList(attributeValue, constructedObject, rm, setterMethod, setterAnnotation));
case PROPERTY_LIST ->
handlePropertyList(attributeValue, constructedObject, setterMethod, setterAnnotation);
default ->
Mono.error(new MappingException(String.format("Received type %s is not supported.", setterAnnotation.value())));
};
}).orElse(Mono.just(constructedObject));
}).toList(), constructedObjects -> constructedObject);
} catch (NoSuchMethodException e) {
return Mono.error(new MappingException(String.format("The class %s does not declare the required String id constructor.", targetClass)));
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
return Mono.error(new MappingException(String.format("Was not able to create instance of %s.", targetClass), e));
}
}
/**
* Returns a list of all entityIDs that are defined as relationships from the given entity.
*
* @param additionalProperties map of the properties to evaluate
* @param targetClass target class of the mapping
* @param the class
* @return a list of uris
*/
private List getRelationshipObjects(Map additionalProperties, Class targetClass) {
return Arrays.stream(targetClass.getMethods())
.map(this::getAttributeSetterAnnotation)
.filter(Optional::isPresent)
.map(Optional::get)
.filter(a -> (a.value().equals(AttributeType.RELATIONSHIP) || a.value().equals(AttributeType.RELATIONSHIP_LIST)))
// we don't need to retrieve entities that should be filled from the properties.
.filter(a -> !a.fromProperties())
.flatMap(attributeSetter -> getEntityURIsByAttributeSetter(attributeSetter, additionalProperties).stream())
.toList();
}
/**
* Evaluate a properties map to get all referenced entity ids
*
* @param attributeSetter the attribute setter annotation
* @param propertiesMap the properties map to check
* @return a list of entity ids
*/
private List getEntityURIsByAttributeSetter(AttributeSetter attributeSetter, Map propertiesMap) {
return Optional.ofNullable(propertiesMap.get(attributeSetter.targetName()))
.map(this::getURIsFromRelationshipObject)
.orElseGet(List::of);
}
/**
* Evaluate a concrete object of a realitonship. If its a list of objects, get the ids of all entities.
*
* @param additionalPropertyVO the object to evaluate
* @return a list of all referenced ids
*/
private List getURIsFromRelationshipObject(AdditionalPropertyVO additionalPropertyVO) {
Optional optionalRelationshipVO = getRelationshipFromProperty(additionalPropertyVO);
if (optionalRelationshipVO.isPresent()) {
// List.of() cannot be used, since we need a mutable list
List uriList = new ArrayList<>();
uriList.add(optionalRelationshipVO.get().getObject());
return uriList;
}
Optional optionalRelationshipListVO = getRelationshipListFromProperty(additionalPropertyVO);
if (optionalRelationshipListVO.isPresent()) {
return optionalRelationshipListVO.get().stream().flatMap(listEntry -> getURIsFromRelationshipObject(listEntry).stream()).toList();
}
return List.of();
}
/**
* Method to translate a Map-Entry(e.g. NGSI-LD relationship) to a typed list as defined by the target object
*
* @param entry attribute of the entity, e.g. a relationship or a list of relationships
* @param targetClass class to be used as type for the typed list
* @param the type
* @return a list of objects, mapping the relationship
*/
private Mono> relationshipListToTargetClass(AdditionalPropertyVO entry, Class targetClass, Map relationShipEntitiesMap) {
Optional optionalRelationshipVO = getRelationshipFromProperty(entry);
if (optionalRelationshipVO.isPresent()) {
return getObjectFromRelationship(optionalRelationshipVO.get(), targetClass, relationShipEntitiesMap, optionalRelationshipVO.get().getAdditionalProperties())
.map(List::of);
}
Optional optionalRelationshipListVO = getRelationshipListFromProperty(entry);
if (optionalRelationshipListVO.isPresent()) {
return zipToList(optionalRelationshipListVO.get().stream(), targetClass, relationShipEntitiesMap);
}
return Mono.error(new MappingException(String.format("Did not receive a valid entry: %s", entry)));
}
private Optional getRelationshipListFromProperty(AdditionalPropertyVO additionalPropertyVO) {
if (additionalPropertyVO instanceof RelationshipListVO rsl) {
return Optional.of(rsl);
} else if (additionalPropertyVO instanceof PropertyListVO propertyListVO && !propertyListVO.isEmpty() && isRelationship(propertyListVO.get(0))) {
RelationshipListVO relationshipListVO = new RelationshipListVO();
propertyListVO.stream()
.map(propertyVO -> objectMapper.convertValue(propertyVO.getValue(), RelationshipVO.class))
.forEach(relationshipListVO::add);
return Optional.of(relationshipListVO);
}
return Optional.empty();
}
private Optional getRelationshipFromProperty(AdditionalPropertyVO additionalPropertyVO) {
if (additionalPropertyVO instanceof RelationshipVO relationshipVO) {
return Optional.of(relationshipVO);
} else if (additionalPropertyVO instanceof PropertyVO propertyVO && isRelationship(propertyVO)) {
return Optional.of(objectMapper.convertValue(propertyVO.getValue(), RelationshipVO.class));
}
return Optional.empty();
}
private boolean isRelationship(PropertyVO testProperty) {
return testProperty.getValue() instanceof Map, ?> valuesMap && valuesMap.get("type").equals(PropertyTypeVO.RELATIONSHIP.getValue());
}
/**
* Helper method for combining the evaluation of relationship entities to a single result lits
*
* @param relationshipVOStream the relationships to evaluate
* @param targetClass target class of the relationship object
* @param relationShipEntitiesMap map of the preevaluated relationship entities
* @param target class of the relationship
* @return a single emitting the full list
*/
private Mono> zipToList(Stream relationshipVOStream, Class targetClass, Map relationShipEntitiesMap) {
return Mono.zip(
relationshipVOStream.map(RelationshipVO::getObject)
.filter(Objects::nonNull)
.map(URI::toString)
.map(relationShipEntitiesMap::get)
.map(entity -> fromEntityVO(entity, targetClass))
.toList(),
oList -> Arrays.stream(oList).map(targetClass::cast).toList()
);
}
/**
* Method to translate a Map-Entry(e.g. NGSI-LD property) to a typed list as defined by the target object
*
* @param propertyVOS a list of properties
* @param targetClass class to be used as type for the typed list
* @param the type
* @return a list of objects, mapping the relationship
*/
private List propertyListToTargetClass(PropertyListVO propertyVOS, Class targetClass) {
return propertyVOS.stream().map(propertyEntry -> objectMapper.convertValue(propertyEntry.getValue(), targetClass)).toList();
}
/**
* Retrieve the object from a relationship and return it as a java object of class T. All sub relationships will be evaluated, too.
*
* @param relationshipVO the relationship entry
* @param targetClass the target-class of the entry
* @param the class
* @return the actual object
*/
private Mono getObjectFromRelationship(RelationshipVO relationshipVO, Class targetClass, Map relationShipEntitiesMap, Map additionalPropertyVOMap) {
Optional optionalEntityVO = Optional.ofNullable(relationShipEntitiesMap.get(relationshipVO.getObject().toString()));
if (optionalEntityVO.isEmpty() && !mappingProperties.isStrictRelationships()) {
try {
Constructor objectConstructor = targetClass.getDeclaredConstructor(String.class);
T theObject = objectConstructor.newInstance(relationshipVO.getObject().toString());
// return the empty object
return Mono.just(theObject);
} catch (InvocationTargetException | InstantiationException | IllegalAccessException |
NoSuchMethodException e) {
return Mono.error(new MappingException(String.format("Was not able to instantiate %s with a string parameter.", targetClass), e));
}
} else if (optionalEntityVO.isEmpty()) {
return Mono.error(new MappingException(String.format("Was not able to resolve the relationship %s", relationshipVO.getObject())));
}
var entityVO = optionalEntityVO.get();
//merge with override properties
if (additionalPropertyVOMap != null) {
entityVO.getAdditionalProperties().putAll(additionalPropertyVOMap);
}
return fromEntityVO(entityVO, targetClass);
}
/**
* Return the type of the setter's parameter.
*/
private Class> getParameterType(Class>[] arrayOfClasses) {
if (arrayOfClasses.length != 1) {
throw new MappingException("Setter method should only have one parameter declared.");
}
return arrayOfClasses[0];
}
/**
* Get the setter method for the given property at the entity.
*/
private Optional getCorrespondingSetterMethod(T entity, String propertyName) {
return getAttributeSettersMethods(entity).stream().filter(m ->
getAttributeSetterAnnotation(m)
.map(attributeSetter -> attributeSetter.targetName().equals(propertyName)).orElse(false))
.findFirst();
}
/**
* Get all attribute setters for the given entity
*/
private List getAttributeSettersMethods(T entity) {
return Arrays.stream(entity.getClass().getMethods()).filter(m -> getAttributeSetterAnnotation(m).isPresent()).toList();
}
private Map getAttributeSetterMethodMap(T entity) {
return Arrays.stream(entity.getClass().getMethods())
.filter(m -> getAttributeSetterAnnotation(m).isPresent())
.collect(Collectors.toMap(m -> getAttributeSetterAnnotation(m).get().targetName(), m -> m));
}
/**
* Get the attribute setter annotation from the given method, if it exists.
*/
private Optional getAttributeSetterAnnotation(Method m) {
return Arrays.stream(m.getAnnotations()).filter(AttributeSetter.class::isInstance)
.findFirst()
.map(AttributeSetter.class::cast);
}
}