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

FIWARE / contract-management / #105

01 Oct 2026 08:22AM UTC coverage: 4.489% (+0.7%) from 3.796%
#105

push

web-flow
Merge pull request #28 from FIWARE/topic/clearer-logging

Add failure reasons, downstream error descriptions and configurable log output

218 of 379 new or added lines in 33 files covered. (57.52%)

9 existing lines in 6 files now uncovered.

1606 of 35773 relevant lines covered (4.49%)

0.04 hits per line

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

88.0
/src/main/java/org/fiware/iam/tmforum/CharacteristicValues.java
1
package org.fiware.iam.tmforum;
2

3
import com.fasterxml.jackson.core.type.TypeReference;
4
import com.fasterxml.jackson.databind.ObjectMapper;
5
import lombok.extern.slf4j.Slf4j;
6
import org.fiware.iam.tmforum.productcatalog.model.CharacteristicValueSpecificationVO;
7
import org.fiware.iam.tmforum.productcatalog.model.ProductSpecificationCharacteristicVO;
8
import org.fiware.iam.tmforum.servicecatalog.model.CharacteristicSpecificationVO;
9

10
import java.util.Collection;
11
import java.util.List;
12
import java.util.Objects;
13
import java.util.Optional;
14

15
/**
16
 * Tolerant read-access to the characteristic configuration plane of a specification, normalized over
17
 * the shapes the different TMForum APIs use for the very same concept.
18
 * <p>
19
 * The container names differ per entity, which is enough to make a single reader impossible without
20
 * this normalization:
21
 * <table border="1">
22
 *     <caption>Characteristic shapes</caption>
23
 *     <tr><th>Entity</th><th>Characteristic list</th><th>Value list</th></tr>
24
 *     <tr><td>{@code ProductSpecification}</td><td>{@code productSpecCharacteristic}</td><td>{@code productSpecCharacteristicValue}</td></tr>
25
 *     <tr><td>{@code ServiceSpecification}</td><td>{@code specCharacteristic}</td><td>{@code characteristicValueSpecification}</td></tr>
26
 * </table>
27
 * <p>
28
 * Beyond the naming, TMForum does not constrain a characteristic: {@code valueType} is free text and
29
 * may be absent, the value list may be absent, and a value may hold either a single object or an
30
 * array of objects. Every writer in the data space uses a slightly different subset of that freedom,
31
 * so reading tolerates all of it - a characteristic that cannot be interpreted contributes nothing
32
 * rather than failing the caller.
33
 *
34
 * @see <a href="https://github.com/FIWARE/data-space-connector/blob/main/doc/tmforum/extensions.md">The
35
 * extension registry of the FIWARE Data Space Connector</a>
36
 */
