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

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

29 Aug 2023 05:45AM UTC coverage: 82.505% (+8.4%) from 74.118%
#121

push

beknazaresenbek
Removed unused class

448 of 543 relevant lines covered (82.5%)

0.83 hits per line

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

82.29
/src/main/java/io/github/wistefan/mapping/JavaObjectMapper.java
1
package io.github.wistefan.mapping;
2

3
import com.fasterxml.jackson.databind.DeserializationFeature;
4
import com.fasterxml.jackson.databind.ObjectMapper;
5
import io.github.wistefan.mapping.annotations.*;
6
import io.github.wistefan.mapping.subscription.SubscriptionMixin;
7
import lombok.RequiredArgsConstructor;
8
import lombok.extern.slf4j.Slf4j;
9
import org.fiware.ngsi.model.*;
10

11
import javax.inject.Singleton;
12
import java.lang.annotation.Annotation;
13
import java.lang.reflect.InvocationTargetException;
14
import java.lang.reflect.Method;
15
import java.net.URI;
16
import java.util.*;
17
import java.util.stream.Collectors;
18
import java.util.stream.Stream;
19

20
/**
21
 * Mapper to handle translation from Java-Objects into NGSI-LD entities.
22
 */
23
@Slf4j
1✔
24
@Singleton
25
@RequiredArgsConstructor
1✔
26
public class JavaObjectMapper extends Mapper {
27

28
        private static final String DEFAULT_CONTEXT = "https://smartdatamodels.org/context.jsonld";
29

30
        public static final String NO_MAPPING_DEFINED_FOR_METHOD_TEMPLATE = "No mapping defined for method %s";
31
        public static final String WAS_NOT_ABLE_INVOKE_METHOD_TEMPLATE = "Was not able invoke method %s on %s";
32

33
        /**
34
         * Translate the attribute path for the given object into the path in the ngsi-ld model.
35
         *
36
         * @param attributePath the original path
37
         * @param tClass        class to use for translation
38
         * @return the path in ngsi-ld and the type of the target attribute
39
         */
40
        public static <T> NgsiLdAttribute getNGSIAttributePath(List<String> attributePath, Class<T> tClass) {
41
                List<String> ngsiAttributePath = new ArrayList<>();
1✔
42
                QueryAttributeType type = QueryAttributeType.STRING;
1✔
43
                String currentAttribute = attributePath.get(0);
1✔
44
                if (isMappingEnabled(tClass).isPresent()) {
1✔
45
                        // for mapped properties, we have to climb down the property names
46
                        // we need the setter in case of type-erasure and the getter in all other cases
47
                        Optional<Method> setter = getSetterMethodByName(tClass, currentAttribute)
1✔
48
                                        .filter(m -> getAttributeSetter(m.getAnnotations()).isPresent())
1✔
49
                                        .findFirst();
1✔
50
                        Optional<Method> getter = getGetterMethodByName(tClass, currentAttribute)
1✔
51
                                        .filter(m -> getAttributeGetter(m.getAnnotations()).isPresent())
1✔
52
                                        .findFirst();
1✔
53
                        if (setter.isPresent() && getter.isPresent()) {
1✔
54
                                Method setterMethod = setter.get();
1✔
55
                                Method getterMethod = getter.get();
1✔
56
                                // no need to check again
57
                                AttributeSetter setterAnnotation = getAttributeSetter(setterMethod.getAnnotations()).get();
1✔
58
                                ngsiAttributePath.add(setterAnnotation.targetName());
1✔
59
                                type = fromClass(getterMethod.getReturnType());
1✔
60
                                if (attributePath.size() > 1) {
1✔
61
                                        List<String> subPaths = attributePath.subList(1, attributePath.size());
1✔
62
                                        if (setterAnnotation.targetClass() != Object.class) {
1✔
63
                                                NgsiLdAttribute subAttribute = getNGSIAttributePath(subPaths, setterAnnotation.targetClass());
1✔
64
                                                ngsiAttributePath.addAll(subAttribute.path());
1✔
65
                                                type = subAttribute.type();
1✔
66
                                        } else {
1✔
67
                                                NgsiLdAttribute subAttribute = getNGSIAttributePath(subPaths, getterMethod.getReturnType());
1✔
68
                                                ngsiAttributePath.addAll(subAttribute.path());
1✔
69
                                                type = subAttribute.type();
1✔
70
                                        }
71
                                }
72
                        } else {
1✔
73
                                log.warn("No corresponding field does exist for attribute {} on {}.", currentAttribute,
×
74
                                                tClass.getCanonicalName());
×
75
                        }
76
                } else {
1✔
77
                        // we can use the "plain" object field-names, no additional mapping happens anymore
78
                        ngsiAttributePath.addAll(attributePath);
1✔
79
                        type = evaluateType(attributePath, tClass);
1✔
80
                }
81
                return new NgsiLdAttribute(ngsiAttributePath, type);
1✔
82
        }
83

84
        private static QueryAttributeType evaluateType(List<String> path, Class<?> tClass) {
85
                Class<?> currentClass = tClass;
1✔
86
                for (String s : path) {
1✔
87
                        try {
88
                                Optional<Class<?>> optionalReturn = getGetterMethodByName(currentClass, s).findAny()
1✔
89
                                                .map(Method::getReturnType);
1✔
90
                                if (optionalReturn.isPresent()) {
1✔
91
                                        currentClass = optionalReturn.get();
1✔
92
                                } else {
93
                                        currentClass = currentClass.getField(s).getType();
×
94
                                }
95
                        } catch (NoSuchFieldException e) {
×
96
                                throw new MappingException(String.format("No field %s exists for %s.", s, tClass.getCanonicalName()),
×
97
                                                e);
98
                        }
1✔
99
                }
1✔
100
                return fromClass(currentClass);
1✔
101
        }
102

103
        private static QueryAttributeType fromClass(Class<?> tClass) {
104
                if (Number.class.isAssignableFrom(tClass)) {
1✔
105
                        return QueryAttributeType.NUMBER;
1✔
106
                } else if (Boolean.class.isAssignableFrom(tClass)) {
1✔
107
                        return QueryAttributeType.BOOLEAN;
1✔
108
                }
109
                return QueryAttributeType.STRING;
1✔
110

111
        }
112

113
        public static <T> Stream<Method> getSetterMethodByName(Class<T> tClass, String propertyName) {
114
                return Arrays.stream(tClass.getMethods())
1✔
115
                                .filter(m -> getCorrespondingSetterFieldName(m.getName()).equals(propertyName));
1✔
116
        }
117

118
        public static <T> Stream<Method> getGetterMethodByName(Class<T> tClass, String propertyName) {
119
                return Arrays.stream(tClass.getMethods())
1✔
120
                                .filter(m -> getCorrespondingGetterFieldName(m.getName()).equals(propertyName));
1✔
121
        }
122

123
        private static String getCorrespondingGetterFieldName(String methodName) {
124
                var fieldName = "";
1✔
125
                if (methodName.matches("^get[A-Z].*")) {
1✔
126
                        fieldName = methodName.replaceFirst("get", "");
1✔
127
                } else if (methodName.matches("^is[A-Z].*")) {
1✔
128
                        fieldName = methodName.replaceFirst("is", "");
×
129
                } else {
130
                        log.debug("The method {} is neither a get or is.", methodName);
1✔
131
                        return fieldName;
1✔
132
                }
133
                return fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
1✔
134
        }
135

136
        private static String getCorrespondingSetterFieldName(String methodName) {
137
                var fieldName = "";
1✔
138
                if (methodName.matches("^set[A-Z].*")) {
1✔
139
                        fieldName = methodName.replaceFirst("set", "");
1✔
140
                } else if (methodName.matches("^is[A-Z].*")) {
1✔
141
                        fieldName = methodName.replaceFirst("is", "");
×
142
                } else {
143
                        log.debug("The method {} is neither a set or is.", methodName);
1✔
144
                        return fieldName;
1✔
145
                }
146
                return fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
1✔
147
        }
148

149
        /**
150
         * Translate the given object into an Entity.
151
         *
152
         * @param entity the object representing the entity
153
         * @param <T>    class of the entity
154
         * @return the NGIS-LD entity objet
155
         */
156
        public <T> EntityVO toEntityVO(T entity) {
157
                isMappingEnabled(entity.getClass())
1✔
158
                                .orElseThrow(() -> new UnsupportedOperationException(
1✔
159
                                                String.format("Generic mapping to NGSI-LD entities is not supported for object %s",
1✔
160
                                                                entity)));
161

162
                List<Method> entityIdMethod = new ArrayList<>();
1✔
163
                List<Method> entityTypeMethod = new ArrayList<>();
1✔
164
                List<Method> propertyMethods = new ArrayList<>();
1✔
165
                List<Method> propertyListMethods = new ArrayList<>();
1✔
166
                List<Method> relationshipMethods = new ArrayList<>();
1✔
167
                List<Method> relationshipListMethods = new ArrayList<>();
1✔
168
                List<Method> geoPropertyMethods = new ArrayList<>();
1✔
169
                List<Method> geoPropertyListMethods = new ArrayList<>();
1✔
170

171
                Arrays.stream(entity.getClass().getMethods()).forEach(method -> {
1✔
172
                        if (isEntityIdMethod(method)) {
1✔
173
                                entityIdMethod.add(method);
1✔
174
                        } else if (isEntityTypeMethod(method)) {
1✔
175
                                entityTypeMethod.add(method);
1✔
176
                        } else {
177
                                getAttributeGetter(method.getAnnotations()).ifPresent(annotation -> {
1✔
178
                                        switch (annotation.value()) {
1✔
179
                                                case PROPERTY -> propertyMethods.add(method);
1✔
180
                                                // We handle property lists the same way as properties, since it is mapped as a property which value is a json array.
181
                                                // A real NGSI-LD property list would require a datasetId, that is not provided here.
182
                                                case PROPERTY_LIST -> propertyMethods.add(method);
1✔
183
                                                case GEO_PROPERTY -> geoPropertyMethods.add(method);
×
184
                                                case RELATIONSHIP -> relationshipMethods.add(method);
1✔
185
                                                case GEO_PROPERTY_LIST -> geoPropertyListMethods.add(method);
×
186
                                                case RELATIONSHIP_LIST -> relationshipListMethods.add(method);
1✔
187
                                                default -> throw new UnsupportedOperationException(
×
188
                                                                String.format("Mapping target %s is not supported.", annotation.value()));
×
189
                                        }
190
                                });
1✔
191
                        }
192
                });
1✔
193

194
                if (entityIdMethod.size() != 1) {
1✔
195
                        throw new MappingException(
1✔
196
                                        String.format("The provided object declares %s id methods, exactly one is expected.",
1✔
197
                                                        entityIdMethod.size()));
1✔
198
                }
199
                if (entityTypeMethod.size() != 1) {
1✔
200
                        throw new MappingException(
1✔
201
                                        String.format("The provided object declares %s type methods, exactly one is expected.",
1✔
202
                                                        entityTypeMethod.size()));
1✔
203

204
                }
205

206
                return buildEntity(entity, entityIdMethod.get(0), entityTypeMethod.get(0), propertyMethods,
1✔
207
                                propertyListMethods,
208
                                geoPropertyMethods, relationshipMethods, relationshipListMethods);
209
        }
210

211
        /**
212
         * Build the entity from its declared methods.
213
         */
214
        private <T> EntityVO buildEntity(T entity, Method entityIdMethod, Method entityTypeMethod,
215
                        List<Method> propertyMethods, List<Method> propertyListMethods,
216
                        List<Method> geoPropertyMethods,
217
                        List<Method> relationshipMethods, List<Method> relationshipListMethods) {
218

219
                EntityVO entityVO = new EntityVO();
1✔
220
                // TODO: Check if we need that configurable
221
                entityVO.setAtContext(DEFAULT_CONTEXT);
1✔
222

223
                // TODO: include extraction via annotation for all well-known attributes
224
                entityVO.setOperationSpace(null);
1✔
225
                entityVO.setObservationSpace(null);
1✔
226
                entityVO.setLocation(null);
1✔
227

228
                try {
229
                        Object entityIdObject = entityIdMethod.invoke(entity);
1✔
230
                        if (!(entityIdObject instanceof URI)) {
1✔
231
                                throw new MappingException(
1✔
232
                                                String.format("The entityId method does not return a valid URI for entity %s.", entity));
1✔
233
                        }
234
                        entityVO.id((URI) entityIdObject);
1✔
235

236
                        Object entityTypeObject = entityTypeMethod.invoke(entity);
1✔
237
                        if (!(entityTypeObject instanceof String)) {
1✔
238
                                throw new MappingException("The entityType method does not return a valid String.");
1✔
239
                        }
240
                        entityVO.setType((String) entityTypeObject);
1✔
241
                } catch (IllegalAccessException | InvocationTargetException e) {
1✔
242
                        throw new MappingException(String.format(WAS_NOT_ABLE_INVOKE_METHOD_TEMPLATE, "unknown-method", entity),
1✔
243
                                        e);
244
                }
1✔
245

246
                Map<String, AdditionalPropertyVO> additionalProperties = new LinkedHashMap<>();
1✔
247
                additionalProperties.putAll(buildProperties(entity, propertyMethods));
1✔
248
                additionalProperties.putAll(buildPropertyList(entity, propertyListMethods));
1✔
249
                additionalProperties.putAll(buildGeoProperties(entity, geoPropertyMethods));
1✔
250
                Map<String, RelationshipVO> relationshipVOMap = buildRelationships(entity, relationshipMethods);
1✔
251
                Map<String, RelationshipListVO> relationshipListVOMap = buildRelationshipList(entity,
1✔
252
                                relationshipListMethods);
253
                // we need to post-process the relationships, since orion-ld only accepts dataset-ids for lists > 1
254
                relationshipVOMap.entrySet().stream().forEach(e -> e.getValue().setDatasetId(null));
1✔
255
                relationshipListVOMap.entrySet().stream().forEach(e -> {
1✔
256
                        if (e.getValue().size() == 1) {
1✔
257
                                e.getValue().get(0).setDatasetId(null);
1✔
258
                        }
259
                });
1✔
260

261
                additionalProperties.putAll(relationshipVOMap);
1✔
262
                additionalProperties.putAll(relationshipListVOMap);
1✔
263

264
                additionalProperties.forEach(entityVO::setAdditionalProperties);
1✔
265

266
                return entityVO;
1✔
267
        }
268

269
        public <T> SubscriptionVO toSubscriptionVO(T subscription) {
270
                isMappingEnabled(subscription.getClass())
1✔
271
                                .orElseThrow(() -> new UnsupportedOperationException(
1✔
272
                                                String.format("Generic mapping to NGSI-LD subscriptions is not supported for object %s",
×
273
                                                                subscription)));
274

275
                ObjectMapper objectMapper = new ObjectMapper();
1✔
276
                objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
1✔
277
                objectMapper.addMixIn(SubscriptionVO.class, SubscriptionMixin.class);
1✔
278

279
                SubscriptionVO subscriptionVO = objectMapper.convertValue(subscription, SubscriptionVO.class);
1✔
280
                subscriptionVO.setAtContext(DEFAULT_CONTEXT);
1✔
281

282
                return subscriptionVO;
1✔
283
        }
284

285
        /**
286
         * Check if the given method defines the entity type
287
         */
288
        private boolean isEntityTypeMethod(Method method) {
289
                return Arrays.stream(method.getAnnotations()).anyMatch(EntityType.class::isInstance);
1✔
290
        }
291

292
        /**
293
         * Check if the given method defines the entity id
294
         */
295
        private boolean isEntityIdMethod(Method method) {
296
                return Arrays.stream(method.getAnnotations()).anyMatch(EntityId.class::isInstance);
1✔
297
        }
298

299
        /**
300
         * Build a relationship from the declared methods
301
         */
302
        private <T> Map<String, RelationshipVO> buildRelationships(T entity, List<Method> relationshipMethods) {
303
                return relationshipMethods.stream()
1✔
304
                                .map(method -> methodToRelationshipEntry(entity, method))
1✔
305
                                .filter(Optional::isPresent)
1✔
306
                                .map(Optional::get)
1✔
307
                                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
1✔
308
        }
309

310
        /**
311
         * Build a list of relationships from the declared methods
312
         */
313
        private <
314
                        T> Map<String, RelationshipListVO> buildRelationshipList(T entity, List<Method> relationshipListMethods) {
315
                return relationshipListMethods.stream()
1✔
316
                                .map(relationshipMethod -> methodToRelationshipListEntry(entity, relationshipMethod))
1✔
317
                                .filter(Optional::isPresent)
1✔
318
                                .map(Optional::get)
1✔
319
                                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
1✔
320
        }
321

322
        /*
323
         * Build a list of properties from the declared methods
324
         */
325
        private <T> Map<String, PropertyListVO> buildPropertyList(T entity, List<Method> propertyListMethods) {
326
                return propertyListMethods.stream()
1✔
327
                                .map(propertyListMethod -> methodToPropertyListEntry(entity, propertyListMethod))
1✔
328
                                .filter(Optional::isPresent)
1✔
329
                                .map(Optional::get)
1✔
330
                                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
1✔
331
        }
332

333
        /**
334
         * Build geoproperties from the declared methods
335
         */
336
        private <T> Map<String, GeoPropertyVO> buildGeoProperties(T entity, List<Method> geoPropertyMethods) {
337
                return geoPropertyMethods.stream()
1✔
338
                                .map(geoPropertyMethod -> methodToGeoPropertyEntry(entity, geoPropertyMethod))
1✔
339
                                .filter(Optional::isPresent)
1✔
340
                                .map(Optional::get)
1✔
341
                                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
1✔
342
        }
343

344
        /**
345
         * Build properties from the declared methods
346
         */
347
        private <T> Map<String, PropertyVO> buildProperties(T entity, List<Method> propertyMethods) {
348
                return propertyMethods.stream()
1✔
349
                                .map(propertyMethod -> methodToPropertyEntry(entity, propertyMethod))
1✔
350
                                .filter(Optional::isPresent)
1✔
351
                                .map(Optional::get)
1✔
352
                                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
1✔
353
        }
354

355
        /**
356
         * Return method defining the object of the relationship for the given entity, if exists.
357
         */
358
        private <T> Optional<Method> getRelationshipObjectMethod(T entity) {
359
                return Arrays.stream(entity.getClass().getMethods()).filter(this::isRelationShipObject).findFirst();
1✔
360
        }
361

362
        /**
363
         * Return method defining the datasetid for the given entity, if exists.
364
         */
365
        private <T> Optional<Method> getDatasetIdMethod(T entity) {
366
                return Arrays.stream(entity.getClass().getMethods()).filter(this::isDatasetId).findFirst();
1✔
367
        }
368

369
        /**
370
         * Get all methods declared as attribute getters.
371
         */
372
        private <T> List<Method> getAttributeGettersMethods(T entity) {
373
                return Arrays.stream(entity.getClass().getMethods())
1✔
374
                                .filter(m -> getAttributeGetterAnnotation(m).isPresent())
1✔
375
                                .toList();
1✔
376
        }
377

378
        /**
379
         * return the {@link  AttributeGetter} annotation for the method if there is such.
380
         */
381
        private Optional<AttributeGetter> getAttributeGetterAnnotation(Method m) {
382
                return Arrays.stream(m.getAnnotations()).filter(AttributeGetter.class::isInstance).findFirst()
1✔
383
                                .map(AttributeGetter.class::cast);
1✔
384
        }
385

386
        /**
387
         * Find the attribute getter from all the annotations.
388
         */
389
        private static Optional<AttributeGetter> getAttributeGetter(Annotation[] annotations) {
390
                return Arrays.stream(annotations).filter(AttributeGetter.class::isInstance).map(AttributeGetter.class::cast)
1✔
391
                                .findFirst();
1✔
392
        }
393

394
        /**
395
         * Find the attribute setter from all the annotations.
396
         */
397
        private static Optional<AttributeSetter> getAttributeSetter(Annotation[] annotations) {
398
                return Arrays.stream(annotations).filter(AttributeSetter.class::isInstance).map(AttributeSetter.class::cast)
1✔
399
                                .findFirst();
1✔
400
        }
401

402
        /**
403
         * Check if the given method is declared to be used as object of a relationship
404
         */
405
        private boolean isRelationShipObject(Method m) {
406
                return Arrays.stream(m.getAnnotations()).anyMatch(RelationshipObject.class::isInstance);
1✔
407
        }
408

409
        /**
410
         * Check if the given method is declared to be used as datasetId
411
         */
412
        private boolean isDatasetId(Method m) {
413
                return Arrays.stream(m.getAnnotations()).anyMatch(DatasetId.class::isInstance);
1✔
414
        }
415

416
        /**
417
         * Build a property entry from the given method on the entity
418
         */
419
        private <T> Optional<Map.Entry<String, PropertyVO>> methodToPropertyEntry(T entity, Method method) {
420
                try {
421
                        Object propertyObject = method.invoke(entity);
1✔
422
                        if (propertyObject == null) {
1✔
423
                                return Optional.empty();
×
424
                        }
425
                        AttributeGetter attributeMapping = getAttributeGetter(method.getAnnotations()).orElseThrow(
1✔
426
                                        () -> new MappingException(String.format(NO_MAPPING_DEFINED_FOR_METHOD_TEMPLATE, method)));
×
427

428
                        PropertyVO propertyVO = new PropertyVO();
1✔
429
                        propertyVO.setValue(propertyObject);
1✔
430

431
                        return Optional.of(new AbstractMap.SimpleEntry<>(attributeMapping.targetName(), propertyVO));
1✔
432
                } catch (IllegalAccessException | InvocationTargetException e) {
1✔
433
                        throw new MappingException(String.format(WAS_NOT_ABLE_INVOKE_METHOD_TEMPLATE, method, entity));
1✔
434
                }
435
        }
436

437
        /**
438
         * Build a geo-property entry from the given method on the entity
439
         */
440
        private <T> Optional<Map.Entry<String, GeoPropertyVO>> methodToGeoPropertyEntry(T entity, Method method) {
441
                try {
442
                        Object o = method.invoke(entity);
×
443
                        if (o == null) {
×
444
                                return Optional.empty();
×
445
                        }
446
                        AttributeGetter attributeMapping = getAttributeGetter(method.getAnnotations()).orElseThrow(
×
447
                                        () -> new MappingException(String.format(NO_MAPPING_DEFINED_FOR_METHOD_TEMPLATE, method)));
×
448
                        GeoPropertyVO geoPropertyVO = new GeoPropertyVO();
×
449
                        geoPropertyVO.setValue(o);
×
450
                        return Optional.of(new AbstractMap.SimpleEntry<>(attributeMapping.targetName(), geoPropertyVO));
×
451
                } catch (IllegalAccessException | InvocationTargetException e) {
×
452
                        throw new MappingException(String.format(WAS_NOT_ABLE_INVOKE_METHOD_TEMPLATE, method, entity));
×
453
                }
454
        }
455

456
        /**
457
         * Build a relationship entry from the given method on the entity
458
         */
459
        private <T> Optional<Map.Entry<String, RelationshipVO>> methodToRelationshipEntry(T entity, Method method) {
460
                try {
461
                        Object relationShipObject = method.invoke(entity);
1✔
462
                        if (relationShipObject == null) {
1✔
463
                                return Optional.empty();
×
464
                        }
465
                        RelationshipVO relationshipVO = getRelationshipVO(method, relationShipObject);
1✔
466
                        AttributeGetter attributeMapping = getAttributeGetter(method.getAnnotations()).orElseThrow(
1✔
467
                                        () -> new MappingException(String.format(NO_MAPPING_DEFINED_FOR_METHOD_TEMPLATE, method)));
×
468
                        return Optional.of(new AbstractMap.SimpleEntry<>(attributeMapping.targetName(), relationshipVO));
1✔
469
                } catch (IllegalAccessException | InvocationTargetException e) {
1✔
470
                        throw new MappingException(String.format(WAS_NOT_ABLE_INVOKE_METHOD_TEMPLATE, method, entity));
1✔
471
                }
472
        }
473

474
        /**
475
         * Build a relationship list entry from the given method on the entity
476
         */
477
        private <
478
                        T> Optional<Map.Entry<String, RelationshipListVO>> methodToRelationshipListEntry(T entity, Method method) {
479
                try {
480
                        Object o = method.invoke(entity);
1✔
481
                        if (o == null) {
1✔
482
                                return Optional.empty();
1✔
483
                        }
484
                        if (!(o instanceof List)) {
1✔
485
                                throw new MappingException(
1✔
486
                                                String.format("Relationship list method %s::%s did not return a List.", entity, method));
1✔
487
                        }
488
                        List<Object> entityObjects = (List) o;
1✔
489

490
                        AttributeGetter attributeGetter = getAttributeGetter(method.getAnnotations()).orElseThrow(
1✔
491
                                        () -> new MappingException(String.format(NO_MAPPING_DEFINED_FOR_METHOD_TEMPLATE, method)));
×
492
                        RelationshipListVO relationshipVOS = new RelationshipListVO();
1✔
493

494
                        relationshipVOS.addAll(entityObjects.stream()
1✔
495
                                        .filter(Objects::nonNull)
1✔
496
                                        .map(entityObject -> getRelationshipVO(method, entityObject))
1✔
497
                                        .toList());
1✔
498
                        return Optional.of(new AbstractMap.SimpleEntry<>(attributeGetter.targetName(), relationshipVOS));
1✔
499
                } catch (IllegalAccessException | InvocationTargetException e) {
1✔
500
                        throw new MappingException(String.format(WAS_NOT_ABLE_INVOKE_METHOD_TEMPLATE, method, entity));
1✔
501
                }
502
        }
503

504
        /**
505
         * Get the relationship for the given method and relationship object
506
         */
507
        private RelationshipVO getRelationshipVO(Method method, Object relationShipObject) {
508
                try {
509

510
                        Method objectMethod = getRelationshipObjectMethod(relationShipObject).orElseThrow(
1✔
511
                                        () -> new MappingException(
1✔
512
                                                        String.format("The relationship %s-%s does not provide an object method.",
1✔
513
                                                                        relationShipObject, method)));
514
                        Object objectObject = objectMethod.invoke(relationShipObject);
1✔
515
                        if (!(objectObject instanceof URI)) {
1✔
516
                                throw new MappingException(
1✔
517
                                                String.format("The object %s of the relationship is not a URI.", relationShipObject));
1✔
518
                        }
519

520
                        Method datasetIdMethod = getDatasetIdMethod(relationShipObject).orElseThrow(() -> new MappingException(
1✔
521
                                        String.format("The relationship %s-%s does not provide a datasetId method.", relationShipObject,
×
522
                                                        method)));
523
                        Object datasetIdObject = datasetIdMethod.invoke(relationShipObject);
1✔
524
                        if (!(datasetIdObject instanceof URI)) {
1✔
525
                                throw new MappingException(
1✔
526
                                                String.format("The datasetId %s of the relationship is not a URI.", relationShipObject));
1✔
527
                        }
528
                        RelationshipVO relationshipVO = new RelationshipVO();
1✔
529
                        relationshipVO.setObject((URI) objectObject);
1✔
530
                        relationshipVO.setDatasetId((URI) datasetIdObject);
1✔
531

532
                        // get additional properties. We do not support more depth/complexity for now
533
                        Map<String, AdditionalPropertyVO> additionalProperties = getAttributeGettersMethods(
1✔
534
                                        relationShipObject).stream()
1✔
535
                                        .map(getterMethod -> getAdditionalPropertyEntryFromMethod(relationShipObject, getterMethod))
1✔
536
                                        .filter(Optional::isPresent)
1✔
537
                                        .map(Optional::get)
1✔
538
                                        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
1✔
539

540
                        additionalProperties.forEach(relationshipVO::setAdditionalProperties);
1✔
541

542
                        return relationshipVO;
1✔
543
                } catch (IllegalAccessException | InvocationTargetException e) {
×
544
                        throw new MappingException(
×
545
                                        String.format(WAS_NOT_ABLE_INVOKE_METHOD_TEMPLATE, method, relationShipObject));
×
546
                }
547
        }
548

549
        /**
550
         * Get all additional properties for the object of the relationship
551
         */
552
        private Optional<Map.Entry<String, PropertyVO>> getAdditionalPropertyEntryFromMethod(Object relationShipObject,
553
                        Method getterMethod) {
554
                Optional<AttributeGetter> optionalAttributeGetter = getAttributeGetter(getterMethod.getAnnotations());
1✔
555
                if (optionalAttributeGetter.isEmpty() || !optionalAttributeGetter.get().embedProperty()) {
1✔
556
                        return Optional.empty();
1✔
557
                }
558
                if (optionalAttributeGetter.get().value().equals(AttributeType.PROPERTY)) {
1✔
559
                        return methodToPropertyEntry(relationShipObject, getterMethod);
1✔
560
                } else {
561
                        return Optional.empty();
×
562
                }
563
        }
564

565
        /**
566
         * Build a property list entry from the given method on the entity
567
         */
568
        private <T> Optional<Map.Entry<String, PropertyListVO>> methodToPropertyListEntry(T entity, Method method) {
569
                try {
570
                        Object o = method.invoke(entity);
×
571
                        if (o == null) {
×
572
                                return Optional.empty();
×
573
                        }
574
                        if (!(o instanceof List)) {
×
575
                                throw new MappingException(
×
576
                                                String.format("Property list method %s::%s did not return a List.", entity, method));
×
577
                        }
578
                        AttributeGetter attributeMapping = getAttributeGetter(method.getAnnotations()).orElseThrow(
×
579
                                        () -> new MappingException(String.format(NO_MAPPING_DEFINED_FOR_METHOD_TEMPLATE, method)));
×
580
                        List<Object> entityObjects = (List) o;
×
581

582
                        PropertyListVO propertyVOS = new PropertyListVO();
×
583

584
                        propertyVOS.addAll(entityObjects.stream()
×
585
                                        .map(propertyObject -> {
×
586
                                                PropertyVO propertyVO = new PropertyVO();
×
587
                                                propertyVO.setValue(propertyObject);
×
588
                                                return propertyVO;
×
589
                                        })
590
                                        .toList());
×
591

592
                        return Optional.of(new AbstractMap.SimpleEntry<>(attributeMapping.targetName(), propertyVOS));
×
593
                } catch (IllegalAccessException | InvocationTargetException e) {
×
594
                        throw new MappingException(String.format(WAS_NOT_ABLE_INVOKE_METHOD_TEMPLATE, method, entity));
×
595
                }
596
        }
597

598
}
599

600

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