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

FIWARE / contract-management / #60

21 Aug 2025 01:23PM UTC coverage: 1.722% (+0.07%) from 1.652%
#60

push

wistefan
support deletion of policies

0 of 19 new or added lines in 2 files covered. (0.0%)

25 existing lines in 2 files now uncovered.

583 of 33861 relevant lines covered (1.72%)

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/tmforum/handlers/ProductOrderEventHandler.java
1
package org.fiware.iam.tmforum.handlers;
2

3
import com.fasterxml.jackson.core.JsonProcessingException;
4
import com.fasterxml.jackson.databind.ObjectMapper;
5
import io.micronaut.http.HttpResponse;
6
import io.micronaut.http.HttpResponseFactory;
7
import io.micronaut.http.HttpStatus;
8
import jakarta.inject.Singleton;
9
import lombok.RequiredArgsConstructor;
10
import lombok.extern.slf4j.Slf4j;
11
import org.fiware.iam.PAPAdapter;
12
import org.fiware.iam.TMFMapper;
13
import org.fiware.iam.configuration.GeneralProperties;
14
import org.fiware.iam.dsp.RainbowAdapter;
15
import org.fiware.iam.exception.RainbowException;
16
import org.fiware.iam.exception.TMForumException;
17
import org.fiware.iam.til.TrustedIssuersListAdapter;
18
import org.fiware.iam.tmforum.CredentialsConfigResolver;
19
import org.fiware.iam.tmforum.OrganizationResolver;
20
import org.fiware.iam.tmforum.PolicyResolver;
21
import org.fiware.iam.tmforum.TMForumAdapter;
22
import org.fiware.iam.tmforum.agreement.model.RelatedPartyTmfVO;
23
import org.fiware.iam.tmforum.productorder.model.*;
24
import org.fiware.iam.tmforum.quote.model.QuoteItemVO;
25
import org.fiware.iam.tmforum.quote.model.QuoteStateTypeVO;
26
import org.fiware.iam.tmforum.quote.model.QuoteVO;
27
import org.fiware.rainbow.model.AgreementVO;
28
import org.fiware.rainbow.model.ProviderNegotiationVO;
29
import org.graalvm.compiler.nodes.calc.IntegerDivRemNode;
30
import reactor.core.publisher.Mono;
31

32
import java.util.*;
33
import java.util.function.Function;
34
import java.util.stream.Stream;
35

36

37
/**
38
 * Handle all incoming events in connection to ProductOrder
39
 */
