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

FIWARE / contract-management / #26

28 Jan 2025 10:59AM UTC coverage: 0.982% (-0.6%) from 1.629%
#26

push

web-flow
Merge pull request #3 from FIWARE/tpp-integration

Tpp integration

99 of 500 new or added lines in 14 files covered. (19.8%)

4 existing lines in 3 files now uncovered.

281 of 28625 relevant lines covered (0.98%)

0.01 hits per line

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

0.0
/src/main/java/org/fiware/iam/tmforum/handlers/ProductOfferingEventHandler.java
1
package org.fiware.iam.tmforum.handlers;
2

3
import com.fasterxml.jackson.databind.ObjectMapper;
4
import io.micronaut.http.HttpResponse;
5
import io.micronaut.http.HttpStatus;
6
import jakarta.inject.Singleton;
7
import lombok.RequiredArgsConstructor;
8
import lombok.extern.slf4j.Slf4j;
9
import org.fiware.iam.tmforum.productcatalog.api.ProductSpecificationApiClient;
10
import org.fiware.iam.tmforum.productcatalog.model.*;
11
import org.fiware.rainbow.api.CatalogApiClient;
12
import org.fiware.rainbow.model.CatalogVO;
13
import org.fiware.rainbow.model.DataServiceVO;
14
import org.fiware.rainbow.model.NewDataserviceVO;
15
import reactor.core.publisher.Mono;
16

17
import java.util.*;
18
import java.util.function.Function;
19

20
/**
21
 * Handle all incoming events in connection to ProductOfferings
22
 */
