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

FIWARE / contract-management / #100

02 Sep 2026 02:30PM UTC coverage: 3.796% (+1.1%) from 2.677%
#100

Pull #27

web-flow
Update til.yaml
Pull Request #27: Topic/spec composition hardening

326 of 348 new or added lines in 7 files covered. (93.68%)

1 existing line in 1 file now uncovered.

1353 of 35639 relevant lines covered (3.8%)

0.04 hits per line

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

91.67
/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✔
NEW
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✔
NEW
158
                } catch (IllegalArgumentException iae) {
×
NEW
159
                        log.warn("The characteristic value {} is invalid and will be skipped.", value, iae);
×
NEW
160
                        return null;
×
161
                }
162
        }
163
}
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