40
@RequiredArgsConstructor
41
@Singleton
42
@Slf4j
×
43
public class ProductOrderEventHandler implements EventHandler {
44

45
        private static final String CREATE_EVENT = "ProductOrderCreateEvent";
46
        private static final String DELETE_EVENT = "ProductOrderDeleteEvent";
47
        private static final String STATE_CHANGE_EVENT = "ProductOrderStateChangeEvent";
48
        private static final List<String> SUPPORTED_EVENT_TYPES = List.of(CREATE_EVENT, DELETE_EVENT, STATE_CHANGE_EVENT);
×
49

50
        private static final String STATE_VERIFIED = "dspace:VERIFIED";
51
        private static final String STATE_FINALIZED = "dspace:FINALIZED";
52

53
        private static final String CUSTOMER_ROLE = "Customer";
54

55
        private final ObjectMapper objectMapper;
56
        private final OrganizationResolver organizationResolver;
57
        private final CredentialsConfigResolver credentialsConfigResolver;
58
        private final PolicyResolver policyResolver;
59
        private final TrustedIssuersListAdapter trustedIssuersListAdapter;
60
        private final RainbowAdapter rainbowAdapter;
61
        private final PAPAdapter papAdapter;
62
        private final TMForumAdapter tmForumAdapter;
63

64
        private final TMFMapper tmfMapper;
65

66
        @Override
67
        public boolean isEventTypeSupported(String eventType) {
68
                return SUPPORTED_EVENT_TYPES.contains(eventType);
×
69
        }
70

71
        @Override
72
        public Mono<HttpResponse<?>> handleEvent(String eventType, Map<String, Object> event) {
73

74
                String orgId = Stream
×
75
                                .ofNullable(event)
×
76
                                .map(rawEvent -> objectMapper.convertValue(rawEvent, ProductOrderCreateEventVO.class))
×
77
                                .map(ProductOrderCreateEventVO::getEvent)
×
78
                                .map(ProductOrderCreateEventPayloadVO::getProductOrder)
×
79
                                .map(ProductOrderVO::getRelatedParty)
×
80
                                .filter(Objects::nonNull)
×
81
                                .map(rpl -> getCustomer(rpl).orElseThrow(() -> new IllegalArgumentException("Exactly one ordering related party is expected.")))
×
82
                                .map(RelatedPartyVO::getId)
×
83
                                .findAny()
×
84
                                .orElseThrow(() -> new IllegalArgumentException("The ProductOrder-Event does not include a valid organization id."));
×
85

86
                return switch (eventType) {
×
87
                        case CREATE_EVENT -> handelCreateEvent(orgId, event);
×
88
                        case STATE_CHANGE_EVENT -> handelStateChangeEvent(orgId, event);
×
89
                        case DELETE_EVENT -> handelDeleteEvent(orgId, event);
×
90
                        default -> throw new IllegalArgumentException("Invalid event type received.");
×
91
                };
92

93
        }
94

95
        private Optional<RelatedPartyVO> getCustomer(List<RelatedPartyVO> relatedPartyVOS) {
96
                if (relatedPartyVOS == null || relatedPartyVOS.isEmpty()) {
×
97
                        return Optional.empty();
×
98
                }
99
                if (relatedPartyVOS.size() == 1) {
×
100
                        String role = relatedPartyVOS.getFirst().getRole();
×
101
                        if (role == null || role.equals(CUSTOMER_ROLE)) {
×
102
                                return Optional.of(relatedPartyVOS.getFirst());
×
103
                        }
104
                }
105
                return relatedPartyVOS.stream()
×
106
                                .filter(relatedPartyVO -> relatedPartyVO.getRole() != null)
×
107
                                .filter(relatedPartyVO -> relatedPartyVO.getRole().equals(CUSTOMER_ROLE))
×
108
                                .findFirst();
×
109
        }
110

111
        private Mono<HttpResponse<?>> handelCreateEvent(String organizationId, Map<String, Object> event) {
112
                ProductOrderCreateEventVO productOrderCreateEventVO = objectMapper.convertValue(event, ProductOrderCreateEventVO.class);
×
113

114
                ProductOrderVO productOrderVO = Optional.ofNullable(productOrderCreateEventVO.getEvent())
×
115
                                .map(ProductOrderCreateEventPayloadVO::getProductOrder)
×
116
                                .orElseThrow(() -> new IllegalArgumentException("The event does not contain a product order."));
×
117

118
                if (isNotRejected(productOrderVO) && containsQuote(productOrderVO)) {
×
119
                        return updateNegotiation(productOrderVO);
×
120
                }
121

122
                boolean isCompleted = isCompleted(productOrderVO);
×
123
                if (!isCompleted) {
×
124
                        log.debug("The received event is not in state completed.");
×
125
                        return Mono.just(HttpResponse.noContent());
×
126
                }
127

128
                return Mono.zipDelayError(handleComplete(productOrderVO, organizationId), allowIssuer(organizationId, productOrderVO), createPolicy(organizationId, productOrderVO))
×
129
                                .map(tuple -> HttpResponse.noContent());
×
130
        }
131

132
        private Mono<HttpResponse<?>> updateNegotiation(ProductOrderVO productOrderVO) {
133

134
                return tmForumAdapter
×
135
                                .getQuoteById(getQuoteRef(productOrderVO).getId())
×
136
                                .flatMap(quoteVO -> {
×
137
                                        if (quoteVO.getState() != QuoteStateTypeVO.ACCEPTED) {
×
138
                                                throw new TMForumException(String.format("The quote is not in state accepted, cannot be used for product ordering. %s:%s.", quoteVO.getId(), quoteVO.getState()));
×
139
                                        }
140
                                        return rainbowAdapter.updateNegotiationProcessByProviderId(quoteVO.getExternalId(), STATE_VERIFIED);
×
141
                                })
142
                                .map(t -> HttpResponse.noContent());
×
143

144
        }
145

146
        private static QuoteRefVO getQuoteRef(ProductOrderVO productOrderVO) {
147
                // integration with IDSA Contract Negotiation is only supported for productOrders with a single quote.
148
                if (productOrderVO.getQuote().size() != 1) {
×
149
                        throw new RainbowException("IDSA Contract Negotiation does not support the inclusion of multiple processes into one product.");
×
150
                }
151
                return productOrderVO.getQuote().get(0);
×
152
        }
153

154
        private static boolean containsQuote(ProductOrderVO productOrderVO) {
155
                return productOrderVO.getQuote() != null && !productOrderVO.getQuote().isEmpty();
×
156
        }
157

158
        private static boolean isCompleted(ProductOrderVO productOrderVO) {
159
                return Optional.ofNullable(productOrderVO.getState())
×
160
                                .filter(ProductOrderStateTypeVO.COMPLETED::equals)
×
161
                                .isPresent();
×
162
        }
163

164
        private static boolean isNotRejected(ProductOrderVO productOrderVO) {
165
                return Optional.ofNullable(productOrderVO.getState())
×
166
                                .filter(state -> state == ProductOrderStateTypeVO.REJECTED)
×
167
                                .isEmpty();
×
168
        }
169

170
        private Mono<HttpResponse<?>> handelStateChangeEvent(String organizationId, Map<String, Object> event) {
171
                log.info("State change event.");
×
172
                ProductOrderStateChangeEventVO productOrderStateChangeEventVO = objectMapper.convertValue(event, ProductOrderStateChangeEventVO.class);
×
173
                ProductOrderVO productOrderVO = Optional.ofNullable(productOrderStateChangeEventVO.getEvent())
×
174
                                .map(ProductOrderStateChangeEventPayloadVO::getProductOrder)
×
175
                                .orElseThrow(() -> new IllegalArgumentException("The event does not contain a product order."));
×
176

177
                if (isCompleted(productOrderVO)) {
×
178
                        log.info("Product order is completed.");
×
179
                        return Mono.zipDelayError(
×
180
                                                        handleComplete(productOrderVO, organizationId),
×
181
                                                        allowIssuer(organizationId, productOrderVO),
×
182
                                                        createPolicy(organizationId, productOrderVO))
×
183
                                        .map(tuple -> HttpResponse.noContent());
×
184
                } else {
185
                        return handleStopEvent(organizationId, event);
×
186
                }
187
        }
188

189
        private Mono<HttpResponse<?>> handleStopEvent(String organizationId, Map<String, Object> event) {
190
                ProductOrderStateChangeEventVO productOrderStateChangeEventVO = objectMapper.convertValue(event, ProductOrderStateChangeEventVO.class);
×
191
                ProductOrderVO productOrderVO = Optional.ofNullable(productOrderStateChangeEventVO.getEvent())
×
192
                                .map(ProductOrderStateChangeEventPayloadVO::getProductOrder)
×
193
                                .orElseThrow(() -> new IllegalArgumentException("The event does not contain a product order."));
×
194

195
                Mono<HttpResponse<?>> agreementsDeletion = deleteAgreement(productOrderVO);
×
196
                Mono<HttpResponse<?>> issuerDenial = denyIssuer(organizationId, productOrderVO);
×
NEW
197
                Mono<HttpResponse<?>> policyDeletion = deletePolicy(productOrderVO);
×
198

NEW
199
                return Mono.zipDelayError(List.of(agreementsDeletion, issuerDenial, policyDeletion), responses -> Arrays.stream(responses)
×
200
                                .filter(HttpResponse.class::isInstance)
×
201
                                .map(HttpResponse.class::cast)
×
202
                                .filter(response -> response.status().getCode() > 299)
×
203
                                .findAny()
×
204
                                .orElse(HttpResponse.ok()));
×
205
        }
206

207
        private Mono<HttpResponse<?>> handelDeleteEvent(String organizationId, Map<String, Object> event) {
208
                ProductOrderDeleteEventVO productOrderDeleteEventVO = objectMapper.convertValue(event, ProductOrderDeleteEventVO.class);
×
209
                ProductOrderVO productOrderVO = Optional.ofNullable(productOrderDeleteEventVO.getEvent())
×
210
                                .map(ProductOrderDeleteEventPayloadVO::getProductOrder)
×
211
                                .orElseThrow(() -> new IllegalArgumentException("The event does not contain a product order."));
×
212

213
                Mono<HttpResponse<?>> agreementsDeletion = deleteAgreement(productOrderVO);
×
214
                Mono<HttpResponse<?>> issuerDenial = denyIssuer(organizationId, productOrderVO);
×
NEW
215
                Mono<HttpResponse<?>> policyDeletion = deletePolicy(productOrderVO);
×
216

NEW
217
                return Mono.zipDelayError(List.of(agreementsDeletion, issuerDenial, policyDeletion), responses -> Arrays.stream(responses)
×
218
                                .filter(HttpResponse.class::isInstance)
×
219
                                .map(HttpResponse.class::cast)
×
220
                                .filter(response -> response.status().getCode() > 299)
×
221
                                .findAny()
×
222
                                .orElse(HttpResponse.ok()));
×
223
        }
224

225

226
        private Mono<?> handleComplete(ProductOrderVO productOrderVO, String organizationId) {
227

228
                List<RelatedPartyTmfVO> relatedPartyTmfVOS = productOrderVO
×
229
                                .getRelatedParty()
×
230
                                .stream()
×
231
                                .map(tmfMapper::map)
×
232
                                .map(rr -> {
×
233
                                        rr.unknownProperties(null);
×
234
                                        return rr;
×
235
                                })
236
                                .toList();
×
237

238
                if (!containsQuote(productOrderVO)) {
×
239
                        return Mono.zipDelayError(
×
240
                                                        productOrderVO
241
                                                                        .getProductOrderItem()
×
242
                                                                        .stream()
×
243
                                                                        .map(ProductOrderItemVO::getProductOffering)
×
244
                                                                        .filter(Objects::nonNull)
×
245
                                                                        .map(offering -> rainbowAdapter.createAgreement(organizationId, offering.getId()))
×
246
                                                                        .toList(),
×
247
                                                        res -> {
248
                                                                List<AgreementVO> agreementVOS = Arrays.stream(res).filter(Objects::nonNull).filter(AgreementVO.class::isInstance).map(AgreementVO.class::cast).toList();
×
249
                                                                return updateProductOrder(productOrderVO, agreementVOS, relatedPartyTmfVOS);
×
250
                                                        })
251
                                        .flatMap(Function.identity());
×
252
                } else {
253
                        return tmForumAdapter.getQuoteById(getQuoteRef(productOrderVO).getId())
×
254
                                        .flatMap(quoteVO -> {
×
255
                                                String offerId = getOfferIdFromQuote(quoteVO);
×
256

257
                                                Mono<?> agreementMono = rainbowAdapter.getNegotiationProcess(quoteVO.getExternalId())
×
258
                                                                .map(ProviderNegotiationVO::getCnProcessId)
×
259
                                                                .flatMap(rainbowAdapter::getAgreement)
×
260
                                                                .map(avo -> avo.dataServiceId(offerId))
×
261
                                                                .flatMap(agreementVO -> updateProductOrder(productOrderVO, List.of(agreementVO), relatedPartyTmfVOS));
×
262

263
                                                Mono<?> negotiationMono = rainbowAdapter.getNegotiationProcessState(quoteVO.getExternalId())
×
264
                                                                .flatMap(state -> {
×
265
                                                                        if (state.equals(STATE_FINALIZED)) {
×
266
                                                                                // nothing to do here, but we want the chain to continue
267
                                                                                return Mono.just(Optional.empty());
×
268
                                                                        }
269
                                                                        if (!state.equals(STATE_VERIFIED)) {
×
270
                                                                                throw new RainbowException(String.format("Negotiation process %s is in state %s. Not allowed for order completion.", quoteVO.getExternalId(), state));
×
271
                                                                        }
272
                                                                        return rainbowAdapter.updateNegotiationProcessByProviderId(quoteVO.getExternalId(), "dspace:FINALIZED");
×
273
                                                                });
274
                                                return Mono.zipDelayError(agreementMono, negotiationMono);
×
275
                                        })
276
                                        .onErrorMap(t -> {
×
277
                                                throw new RainbowException("Was not able to update the negotiation.");
×
278
                                        });
279
                }
280
        }
281

282
        private Mono<ProductOrderVO> updateProductOrder(ProductOrderVO productOrderVO, List<AgreementVO> agreementVOS, List<RelatedPartyTmfVO> relatedPartyTmfVOS) {
283
                return Mono.zipDelayError(
×
284
                                agreementVOS.stream()
×
285
                                                .map(agreementVO ->
×
286
                                                                tmForumAdapter.createAgreement(productOrderVO.getId(), agreementVO.getDataServiceId(), agreementVO.getAgreementId(), relatedPartyTmfVOS))
×
287
                                                .toList(),
×
288
                                agreements -> {
289
                                        List<String> agreementIds = Arrays.stream(agreements)
×
290
                                                        .filter(String.class::isInstance)
×
291
                                                        .map(String.class::cast)
×
292
                                                        .toList();
×
293
                                        return tmForumAdapter.addAgreementToOrder(productOrderVO.getId(), agreementIds);
×
294
                                }).flatMap(Function.identity());
×
295

296
        }
297

298
        private Mono<HttpResponse<?>> createPolicy(String organizationId, ProductOrderVO productOrderVO) {
299
                return policyResolver
×
NEW
300
                                .getAuthorizationPolicy(productOrderVO).flatMap(policies -> Mono.zipDelayError(policies.stream().map(p -> papAdapter.createPolicy(organizationId, productOrderVO.getId(), p)).toList(),
×
301
                                                results -> {
NEW
302
                                                        if (Stream.of(results).map(r -> (Boolean) r).toList().contains(false)) {
×
NEW
303
                                                                return HttpResponse.status(HttpStatus.BAD_GATEWAY);
×
304
                                                        }
NEW
305
                                                        return HttpResponse.ok();
×
306
                                                }
307
                                ));
308
        }
309

310
        private Mono<HttpResponse<?>> deletePolicy(ProductOrderVO productOrderVO) {
NEW
311
                return policyResolver
×
NEW
312
                                .getAuthorizationPolicy(productOrderVO).flatMap(policies -> Mono.zipDelayError(policies.stream().map(p -> papAdapter.deletePolicy(productOrderVO.getId(), p)).toList(),
×
313
                                                results -> {
314
                                                        if (Stream.of(results).map(r -> (Boolean) r).toList().contains(false)) {
×
315
                                                                return HttpResponse.status(HttpStatus.BAD_GATEWAY);
×
316
                                                        }
317
                                                        return HttpResponse.ok();
×
318
                                                }
319
                                ));
320
        }
321

322
        private Mono<HttpResponse<?>> allowIssuer(String organizationId, ProductOrderVO productOrderVO) {
323
                return Mono.zip(organizationResolver.getDID(organizationId), credentialsConfigResolver.getCredentialsConfig(productOrderVO))
×
324
                                .flatMap(resultTuple -> trustedIssuersListAdapter.allowIssuer(resultTuple.getT1(), resultTuple.getT2()))
×
325
                                .map(issuer -> HttpResponseFactory.INSTANCE.status(HttpStatus.CREATED));
×
326
        }
327

328
        private Mono<HttpResponse<?>> deleteAgreement(ProductOrderVO productOrderVO) {
329
                List<Mono<Boolean>> deletionMonos = productOrderVO.getAgreement()
×
330
                                .stream()
×
331
                                .map(AgreementRefVO::getId)
×
332
                                .map(rainbowAdapter::deleteAgreement)
×
333
                                .toList();
×
334
                return Mono.zipDelayError(deletionMonos, deletions -> {
×
335
                        if (Set.of(deletions).contains(false)) {
×
336
                                log.warn("Was not able to delete the agreement for order {}.", productOrderVO);
×
337
                                HttpResponse.status(HttpStatus.BAD_GATEWAY);
×
338
                        }
339
                        return HttpResponse.status(HttpStatus.ACCEPTED);
×
340
                });
341
        }
342

343
        private Mono<HttpResponse<?>> denyIssuer(String organizationId, ProductOrderVO productOrderVO) {
344

345
                return Mono.zip(organizationResolver.getDID(organizationId), credentialsConfigResolver.getCredentialsConfig(productOrderVO))
×
346
                                .flatMap(resultTuple -> trustedIssuersListAdapter.denyIssuer(resultTuple.getT1(), resultTuple.getT2()));
×
347
        }
348

349
        private String getOfferIdFromQuote(QuoteVO quoteVO) {
350
                return quoteVO
×
351
                                .getQuoteItem()
×
352
                                .stream()
×
353
                                .filter(qi -> qi.getState().equals("accepted"))
×
354
                                .map(QuoteItemVO::getProductOffering)
×
355
                                .map(org.fiware.iam.tmforum.quote.model.ProductOfferingRefVO::getId)
×
356
                                .findFirst()
×
357
                                .orElseThrow(() -> new IllegalArgumentException("The event does not reference an offer."));
×
358
        }
359

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