• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wistefan / ngsi-ld-java-mapping / #119

pending completion
#119

push

beknazaresenbek
Initial subscriptionVO mapper was added

81 of 81 new or added lines in 2 files covered. (100.0%)

504 of 610 relevant lines covered (82.62%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.12
/src/main/java/io/github/wistefan/mapping/SubscriptionVOMapper.java
1
package io.github.wistefan.mapping;
2

3
import com.fasterxml.jackson.databind.ObjectMapper;
4
import io.github.wistefan.mapping.annotations.AttributeSetter;
5
import io.github.wistefan.mapping.annotations.MappingEnabled;
6
import lombok.extern.slf4j.Slf4j;
7
import org.fiware.ngsi.model.*;
8
import reactor.core.publisher.Mono;
9

10
import javax.inject.Singleton;
11
import java.lang.reflect.Constructor;
12
import java.lang.reflect.InvocationTargetException;
13
import java.lang.reflect.Method;
14
import java.util.*;
15

16
/**
17
 * Mapper to handle translation from NGSI-LD subscriptions to Java-Objects, based on annotations added to the target class
18
 */
19
@Slf4j
1✔
20
@Singleton
21
public class SubscriptionVOMapper extends Mapper {
22

23
    private final ObjectMapper objectMapper;
24

25
    public SubscriptionVOMapper(ObjectMapper objectMapper) {
1✔
26
        this.objectMapper = objectMapper;
1✔
27
        this.objectMapper.findAndRegisterModules();
1✔
28
    }
1✔
29

30
    /**
31
     * Method to map an NGSI-LD Subscription into a Java-Object of class targetClass. The class has to provide a
32
     * string constructor to receive the subscription id
33
     *
34
     * @param subscriptionVO    the NGSI-LD subscription to be mapped
35
     * @param targetClass class of the target object
36
     * @param <T>         generic type of the target object, has to extend provide a string-constructor to receive the
37
     *           subscription id
38
     * @return the mapped object
39
     */
40
    public <T> Mono<T> fromSubscriptionVO(SubscriptionVO subscriptionVO, Class<T> targetClass) {
41

42
        Optional<MappingEnabled> optionalMappingEnabled = isMappingEnabled(targetClass);
1✔
43
        if (optionalMappingEnabled.isEmpty()) {
1✔
44
            return Mono.error(new MappingException(String.format("Mapping is not enabled for class %s", targetClass)));
×
45
        }
46

47
        MappingEnabled mappingEnabled = optionalMappingEnabled.get();
1✔
48

49
        if (!Arrays.stream(mappingEnabled.subscriptionType()).toList().contains(subscriptionVO.getType().getValue())) {
1✔
50
            return Mono.error(new MappingException(String.format("Subscription and Class type do not match - %s vs %s.",
×
51
                    subscriptionVO.getType().getValue(), Arrays.asList(mappingEnabled.subscriptionType()))));
×
52
        }
53

54
        try {
55
            Constructor<T> objectConstructor = targetClass.getDeclaredConstructor(String.class);
1✔
56
            T constructedObject = objectConstructor.newInstance(subscriptionVO.getId().toString());
1✔
57

58
            // handle "well-known" properties
59
            Map<String, Object> propertiesMap = new LinkedHashMap<>();
1✔
60
            propertiesMap.put(SubscriptionVO.JSON_PROPERTY_NAME, propertyVOFromValue(subscriptionVO.getName()));
1✔
61
            propertiesMap.put(SubscriptionVO.JSON_PROPERTY_CREATED_AT, propertyVOFromValue(subscriptionVO.getCreatedAt()));
1✔
62
            propertiesMap.put(SubscriptionVO.JSON_PROPERTY_MODIFIED_AT, propertyVOFromValue(subscriptionVO.getModifiedAt()));
1✔
63
            propertiesMap.put(SubscriptionVO.JSON_PROPERTY_EXPIRES, propertyVOFromValue(subscriptionVO.getExpires()));
1✔
64
            propertiesMap.put(SubscriptionVO.JSON_PROPERTY_Q, propertyVOFromValue(subscriptionVO.getQ()));
1✔
65
            propertiesMap.put(SubscriptionVO.JSON_PROPERTY_GEO_Q, subscriptionVO.getGeoQ());
1✔
66
            propertiesMap.put(SubscriptionVO.JSON_PROPERTY_ENTITIES, subscriptionVO.getEntities());
1✔
67
            propertiesMap.put(SubscriptionVO.JSON_PROPERTY_WATCHED_ATTRIBUTES, subscriptionVO.getWatchedAttributes());
1✔
68
            propertiesMap.put(SubscriptionVO.JSON_PROPERTY_NOTIFICATION, subscriptionVO.getNotification());
1✔
69

70
            List<Mono<T>> singleInvocations = propertiesMap.entrySet().stream()
1✔
71
                    .map(entry -> getObjectInvocation(entry, constructedObject, subscriptionVO.getId().toString()))
1✔
72
                    .toList();
1✔
73

74
            return Mono.zip(singleInvocations, constructedObjects -> constructedObject);
1✔
75

76
        } catch (NoSuchMethodException e) {
×
77
            return Mono.error(new MappingException(String.format("The class %s does not declare the required String id constructor.", targetClass)));
×
78
        } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
×
79
            return Mono.error(new MappingException(String.format("Was not able to create instance of %s.", targetClass), e));
×
80
        }
81
    }
82

83
    /**
84
     * Helper method to create a propertyVO for well-known(thus flat) properties
85
     *
86
     * @param value the value to wrap
87
     * @return a propertyVO containing the value
88
     */
89
    private PropertyVO propertyVOFromValue(Object value) {
90
        PropertyVO propertyVO = new PropertyVO();
1✔
91
        propertyVO.setValue(value);
1✔
92
        return propertyVO;
1✔
93
    }
94

95
    /**
96
     * Get the invocation on the object to be constructed.
97
     *
98
     * @param entry                   additional properties entry
99
     * @param objectUnderConstruction the new object, to be filled with the values
100
     * @param subscriptionId          id of the subscription
101
     * @param <T>                     class of the constructed object
102
     * @return                        single, emitting the constructed object
103
     */
104
    private <T> Mono<T> getObjectInvocation(Map.Entry<String, Object> entry, T objectUnderConstruction,
105
                                            String subscriptionId) {
106
        Optional<Method> optionalSetter = getCorrespondingSetterMethod(objectUnderConstruction, entry.getKey());
1✔
107
        if (optionalSetter.isEmpty()) {
1✔
108
            log.warn("Ignoring property {} for subscription {} since there is no mapping configured.", entry.getKey(), subscriptionId);
1✔
109
            return Mono.just(objectUnderConstruction);
1✔
110
        }
111
        Method setterMethod = optionalSetter.get();
1✔
112
        Optional<AttributeSetter> optionalAttributeSetter = getAttributeSetterAnnotation(setterMethod);
1✔
113
        if (optionalAttributeSetter.isEmpty()) {
1✔
114
            log.warn("Ignoring property {} for subscription {} since there is no attribute setter configured.", entry.getKey(), subscriptionId);
×
115
            return Mono.just(objectUnderConstruction);
×
116
        }
117
        AttributeSetter setterAnnotation = optionalAttributeSetter.get();
1✔
118

119
        Class<?> parameterType = getParameterType(setterMethod.getParameterTypes());
1✔
120

121
        return switch (setterAnnotation.value()) {
1✔
122
            case PROPERTY -> handleProperty((PropertyVO) entry.getValue(), objectUnderConstruction, optionalSetter.get(), parameterType);
1✔
123
            case GEO_QUERY -> handleGeoQuery((GeoQueryVO) entry.getValue(), objectUnderConstruction, optionalSetter.get(), parameterType);
1✔
124
            case ENTITY_INFO_LIST -> handleEntityInfoList((List<EntityInfoVO>) entry.getValue(), objectUnderConstruction,
1✔
125
                    optionalSetter.get(), setterAnnotation);
1✔
126
            case PROPERTY_SET -> handlePropertySet((Set<Object>) entry.getValue(), objectUnderConstruction, optionalSetter.get(), parameterType);
1✔
127
            case NOTIFICATION_PARAMS -> handleNotificationParams((NotificationParamsVO) entry.getValue(), objectUnderConstruction, optionalSetter.get(), parameterType);
1✔
128
            default -> Mono.error(new MappingException(String.format("Received type %s is not supported.", setterAnnotation.value())));
×
129
        };
130
    }
131

132
    /**
133
     * Handle the evaluation of a property entry. Returns a single, emitting the target object, while invoking the property setting method.
134
     *
135
     * @param propertyVO              the value of the property
136
     * @param objectUnderConstruction the object under construction
137
     * @param setter                  the setter to be used for the property
138
     * @param parameterType           type of the property in the target object
139
     * @param <T>                     class of the object under construction
140
     * @return the single, emitting the objectUnderConstruction
141
     */
142
    private <T> Mono<T> handleProperty(PropertyVO propertyVO, T objectUnderConstruction, Method setter, Class<?> parameterType) {
143
        return invokeWithExceptionHandling(setter, objectUnderConstruction, objectMapper.convertValue(propertyVO.getValue(), parameterType));
1✔
144
    }
145

146
    /**
147
     * Handle the evaluation of a property entry. Returns a single, emitting the target object, while invoking the property setting method.
148
     *
149
     * @param propertySet             the value of the property
150
     * @param objectUnderConstruction the object under construction
151
     * @param setter                  the setter to be used for the property
152
     * @param parameterType           type of the property in the target object
153
     * @param <T>                     class of the object under construction
154
     * @return the single, emitting the objectUnderConstruction
155
     */
156
    private <T> Mono<T> handlePropertySet(Set<Object> propertySet, T objectUnderConstruction, Method setter, Class<?> parameterType) {
157
        return invokeWithExceptionHandling(setter, objectUnderConstruction, objectMapper.convertValue(propertySet, parameterType));
1✔
158
    }
159

160
    /**
161
     * Handle the evaluation of an entity info list entry. Returns a single, emitting the target object, while invoking
162
     * the property setting method.
163
     *
164
     * @param entityInfoVOS           the list containing the entity infos
165
     * @param objectUnderConstruction the object under construction
166
     * @param setter                  the setter to be used for the property
167
     * @param <T>                     class of the object under construction
168
     * @return the single, emitting the objectUnderConstruction
169
     */
170
    private <T> Mono<T> handleEntityInfoList(List<EntityInfoVO> entityInfoVOS, T objectUnderConstruction, Method setter,
171
                                             AttributeSetter setterAnnotation) {
172
        return invokeWithExceptionHandling(setter, objectUnderConstruction, entityInfoVOS != null ?
1✔
173
                entityInfoVOS.stream().map(entityInfoVO ->
1✔
174
                objectMapper.convertValue(entityInfoVO, setterAnnotation.targetClass())).toList() : null);
1✔
175
    }
176

177
    /**
178
     * Handle the evaluation of a geo query entry. Returns a single, emitting the target object, while invoking the geo
179
     * query setting method.
180
     *
181
     * @param geoQueryVO              the value of the geo query
182
     * @param objectUnderConstruction the object under construction
183
     * @param setter                  the setter to be used for the property
184
     * @param <T>                     class of the object under construction
185
     * @return the single, emitting the objectUnderConstruction
186
     */
187
    private <T> Mono<T> handleGeoQuery(GeoQueryVO geoQueryVO, T objectUnderConstruction, Method setter, Class<?> parameterType) {
188
        return invokeWithExceptionHandling(setter, objectUnderConstruction, objectMapper.convertValue(geoQueryVO, parameterType));
1✔
189
    }
190

191
    /**
192
     * Handle the evaluation of a geo query entry. Returns a single, emitting the target object, while invoking the geo
193
     * query setting method.
194
     *
195
     * @param notificationParamsVO    the value of the geo query
196
     * @param objectUnderConstruction the object under construction
197
     * @param setter                  the setter to be used for the property
198
     * @param <T>                     class of the object under construction
199
     * @return the single, emitting the objectUnderConstruction
200
     */
201
    private <T> Mono<T> handleNotificationParams(NotificationParamsVO notificationParamsVO, T objectUnderConstruction,
202
                                       Method setter, Class<?> parameterType) {
203
        return invokeWithExceptionHandling(setter, objectUnderConstruction, objectMapper.convertValue(notificationParamsVO, parameterType));
1✔
204
    }
205

206
    /**
207
     * Invoke the given method and handle potential exceptions.
208
     */
209
    private <T> Mono<T> invokeWithExceptionHandling(Method invocationMethod, T objectUnderConstruction, Object... invocationArgs) {
210
        try {
211
            invocationMethod.invoke(objectUnderConstruction, invocationArgs);
1✔
212
            return Mono.just(objectUnderConstruction);
1✔
213
        } catch (IllegalAccessException | InvocationTargetException | RuntimeException e) {
×
214
            return Mono.error(new MappingException(String.format("Was not able to invoke method %s.", invocationMethod.getName()), e));
×
215
        }
216
    }
217

218
    /**
219
     * Return the type of the setter's parameter.
220
     */
221
    private Class<?> getParameterType(Class<?>[] arrayOfClasses) {
222
        if (arrayOfClasses.length != 1) {
1✔
223
            throw new MappingException("Setter method should only have one parameter declared.");
×
224
        }
225
        return arrayOfClasses[0];
1✔
226
    }
227

228
    /**
229
     * Get the setter method for the given property at the subscription.
230
     */
231
    private <T> Optional<Method> getCorrespondingSetterMethod(T subscription, String propertyName) {
232
        return getAttributeSettersMethods(subscription).stream().filter(m ->
1✔
233
                        getAttributeSetterAnnotation(m)
1✔
234
                                .map(attributeSetter -> attributeSetter.targetName().equals(propertyName)).orElse(false))
1✔
235
                .findFirst();
1✔
236
    }
237

238
    /**
239
     * Get all attribute setters for the given subscription
240
     */
241
    private <T> List<Method> getAttributeSettersMethods(T subscription) {
242
        return Arrays.stream(subscription.getClass().getMethods()).filter(m -> getAttributeSetterAnnotation(m)
1✔
243
                .isPresent()).toList();
1✔
244
    }
245

246
    /**
247
     * Get the attribute setter annotation from the given method, if it exists.
248
     */
249
    private Optional<AttributeSetter> getAttributeSetterAnnotation(Method m) {
250
        return Arrays.stream(m.getAnnotations()).filter(AttributeSetter.class::isInstance)
1✔
251
                .findFirst()
1✔
252
                .map(AttributeSetter.class::cast);
1✔
253
    }
254

255
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc