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

FIWARE / contract-management / #68

29 Oct 2025 11:26AM UTC coverage: 1.686% (+0.04%) from 1.651%
#68

Pull #12

wistefan
fixes from pr review
Pull Request #12: Support for central marketplace and policy creation

117 of 1238 new or added lines in 31 files covered. (9.45%)

5 existing lines in 2 files now uncovered.

587 of 34807 relevant lines covered (1.69%)

0.02 hits per line

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

0.0
/src/main/java/org/fiware/iam/dsp/RainbowProductOfferingHandler.java
1
package org.fiware.iam.dsp;
2

3
import io.micronaut.context.annotation.Requires;
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.configuration.GeneralProperties;
10
import org.fiware.iam.handlers.ProductOfferingHandler;
11
import org.fiware.iam.tmforum.ProductOfferingConstants;
12
import org.fiware.iam.tmforum.productcatalog.api.ProductSpecificationApiClient;
13
import org.fiware.iam.tmforum.productcatalog.model.*;
14
import org.fiware.rainbow.api.CatalogApiClient;
15
import org.fiware.rainbow.model.CatalogVO;
16
import org.fiware.rainbow.model.DataServiceVO;
17
import org.fiware.rainbow.model.NewDataserviceVO;
18
import reactor.core.publisher.Mono;
19

20
import java.util.ArrayList;
21
import java.util.Arrays;
22
import java.util.List;
23
import java.util.Optional;
24
import java.util.function.Function;
25

26

27
/**
28
 * Handler to manage product offerings and exchange them with Rainbow.
29
 */
30
@Requires(condition = GeneralProperties.RainbowCondition.class)
31
@RequiredArgsConstructor
32
@Singleton
NEW
33
@Slf4j
×
34
public class RainbowProductOfferingHandler implements ProductOfferingHandler {
35

36
    private static final String EMPTY_STRING_MARKER = "Empty";
37
    private static final String OWNER_ROLE = "Owner";
38

39
    private final CatalogApiClient rainbowCatalogApiClient;
40
    private final org.fiware.iam.tmforum.productcatalog.api.CatalogApiClient catalogApiClient;
41
    private final ProductSpecificationApiClient productSpecificationApiClient;
42

43
    @Override
44
    public Mono<HttpResponse<?>> handleOfferingCreation(ProductOfferingVO productOfferingVO) {
45

NEW
46
        Mono<NewDataserviceVO> dataserviceVOMono = prepareNewDataservice(productOfferingVO);
×
NEW
47
        Mono<List<String>> catalogsMono = getCatalogsForProductOffering(productOfferingVO);
×
NEW
48
        return Mono.zip(dataserviceVOMono, catalogsMono, this::createDataservice)
×
NEW
49
                        .flatMap(Function.identity());
×
50
    }
51

52
    @Override
53
    public Mono<HttpResponse<?>> handleOfferingStateChange(ProductOfferingVO productOfferingVO) {
54
        // find catalogs that the offering should be in
NEW
55
        Mono<List<String>> targetCatalogs = getCatalogsForProductOffering(productOfferingVO);
×
56

57
        // (rainbow) catalogs that the offering is currently included
NEW
58
        Mono<List<String>> currentCatalogs = rainbowCatalogApiClient.getCatalogs()
×
NEW
59
                .map(HttpResponse::body)
×
NEW
60
                .map(catalogVOS -> catalogVOS.stream().map(CatalogVO::getAtId).toList());
×
61

NEW
62
        return Mono.zipDelayError(targetCatalogs, currentCatalogs)
×
NEW
63
                .flatMap(tuple -> handleCatalogEntries(tuple.getT1(), tuple.getT2(), productOfferingVO));
×
64
    }
65

66
    @Override
67
    public Mono<HttpResponse<?>> handleOfferingDeletion(ProductOfferingVO productOfferingVO) {
NEW
68
        return rainbowCatalogApiClient.getCatalogs()
×
NEW
69
                .map(HttpResponse::body)
×
NEW
70
                .flatMap(catalogVOS ->
×
NEW
71
                        Mono.zipDelayError(
×
NEW
72
                                catalogVOS.stream()
×
NEW
73
                                        .filter(catalogVO ->
×
NEW
74
                                                catalogVO.getDcatColonService()
×
NEW
75
                                                        .stream()
×
NEW
76
                                                        .map(DataServiceVO::getAtId)
×
NEW
77
                                                        .anyMatch(id -> id.equals(productOfferingVO.getId()))
×
78
                                        )
NEW
79
                                        .map(catalogVO -> rainbowCatalogApiClient
×
NEW
80
                                                .deleteDataserviceInCatalog(catalogVO.getAtId(), productOfferingVO.getId())
×
81

NEW
82
                                        ).toList(),
×
NEW
83
                                responses -> HttpResponse.noContent())
×
84
                );
85
    }
86

87
    private Optional<String> getCharValue(List<CharacteristicValueSpecificationVO> specs) {
NEW
88
        if (specs == null || specs.isEmpty()) {
×
NEW
89
            return Optional.empty();
×
90
        }
NEW
91
        if (specs.size() == 1 && specs.get(0).getValue() instanceof String stringValue) {
×
NEW
92
            return Optional.of(stringValue);
×
93
        }
NEW
94
        return specs.stream()
×
NEW
95
                .filter(cvs -> Optional.ofNullable(cvs.getIsDefault()).orElse(false))
×
NEW
96
                .findFirst()
×
NEW
97
                .map(CharacteristicValueSpecificationVO::getValue)
×
NEW
98
                .filter(String.class::isInstance)
×
NEW
99
                .map(String.class::cast);
×
100
    }
101

102
    private void setEndpoint(ProductSpecificationVO spec, NewDataserviceVO newDataserviceVO) {
NEW
103
        if (spec.getProductSpecCharacteristic() != null && !spec.getProductSpecCharacteristic().isEmpty()) {
×
NEW
104
            spec.getProductSpecCharacteristic().forEach(psc -> {
×
NEW
105
                if (psc.getValueType().equals(ProductOfferingConstants.ENDPOINT_URL_TYPE)) {
×
NEW
106
                    getCharValue(psc.getProductSpecCharacteristicValue())
×
NEW
107
                            .ifPresent(newDataserviceVO::dcatColonEndpointURL);
×
108

NEW
109
                } else if (psc.getValueType().equals(ProductOfferingConstants.ENDPOINT_DESCRIPTION_TYPE)) {
×
NEW
110
                    getCharValue(psc.getProductSpecCharacteristicValue())
×
NEW
111
                            .ifPresent(newDataserviceVO::dcatColonEndpointDescription);
×
112
                }
NEW
113
            });
×
114
        }
NEW
115
    }
×
116

117
    private void setRelatedParty(ProductSpecificationVO spec, NewDataserviceVO newDataserviceVO) {
NEW
118
        if (spec.getRelatedParty() != null && !spec.getRelatedParty().isEmpty()) {
×
NEW
119
            spec.getRelatedParty().stream()
×
NEW
120
                    .filter(rp -> rp.getRole().equals(OWNER_ROLE))
×
NEW
121
                    .map(RelatedPartyVO::getId)
×
NEW
122
                    .findFirst()
×
NEW
123
                    .ifPresent(newDataserviceVO::dctColonCreator);
×
124
        }
NEW
125
    }
×
126

127
    private Mono<HttpResponse<?>> handleCatalogEntries(List<String> targetCatalogs, List<String> currentCatalogs, ProductOfferingVO productOfferingVO) {
NEW
128
        List<String> newCatalogs = new ArrayList<>();
×
NEW
129
        List<String> updateCatalogs = new ArrayList<>();
×
NEW
130
        targetCatalogs.stream().forEach(targetCatalog -> {
×
NEW
131
            if (currentCatalogs.contains(targetCatalog)) {
×
NEW
132
                updateCatalogs.add(targetCatalog);
×
133
            } else {
NEW
134
                newCatalogs.add(targetCatalog);
×
135
            }
NEW
136
        });
×
NEW
137
        List<String> deleteCatalogs = currentCatalogs.stream().filter(currentCatalog -> !targetCatalogs.contains(currentCatalog)).toList();
×
138

NEW
139
        List<Mono<HttpResponse<?>>> offeringMonos = new ArrayList<>();
×
NEW
140
        offeringMonos.add(
×
NEW
141
                Mono.zipDelayError(deleteCatalogs.stream()
×
NEW
142
                        .map(catalogId -> rainbowCatalogApiClient.deleteDataserviceInCatalog(catalogId, productOfferingVO.getId()))
×
NEW
143
                        .toList(), r -> HttpResponse.accepted()));
×
144

NEW
145
        if (!newCatalogs.isEmpty() || !updateCatalogs.isEmpty()) {
×
NEW
146
            Mono<NewDataserviceVO> newDataservice = prepareNewDataservice(productOfferingVO);
×
NEW
147
            offeringMonos.add(newDataservice.flatMap(dataserviceVO -> {
×
NEW
148
                List<Mono<HttpResponse<?>>> rainbowResponses = new ArrayList<>();
×
NEW
149
                if (!newCatalogs.isEmpty()) {
×
NEW
150
                    rainbowResponses.add(createDataservice(dataserviceVO, newCatalogs));
×
151
                }
NEW
152
                if (!updateCatalogs.isEmpty()) {
×
NEW
153
                    rainbowResponses.add(updateDataservice(dataserviceVO, updateCatalogs));
×
154
                }
NEW
155
                return Mono.zipDelayError(rainbowResponses, r -> HttpResponse.accepted());
×
156
            }));
157
        }
NEW
158
        return Mono.zipDelayError(offeringMonos, r -> HttpResponse.accepted());
×
159
    }
160

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

175
    }
176

177
    private Mono<NewDataserviceVO> prepareNewDataservice(ProductOfferingVO productOfferingVO) {
NEW
178
        NewDataserviceVO newDataserviceVO = new NewDataserviceVO().atId(productOfferingVO.getId());
×
NEW
179
        return getSpecForOffering(productOfferingVO)
×
NEW
180
                .map(spec -> {
×
NEW
181
                    newDataserviceVO.dctColonTitle(spec.getName());
×
NEW
182
                    setRelatedParty(spec, newDataserviceVO);
×
NEW
183
                    setEndpoint(spec, newDataserviceVO);
×
NEW
184
                    return newDataserviceVO;
×
185
                });
186
    }
187

188
    private Mono<ProductSpecificationVO> getSpecForOffering(ProductOfferingVO productOfferingVO) {
NEW
189
        return productSpecificationApiClient.retrieveProductSpecification(productOfferingVO.getProductSpecification().getId(), null)
×
NEW
190
                .map(HttpResponse::body);
×
191
    }
192

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

NEW
196
        return rainbowCatalogApiClient.getCatalogs()
×
NEW
197
                .map(HttpResponse::body)
×
NEW
198
                .flatMap(catalogList ->
×
NEW
199
                        Mono.zip(catalogList.stream()
×
NEW
200
                                        .map(CatalogVO::getAtId)
×
NEW
201
                                        .map(id -> catalogApiClient.retrieveCatalog(id, null)
×
NEW
202
                                                .map(HttpResponse::body)
×
NEW
203
                                                .filter(cvo -> cvo.getCategory().stream()
×
NEW
204
                                                        .map(CategoryRefVO::getId)
×
NEW
205
                                                        .anyMatch(categoryIds::contains)
×
206
                                                )
NEW
207
                                                .map(org.fiware.iam.tmforum.productcatalog.model.CatalogVO::getId)
×
208
                                                // prevent empty monos, because they will terminate the zip
NEW
209
                                                .defaultIfEmpty(EMPTY_STRING_MARKER)
×
210
                                        )
NEW
211
                                        .toList(),
×
NEW
212
                                ids -> Arrays.stream(ids)
×
NEW
213
                                        .filter(String.class::isInstance)
×
NEW
214
                                        .map(String.class::cast)
×
NEW
215
                                        .filter(id -> !id.equals(EMPTY_STRING_MARKER))
×
NEW
216
                                        .toList())
×
217
                );
218
    }
219

220

221
    private Mono<HttpResponse<?>> createDataservice(NewDataserviceVO dataserviceVO, List<String> catalogs) {
NEW
222
        return Mono.zip(catalogs.stream()
×
NEW
223
                        .map(id -> rainbowCatalogApiClient
×
NEW
224
                                .createDataserviceInCatalog(id, dataserviceVO)
×
NEW
225
                                .onErrorMap(t ->
×
NEW
226
                                        new IllegalArgumentException("Was not able to create dataservice %s".formatted(dataserviceVO), t)))
×
NEW
227
                        .toList(),
×
NEW
228
                responses -> Arrays.stream(responses)
×
NEW
229
                        .map(HttpResponse.class::cast)
×
NEW
230
                        .filter(resp -> resp.getStatus() != HttpStatus.CREATED)
×
NEW
231
                        .findAny()
×
NEW
232
                        .map(r -> HttpResponse.status(HttpStatus.BAD_GATEWAY))
×
NEW
233
                        .orElse(HttpResponse.ok()));
×
234

235
    }
236
}
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