23
@RequiredArgsConstructor
24
@Singleton
NEW
25
@Slf4j
×
26
public class ProductOfferingEventHandler implements EventHandler {
27

28

29
        private static final String CREATE_EVENT = "ProductOfferingCreateEvent";
30
        private static final String DELETE_EVENT = "ProductOfferingDeleteEvent";
31
        private static final String STATE_CHANGE_EVENT = "ProductOfferingStateChangeEvent";
32

NEW
33
        private static final List<String> SUPPORTED_EVENT_TYPES = List.of(CREATE_EVENT, DELETE_EVENT, STATE_CHANGE_EVENT);
×
34

35
        public static final String EMPTY_STRING_MARKER = "Empty";
36
        public static final String OWNER_ROLE = "Owner";
37
        public static final String ENDPOINT_URL_TYPE = "endpointUrl";
38
        public static final String ENDPOINT_DESCRIPTION_TYPE = "endpointDescription";
39

40
        private final CatalogApiClient rainbowCatalogApiClient;
41
        private final ProductSpecificationApiClient productSpecificationApiClient;
42
        private final org.fiware.iam.tmforum.productcatalog.api.CatalogApiClient catalogApiClient;
43
        private final ObjectMapper objectMapper;
44

45
        @Override
46
        public boolean isEventTypeSupported(String eventType) {
NEW
47
                return SUPPORTED_EVENT_TYPES.contains(eventType);
×
48
        }
49

50
        @Override
51
        public Mono<HttpResponse<?>> handleEvent(String eventType, Map<String, Object> event) {
NEW
52
                return switch (eventType) {
×
NEW
53
                        case CREATE_EVENT -> handleOfferingCreation(event);
×
NEW
54
                        case STATE_CHANGE_EVENT -> handleOfferingStateChange(event);
×
NEW
55
                        case DELETE_EVENT -> handleOfferingDeletion(event);
×
NEW
56
                        default -> throw new IllegalArgumentException("Even type %s is not supported.".formatted(eventType));
×
57
                };
58
        }
59

60
        private Mono<HttpResponse<?>> handleOfferingCreation(Map<String, Object> event) {
NEW
61
                ProductOfferingCreateEventVO productOfferingCreateEventVO = objectMapper.convertValue(event, ProductOfferingCreateEventVO.class);
×
NEW
62
                ProductOfferingVO productOfferingVO = Optional.ofNullable(productOfferingCreateEventVO.getEvent())
×
NEW
63
                                .map(ProductOfferingCreateEventPayloadVO::getProductOffering)
×
NEW
64
                                .orElseThrow(() -> new IllegalArgumentException("The event does not contain a product offering."));
×
65

NEW
66
                if (productOfferingVO.getCategory() == null || productOfferingVO.getCategory().isEmpty()) {
×
NEW
67
                        throw new IllegalArgumentException("Product offering does not have a category.");
×
68
                }
69

NEW
70
                Mono<NewDataserviceVO> dataserviceVOMono = prepareNewDataservice(productOfferingVO);
×
NEW
71
                Mono<List<String>> catalogsMono = getCatalogsForProductOffering(productOfferingVO);
×
NEW
72
                return Mono.zip(dataserviceVOMono, catalogsMono, this::createDataservice).
×
NEW
73
                                flatMap(Function.identity());
×
74
        }
75

76
        private Mono<HttpResponse<?>> handleOfferingStateChange(Map<String, Object> event) {
NEW
77
                ProductOfferingStateChangeEventVO productOfferingStateChangeEventVO = objectMapper.convertValue(event, ProductOfferingStateChangeEventVO.class);
×
NEW
78
                ProductOfferingVO productOfferingVO = Optional.ofNullable(productOfferingStateChangeEventVO.getEvent())
×
NEW
79
                                .map(ProductOfferingStateChangeEventPayloadVO::getProductOffering)
×
NEW
80
                                .orElseThrow(() -> new IllegalArgumentException("The event does not contain a product offering."));
×
81

NEW
82
                if (productOfferingVO.getCategory() == null || productOfferingVO.getCategory().isEmpty()) {
×
83
                        // if no category is included, the offering is not part of a catalog anymore
NEW
84
                        return deleteOffering(productOfferingVO);
×
85
                }
86
                // find catalogs that the offering should be in
NEW
87
                Mono<List<String>> targetCatalogs = getCatalogsForProductOffering(productOfferingVO);
×
88

89
                // (rainbow) catalogs that the offering is currently included
NEW
90
                Mono<List<String>> currentCatalogs = rainbowCatalogApiClient.getCatalogs()
×
NEW
91
                                .map(HttpResponse::body)
×
NEW
92
                                .map(catalogVOS -> catalogVOS.stream().map(CatalogVO::getAtId).toList());
×
93

NEW
94
                return Mono.zipDelayError(targetCatalogs, currentCatalogs)
×
NEW
95
                                .flatMap(tuple -> handleCatalogEntries(tuple.getT1(), tuple.getT2(), productOfferingVO));
×
96
        }
97

98
        private Mono<HttpResponse<?>> handleCatalogEntries(List<String> targetCatalogs, List<String> currentCatalogs, ProductOfferingVO productOfferingVO) {
NEW
99
                List<String> newCatalogs = new ArrayList<>();
×
NEW
100
                List<String> updateCatalogs = new ArrayList<>();
×
NEW
101
                targetCatalogs.stream().forEach(targetCatalog -> {
×
NEW
102
                        if (currentCatalogs.contains(targetCatalog)) {
×
NEW
103
                                updateCatalogs.add(targetCatalog);
×
104
                        } else {
NEW
105
                                newCatalogs.add(targetCatalog);
×
106
                        }
NEW
107
                });
×
NEW
108
                List<String> deleteCatalogs = currentCatalogs.stream().filter(currentCatalog -> !targetCatalogs.contains(currentCatalog)).toList();
×
109

NEW
110
                List<Mono<HttpResponse<?>>> offeringMonos = new ArrayList<>();
×
NEW
111
                offeringMonos.add(
×
NEW
112
                                Mono.zipDelayError(deleteCatalogs.stream()
×
NEW
113
                                                .map(catalogId -> rainbowCatalogApiClient.deleteDataserviceInCatalog(catalogId, productOfferingVO.getId()))
×
NEW
114
                                                .toList(), r -> HttpResponse.accepted()));
×
115

NEW
116
                if (!newCatalogs.isEmpty() || !updateCatalogs.isEmpty()) {
×
NEW
117
                        Mono<NewDataserviceVO> newDataservice = prepareNewDataservice(productOfferingVO);
×
NEW
118
                        offeringMonos.add(newDataservice.flatMap(dataserviceVO -> {
×
NEW
119
                                List<Mono<HttpResponse<?>>> rainbowResponses = new ArrayList<>();
×
NEW
120
                                if (!newCatalogs.isEmpty()) {
×
NEW
121
                                        rainbowResponses.add(createDataservice(dataserviceVO, newCatalogs));
×
122
                                }
NEW
123
                                if (!updateCatalogs.isEmpty()) {
×
NEW
124
                                        rainbowResponses.add(updateDataservice(dataserviceVO, updateCatalogs));
×
125
                                }
NEW
126
                                return Mono.zipDelayError(rainbowResponses, r -> HttpResponse.accepted());
×
127
                        }));
128
                }
NEW
129
                return Mono.zipDelayError(offeringMonos, r -> HttpResponse.accepted());
×
130
        }
131

132
        private Mono<ProductSpecificationVO> getSpecForOffering(ProductOfferingVO productOfferingVO) {
NEW
133
                return productSpecificationApiClient.retrieveProductSpecification(productOfferingVO.getProductSpecification().getId(), null)
×
NEW
134
                                .map(HttpResponse::body);
×
135
        }
136

137
        private Mono<HttpResponse<?>> createDataservice(NewDataserviceVO dataserviceVO, List<String> catalogs) {
NEW
138
                return Mono.zip(catalogs.stream()
×
NEW
139
                                                .map(id -> rainbowCatalogApiClient
×
NEW
140
                                                                .createDataserviceInCatalog(id, dataserviceVO)
×
NEW
141
                                                                .onErrorMap(t ->
×
NEW
142
                                                                                new IllegalArgumentException("Was not able to create dataservice %s".formatted(dataserviceVO), t)))
×
NEW
143
                                                .toList(),
×
NEW
144
                                responses -> Arrays.stream(responses)
×
NEW
145
                                                .map(HttpResponse.class::cast)
×
NEW
146
                                                .filter(resp -> resp.getStatus() != HttpStatus.CREATED)
×
NEW
147
                                                .findAny()
×
NEW
148
                                                .map(r -> HttpResponse.status(HttpStatus.BAD_GATEWAY))
×
NEW
149
                                                .orElse(HttpResponse.ok()));
×
150

151
        }
152

153
        private Mono<HttpResponse<?>> updateDataservice(NewDataserviceVO dataserviceVO, List<String> catalogs) {
NEW
154
                return Mono.zip(catalogs.stream()
×
NEW
155
                                                .map(id -> rainbowCatalogApiClient
×
NEW
156
                                                                .updateDataserviceInCatalog(id, dataserviceVO.getAtId(), dataserviceVO)
×
NEW
157
                                                                .onErrorMap(t ->
×
NEW
158
                                                                                new IllegalArgumentException("Was not able to update dataservice %s in %s".formatted(dataserviceVO, id), t)))
×
NEW
159
                                                .toList(),
×
NEW
160
                                responses -> Arrays.stream(responses)
×
NEW
161
                                                .map(HttpResponse.class::cast)
×
NEW
162
                                                .filter(resp -> resp.getStatus() != HttpStatus.ACCEPTED)
×
NEW
163
                                                .findAny()
×
NEW
164
                                                .map(r -> HttpResponse.status(HttpStatus.BAD_GATEWAY))
×
NEW
165
                                                .orElse(HttpResponse.ok()));
×
166

167
        }
168

169
        private Mono<List<String>> getCatalogsForProductOffering(ProductOfferingVO productOfferingVO) {
NEW
170
                List<String> categoryIds = productOfferingVO.getCategory().stream().map(CategoryRefVO::getId).toList();
×
171

NEW
172
                return rainbowCatalogApiClient.getCatalogs()
×
NEW
173
                                .map(HttpResponse::body)
×
NEW
174
                                .flatMap(catalogList ->
×
NEW
175
                                                Mono.zip(catalogList.stream()
×
NEW
176
                                                                                .map(CatalogVO::getAtId)
×
NEW
177
                                                                                .map(id -> catalogApiClient.retrieveCatalog(id, null)
×
NEW
178
                                                                                                .map(HttpResponse::body)
×
NEW
179
                                                                                                .filter(cvo -> cvo.getCategory().stream()
×
NEW
180
                                                                                                                .map(CategoryRefVO::getId)
×
NEW
181
                                                                                                                .anyMatch(categoryIds::contains)
×
182
                                                                                                )
NEW
183
                                                                                                .map(org.fiware.iam.tmforum.productcatalog.model.CatalogVO::getId)
×
184
                                                                                                // prevent empty monos, because they will terminate the zip
NEW
185
                                                                                                .defaultIfEmpty(EMPTY_STRING_MARKER)
×
186
                                                                                )
NEW
187
                                                                                .toList(),
×
NEW
188
                                                                ids -> Arrays.stream(ids)
×
NEW
189
                                                                                .filter(String.class::isInstance)
×
NEW
190
                                                                                .map(String.class::cast)
×
NEW
191
                                                                                .filter(id -> !id.equals(EMPTY_STRING_MARKER))
×
NEW
192
                                                                                .toList())
×
193
                                );
194

195
        }
196

197
        private static void setEndpoint(ProductSpecificationVO spec, NewDataserviceVO newDataserviceVO) {
NEW
198
                if (spec.getProductSpecCharacteristic() != null && !spec.getProductSpecCharacteristic().isEmpty()) {
×
NEW
199
                        spec.getProductSpecCharacteristic().forEach(psc -> {
×
NEW
200
                                if (psc.getValueType().equals(ENDPOINT_URL_TYPE)) {
×
NEW
201
                                        getCharValue(psc.getProductSpecCharacteristicValue())
×
NEW
202
                                                        .ifPresent(newDataserviceVO::dcatColonEndpointURL);
×
203

NEW
204
                                } else if (psc.getValueType().equals(ENDPOINT_DESCRIPTION_TYPE)) {
×
NEW
205
                                        getCharValue(psc.getProductSpecCharacteristicValue())
×
NEW
206
                                                        .ifPresent(newDataserviceVO::dcatColonEndpointDescription);
×
207
                                }
NEW
208
                        });
×
209
                }
NEW
210
        }
×
211

212
        private static Optional<String> getCharValue(List<CharacteristicValueSpecificationVO> specs) {
NEW
213
                if (specs == null || specs.isEmpty()) {
×
NEW
214
                        return Optional.empty();
×
215
                }
NEW
216
                if (specs.size() == 1 && specs.get(0).getValue() instanceof String stringValue) {
×
NEW
217
                        return Optional.of(stringValue);
×
218
                }
NEW
219
                return specs.stream()
×
NEW
220
                                .filter(cvs -> Optional.ofNullable(cvs.getIsDefault()).orElse(false))
×
NEW
221
                                .findFirst()
×
NEW
222
                                .map(CharacteristicValueSpecificationVO::getValue)
×
NEW
223
                                .filter(String.class::isInstance)
×
NEW
224
                                .map(String.class::cast);
×
225
        }
226

227
        private static void setRelatedParty(ProductSpecificationVO spec, NewDataserviceVO newDataserviceVO) {
NEW
228
                if (spec.getRelatedParty() != null && !spec.getRelatedParty().isEmpty()) {
×
NEW
229
                        spec.getRelatedParty().stream()
×
NEW
230
                                        .filter(rp -> rp.getRole().equals(OWNER_ROLE))
×
NEW
231
                                        .map(RelatedPartyVO::getId)
×
NEW
232
                                        .findFirst()
×
NEW
233
                                        .ifPresent(newDataserviceVO::dctColonCreator);
×
234
                }
NEW
235
        }
×
236

237

238
        private Mono<HttpResponse<?>> handleOfferingDeletion(Map<String, Object> event) {
239

NEW
240
                ProductOfferingDeleteEventVO productOfferingDeleteEventVO = objectMapper.convertValue(event, ProductOfferingDeleteEventVO.class);
×
NEW
241
                ProductOfferingVO productOfferingVO = Optional.ofNullable(productOfferingDeleteEventVO.getEvent())
×
NEW
242
                                .map(ProductOfferingDeleteEventPayloadVO::getProductOffering)
×
NEW
243
                                .orElseThrow(() -> new IllegalArgumentException("The event does not contain a product offering."));
×
244

NEW
245
                return deleteOffering(productOfferingVO);
×
246

247
        }
248

249
        private Mono<HttpResponse<?>> deleteOffering(ProductOfferingVO productOfferingVO) {
NEW
250
                return rainbowCatalogApiClient.getCatalogs()
×
NEW
251
                                .map(HttpResponse::body)
×
NEW
252
                                .flatMap(catalogVOS ->
×
NEW
253
                                                Mono.zipDelayError(
×
NEW
254
                                                                catalogVOS.stream()
×
NEW
255
                                                                                .filter(catalogVO ->
×
NEW
256
                                                                                                catalogVO.getDcatColonService()
×
NEW
257
                                                                                                                .stream()
×
NEW
258
                                                                                                                .map(DataServiceVO::getAtId)
×
NEW
259
                                                                                                                .anyMatch(id -> id.equals(productOfferingVO.getId()))
×
260
                                                                                )
NEW
261
                                                                                .map(catalogVO -> rainbowCatalogApiClient
×
NEW
262
                                                                                                .deleteDataserviceInCatalog(catalogVO.getAtId(), productOfferingVO.getId())
×
263

NEW
264
                                                                                ).toList(),
×
NEW
265
                                                                responses -> HttpResponse.noContent())
×
266
                                );
267
        }
268

269
        private Mono<NewDataserviceVO> prepareNewDataservice(ProductOfferingVO productOfferingVO) {
NEW
270
                NewDataserviceVO newDataserviceVO = new NewDataserviceVO().atId(productOfferingVO.getId());
×
NEW
271
                return getSpecForOffering(productOfferingVO)
×
NEW
272
                                .map(spec -> {
×
NEW
273
                                        newDataserviceVO.dctColonTitle(spec.getName());
×
NEW
274
                                        setRelatedParty(spec, newDataserviceVO);
×
NEW
275
                                        setEndpoint(spec, newDataserviceVO);
×
NEW
276
                                        return newDataserviceVO;
×
277
                                });
278
        }
279

280
}
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

© 2026 Coveralls, Inc