37
@Slf4j
1 ✔
38
public final class CharacteristicValues {
39

40
        private CharacteristicValues() {
41
                // utility class
42
        }
43

44
        /**
45
         * One characteristic, reduced to the two things the connector cares about.
46
         *
47
         * @param valueType the semantic tag the connector discriminates on, may be {@code null}
48
         * @param values    the raw values of the characteristic, never {@code null}
49
         */
50
        public record Characteristic(String valueType, List<Object> values) {
1 ✔
51
        }
52

53
        /**
54
         * Normalize the characteristics of a {@code ProductSpecification}.
55
         *
56
         * @param characteristics the {@code productSpecCharacteristic} list, may be {@code null}
57
         * @return the normalized characteristics, never {@code null}
58
         */
59
        public static List<Characteristic> ofProductSpecification(
60
                        List<ProductSpecificationCharacteristicVO> characteristics) {
61
                return Optional.ofNullable(characteristics)
1 ✔
62
                                .orElseGet(List::of)
1 ✔
63
                                .stream()
1 ✔
64
                                .filter(Objects::nonNull)
1 ✔
65
                                .map(characteristic -> new Characteristic(characteristic.getValueType(),
1 ✔
66
                                                rawValues(characteristic.getProductSpecCharacteristicValue(),
1 ✔
67
                                                                CharacteristicValueSpecificationVO::getValue)))
68
                                .toList();
1 ✔
69
        }
70

71
        /**
72
         * Normalize the characteristics of a {@code ServiceSpecification}.
73
         *
74
         * @param characteristics the {@code specCharacteristic} list, may be {@code null}
75
         * @return the normalized characteristics, never {@code null}
76
         */
77
        public static List<Characteristic> ofServiceSpecification(List<CharacteristicSpecificationVO> characteristics) {
78
                return Optional.ofNullable(characteristics)
1 ✔
79
                                .orElseGet(List::of)
1 ✔
80
                                .stream()
1 ✔
81
                                .filter(Objects::nonNull)
1 ✔
82
                                .map(characteristic -> new Characteristic(characteristic.getValueType(),
1 ✔
83
                                                rawValues(characteristic.getCharacteristicValueSpecification(),
1 ✔
84
                                                                org.fiware.iam.tmforum.servicecatalog.model.CharacteristicValueSpecificationVO::getValue)))
85
                                .toList();
1 ✔
86
        }
87

88
        /**
89
         * Find the first characteristic declaring the given {@code valueType}.
90
         * <p>
91
         * The comparison is null-safe in both directions: a {@code null} list yields an empty result and a
92
         * characteristic without a {@code valueType} is skipped instead of raising a
93
         * {@link NullPointerException}.
94
         *
95
         * @param characteristics the normalized characteristics, may be {@code null}
96
         * @param valueType       the {@code valueType} to look for
97
         * @return the first matching characteristic, or {@link Optional#empty()} if none matches
98
         */
99
        public static Optional<Characteristic> byValueType(List<Characteristic> characteristics, String valueType) {
100
                return Optional.ofNullable(characteristics)
1 ✔
101
                                .orElseGet(List::of)
1 ✔
102
                                .stream()
1 ✔
103
                                .filter(Objects::nonNull)
1 ✔
104
                                .filter(characteristic -> valueType.equals(characteristic.valueType()))
1 ✔
105
                                .findFirst();
1 ✔
106
        }
107

108
        /**
109
         * Read every value of the given characteristic as a flat list of {@code elementType}.
110
         * <p>
111
         * A value holding an array contributes all of its elements, a value holding a single object
112
         * contributes that object - both shapes occur in deployed specifications. Values that cannot be
113
         * converted are logged and skipped, so one malformed entry does not hide the well-formed ones.
114
         *
115
         * @param objectMapper   mapper to convert the untyped characteristic values with
116
         * @param characteristic the characteristic to read, may be {@code null}
117
         * @param elementType    the type a single value is expected to have
118
         * @param <T>            the type a single value is expected to have
119
         * @return every readable value, never {@code null}
120
         */
121
        public static <T> List<T> flatten(ObjectMapper objectMapper, Characteristic characteristic,
122
                        TypeReference<T> elementType) {
123
                if (characteristic == null) {
1 ✔
124
                        return List.of();
×
125
                }
126
                return characteristic.values()
1 ✔
127
                                .stream()
1 ✔
128
                                .flatMap(value -> toElements(objectMapper, value, elementType).stream())
1 ✔
129
                                .toList();
1 ✔
130
        }
131

132
        private static <V> List<Object> rawValues(List<V> values, java.util.function.Function<V, Object> valueAccessor) {
133
                return Optional.ofNullable(values)
1 ✔
134
                                .orElseGet(List::of)
1 ✔
135
                                .stream()
1 ✔
136
                                .filter(Objects::nonNull)
1 ✔
137
                                .map(valueAccessor)
1 ✔
138
                                .filter(Objects::nonNull)
1 ✔
139
                                .toList();
1 ✔
140
        }
141

142
        private static <T> List<T> toElements(ObjectMapper objectMapper, Object value, TypeReference<T> elementType) {
143
                if (value instanceof Collection<?> collection) {
1 ✔
144
                        return collection.stream()
1 ✔
145
                                        .filter(Objects::nonNull)
1 ✔
146
                                        .map(element -> convert(objectMapper, element, elementType))
1 ✔
147
                                        .filter(Objects::nonNull)
1 ✔
148
                                        .toList();
1 ✔
149
                }
150
                return Optional.ofNullable(convert(objectMapper, value, elementType))
1 ✔
151
                                .map(List::of)
1 ✔
152
                                .orElseGet(List::of);
1 ✔
153
        }
154

155
        private static <T> T convert(ObjectMapper objectMapper, Object value, TypeReference<T> elementType) {
156
                try {
157
                        return objectMapper.convertValue(value, elementType);
1 ✔
158
                } catch (IllegalArgumentException iae) {
×
NEW
159
                        log.warn("A characteristic value is not a valid {} and is skipped, the configuration it carries is NOT applied: {}",
×
NEW
160
                                        elementType.getType().getTypeName(), iae.getMessage());
×
NEW
161
                        log.debug("The skipped characteristic value: {}", value);
×
UNCOV
162
                        return null;
×
163
                }
164
        }
165
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc