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

FIWARE / contract-management / #29

02 Apr 2025 12:35PM UTC coverage: 1.242% (+0.3%) from 0.982%
#29

Pull #5

wistefan
fix testing
Pull Request #5: integrate contract negotiation

3 of 127 new or added lines in 5 files covered. (2.36%)

1 existing line in 1 file now uncovered.

417 of 33568 relevant lines covered (1.24%)

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/ProductOrderEventHandler.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.HttpResponseFactory;
6
import io.micronaut.http.HttpStatus;
7
import jakarta.inject.Singleton;
8
import lombok.RequiredArgsConstructor;
9
import lombok.extern.slf4j.Slf4j;
10
import org.fiware.iam.TMFMapper;
11
import org.fiware.iam.dsp.RainbowAdapter;
12
import org.fiware.iam.exception.RainbowException;
13
import org.fiware.iam.exception.TMForumException;
14
import org.fiware.iam.til.TrustedIssuersListAdapter;
15
import org.fiware.iam.tmforum.OrganizationResolver;
16
import org.fiware.iam.tmforum.TMForumAdapter;
17
import org.fiware.iam.tmforum.agreement.model.RelatedPartyTmfVO;
18
import org.fiware.iam.tmforum.productorder.model.*;
19
import org.fiware.iam.tmforum.quote.model.QuoteItemVO;
20
import org.fiware.iam.tmforum.quote.model.QuoteStateTypeVO;
21
import org.fiware.rainbow.model.AgreementVO;
22
import org.graalvm.compiler.nodes.calc.IntegerDivRemNode;
23
import reactor.core.publisher.Mono;
24

25
import java.util.*;
26
import java.util.function.Function;
27
import java.util.stream.Stream;
28

29

30
/**
31
 * Handle all incoming events in connection to ProductOrder
32
 */
33
@RequiredArgsConstructor
34
@Singleton
35
@Slf4j
×
36
public class ProductOrderEventHandler implements EventHandler {
37

38
        private static final String CREATE_EVENT = "ProductOrderCreateEvent";
39
        private static final String DELETE_EVENT = "ProductOrderDeleteEvent";
40
        private static final String STATE_CHANGE_EVENT = "ProductOrderStateChangeEvent";
UNCOV
41
        private static final List<String> SUPPORTED_EVENT_TYPES = List.of(CREATE_EVENT, DELETE_EVENT, STATE_CHANGE_EVENT);
×
42

43
        private static final String STATE_VERIFIED = "dspace:VERIFIED";
44
        private static final String STATE_FINALIZED = "dspace:FINALIZED";
45

46
        private final ObjectMapper objectMapper;
47
        private final OrganizationResolver organizationResolver;
48
        private final TrustedIssuersListAdapter trustedIssuersListAdapter;
49
        private final RainbowAdapter rainbowAdapter;
50
        private final TMForumAdapter tmForumAdapter;
51

52
        private final TMFMapper tmfMapper;
53

54
        @Override
55
        public boolean isEventTypeSupported(String eventType) {
56
                return SUPPORTED_EVENT_TYPES.contains(eventType);
×
57
        }
58

59
        @Override
60
        public Mono<HttpResponse<?>> handleEvent(String eventType, Map<String, Object> event) {
61

62
                String orgId = Stream
×
63
                                .ofNullable(event)
×
64
                                .map(rawEvent -> objectMapper.convertValue(rawEvent, ProductOrderCreateEventVO.class))
×
65
                                .map(ProductOrderCreateEventVO::getEvent)
×
66
                                .map(ProductOrderCreateEventPayloadVO::getProductOrder)
×
67
                                .map(ProductOrderVO::getRelatedParty)
×
68
                                .filter(Objects::nonNull)
×
69
                                .map(rpl -> {
×
70
                                        if (rpl.size() != 1) {
×
71
                                                throw new IllegalArgumentException("Expected exactly one ordering organization.");
×
72
                                        }
73
                                        return rpl.get(0);
×
74
                                })
75
                                .map(RelatedPartyVO::getId)
×
76
                                .findAny()
×
77
                                .orElseThrow(() -> new IllegalArgumentException("The ProductOrder-Event does not include a valid organization id."));
×
78

79
                return switch (eventType) {
×
80
                        case CREATE_EVENT -> handelCreateEvent(orgId, event);
×
81
                        case STATE_CHANGE_EVENT -> handelStateChangeEvent(orgId, event);
×
82
                        case DELETE_EVENT -> handelDeleteEvent(orgId, event);
×
83
                        default -> throw new IllegalArgumentException("Invalid event type received.");
×
84
                };
85

86
        }
87

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

91
                ProductOrderVO productOrderVO = Optional.ofNullable(productOrderCreateEventVO.getEvent())
×
92
                                .map(ProductOrderCreateEventPayloadVO::getProductOrder)
×
93
                                .orElseThrow(() -> new IllegalArgumentException("The event does not contain a product order."));
×
94

NEW
95
                if (isNotRejected(productOrderVO) && containsQuote(productOrderVO)) {
×
NEW
96
                        return updateNegotiation(productOrderVO);
×
97
                }
98

99
                boolean isCompleted = isCompleted(productOrderVO);
×
100
                if (!isCompleted) {
×
101
                        log.debug("The received event is not in state completed.");
×
102
                        return Mono.just(HttpResponse.noContent());
×
103
                }
104

NEW
105
                return Mono.zipDelayError(handleComplete(productOrderVO, organizationId), allowIssuer(organizationId))
×
106
                                .map(tuple -> HttpResponse.noContent());
×
107
        }
108

109
        private Mono<HttpResponse<?>> updateNegotiation(ProductOrderVO productOrderVO) {
110

NEW
111
                return tmForumAdapter
×
NEW
112
                                .getQuoteById(getQuoteRef(productOrderVO).getId())
×
NEW
113
                                .flatMap(quoteVO -> {
×
NEW
114
                                        if (quoteVO.getState() != QuoteStateTypeVO.ACCEPTED) {
×
NEW
115
                                                throw new TMForumException(String.format("The quote is not in state accepted, cannot be used for product ordering. %s:%s.", quoteVO.getId(), quoteVO.getState()));
×
116
                                        }
NEW
117
                                        return rainbowAdapter.updateNegotiationProcessByProviderId(quoteVO.getExternalId(), STATE_VERIFIED);
×
118
                                })
NEW
119
                                .map(t -> HttpResponse.noContent());
×
120

121
        }
122

123
        private static QuoteRefVO getQuoteRef(ProductOrderVO productOrderVO) {
124
                // integration with IDSA Contract Negotiation is only supported for productOrders with a single quote.
NEW
125
                if (productOrderVO.getQuote().size() != 1) {
×
NEW
126
                        throw new RainbowException("IDSA Contract Negotiation does not support the inclusion of multiple processes into one product.");
×
127
                }
NEW
128
                return productOrderVO.getQuote().get(0);
×
129
        }
130

131
        private static boolean containsQuote(ProductOrderVO productOrderVO) {
NEW
132
                return productOrderVO.getQuote() != null && !productOrderVO.getQuote().isEmpty();
×
133
        }
134

135
        private static boolean isCompleted(ProductOrderVO productOrderVO) {
136
                return Optional.ofNullable(productOrderVO.getState())
×
137
                                .filter(ProductOrderStateTypeVO.COMPLETED::equals)
×
138
                                .isPresent();
×
139
        }
140

141
        private static boolean isNotRejected(ProductOrderVO productOrderVO) {
NEW
142
                return Optional.ofNullable(productOrderVO.getState())
×
NEW
143
                                .filter(state -> state == ProductOrderStateTypeVO.REJECTED)
×
NEW
144
                                .isEmpty();
×
145
        }
146

147
        private Mono<HttpResponse<?>> handelStateChangeEvent(String organizationId, Map<String, Object> event) {
148
                ProductOrderStateChangeEventVO productOrderStateChangeEventVO = objectMapper.convertValue(event, ProductOrderStateChangeEventVO.class);
×
149
                ProductOrderVO productOrderVO = Optional.ofNullable(productOrderStateChangeEventVO.getEvent())
×
150
                                .map(ProductOrderStateChangeEventPayloadVO::getProductOrder)
×
151
                                .orElseThrow(() -> new IllegalArgumentException("The event does not contain a product order."));
×
152

153
                if (isCompleted(productOrderVO)) {
×
154
                        return Mono.zipDelayError(
×
NEW
155
                                                        handleComplete(productOrderVO, organizationId),
×
156
                                                        allowIssuer(organizationId))
×
157
                                        .map(tuple -> HttpResponse.noContent());
×
158
                } else {
159
                        return handleStopEvent(organizationId, event);
×
160
                }
161
        }
162

163
        private Mono<HttpResponse<?>> handleStopEvent(String organizationId, Map<String, Object> event) {
164
                ProductOrderStateChangeEventVO productOrderStateChangeEventVO = objectMapper.convertValue(event, ProductOrderStateChangeEventVO.class);
×
165
                ProductOrderVO productOrderVO = Optional.ofNullable(productOrderStateChangeEventVO.getEvent())
×
166
                                .map(ProductOrderStateChangeEventPayloadVO::getProductOrder)
×
167
                                .orElseThrow(() -> new IllegalArgumentException("The event does not contain a product order."));
×
168

169
                Mono<HttpResponse<?>> agreementsDeletion = deleteAgreement(productOrderVO);
×
170
                Mono<HttpResponse<?>> issuerDenial = denyIssuer(organizationId);
×
171

172
                return Mono.zipDelayError(List.of(agreementsDeletion, issuerDenial), responses -> Arrays.stream(responses)
×
173
                                .filter(HttpResponse.class::isInstance)
×
174
                                .map(HttpResponse.class::cast)
×
175
                                .filter(response -> response.status().getCode() > 299)
×
176
                                .findAny()
×
177
                                .orElse(HttpResponse.ok()));
×
178
        }
179

180
        private Mono<HttpResponse<?>> handelDeleteEvent(String organizationId, Map<String, Object> event) {
181
                ProductOrderDeleteEventVO productOrderDeleteEventVO = objectMapper.convertValue(event, ProductOrderDeleteEventVO.class);
×
182
                ProductOrderVO productOrderVO = Optional.ofNullable(productOrderDeleteEventVO.getEvent())
×
183
                                .map(ProductOrderDeleteEventPayloadVO::getProductOrder)
×
184
                                .orElseThrow(() -> new IllegalArgumentException("The event does not contain a product order."));
×
185

186
                Mono<HttpResponse<?>> agreementsDeletion = deleteAgreement(productOrderVO);
×
187
                Mono<HttpResponse<?>> issuerDenial = denyIssuer(organizationId);
×
188

189
                return Mono.zipDelayError(List.of(agreementsDeletion, issuerDenial), responses -> Arrays.stream(responses)
×
190
                                .filter(HttpResponse.class::isInstance)
×
191
                                .map(HttpResponse.class::cast)
×
192
                                .filter(response -> response.status().getCode() > 299)
×
193
                                .findAny()
×
194
                                .orElse(HttpResponse.ok()));
×
195
        }
196

197

198
        private Mono<?> handleComplete(ProductOrderVO productOrderVO, String organizationId) {
199

200
                List<RelatedPartyTmfVO> relatedPartyTmfVOS = productOrderVO
×
201
                                .getRelatedParty()
×
202
                                .stream()
×
203
                                .map(tmfMapper::map)
×
204
                                .toList();
×
205

NEW
206
                if (!containsQuote(productOrderVO)) {
×
NEW
207
                        return Mono.zipDelayError(
×
208
                                                        productOrderVO
NEW
209
                                                                        .getProductOrderItem()
×
NEW
210
                                                                        .stream()
×
NEW
211
                                                                        .map(ProductOrderItemVO::getProductOffering)
×
NEW
212
                                                                        .filter(Objects::nonNull)
×
NEW
213
                                                                        .map(offering -> rainbowAdapter.createAgreement(organizationId, offering.getId()))
×
NEW
214
                                                                        .toList(),
×
215
                                                        res -> {
NEW
216
                                                                List<AgreementVO> agreementVOS = Arrays.stream(res).filter(Objects::nonNull).filter(AgreementVO.class::isInstance).map(AgreementVO.class::cast).toList();
×
NEW
217
                                                                return updateProductOrder(productOrderVO, agreementVOS, relatedPartyTmfVOS);
×
218
                                                        })
NEW
219
                                        .flatMap(Function.identity());
×
220
                } else {
NEW
221
                        return tmForumAdapter.getQuoteById(getQuoteRef(productOrderVO).getId())
×
NEW
222
                                        .flatMap(quoteVO -> {
×
NEW
223
                                                Mono<?> agreementMono = Mono.zipDelayError(
×
NEW
224
                                                                                quoteVO.getQuoteItem()
×
NEW
225
                                                                                                .stream()
×
NEW
226
                                                                                                .filter(quoteItemVO -> !quoteItemVO.getState().equals("rejected"))
×
NEW
227
                                                                                                .map(QuoteItemVO::getProductOffering)
×
NEW
228
                                                                                                .filter(Objects::nonNull)
×
NEW
229
                                                                                                .map(offering -> rainbowAdapter.createAgreement(organizationId, offering.getId()))
×
NEW
230
                                                                                                .toList(), res -> {
×
NEW
231
                                                                                        List<AgreementVO> agreementVOS = Arrays.stream(res).filter(Objects::nonNull).filter(AgreementVO.class::isInstance).map(AgreementVO.class::cast).toList();
×
NEW
232
                                                                                        return updateProductOrder(productOrderVO, agreementVOS, relatedPartyTmfVOS);
×
233
                                                                                })
NEW
234
                                                                .flatMap(Function.identity());
×
235

NEW
236
                                                Mono<?> negotiationMono = rainbowAdapter.getNegotiationProcessState(quoteVO.getExternalId())
×
NEW
237
                                                                .flatMap(state -> {
×
NEW
238
                                                                        if (state.equals(STATE_FINALIZED)) {
×
239
                                                                                // nothing to do here, but we want the chain to continue
NEW
240
                                                                                return Mono.just(Optional.empty());
×
241
                                                                        }
NEW
242
                                                                        if (!state.equals(STATE_VERIFIED)) {
×
NEW
243
                                                                                throw new RainbowException(String.format("Negotiation process %s is in state %s. Not allowed for order completion.", quoteVO.getExternalId(), state));
×
244
                                                                        }
NEW
245
                                                                        return rainbowAdapter.updateNegotiationProcessByProviderId(quoteVO.getExternalId(), "dspace:FINALIZED");
×
246
                                                                });
NEW
247
                                                return Mono.zipDelayError(agreementMono, negotiationMono);
×
248
                                        })
NEW
249
                                        .onErrorMap(t -> {
×
NEW
250
                                                throw new RainbowException("Was not able to update the negotiation.");
×
251
                                        });
252
                }
253
        }
254

255
        private Mono<ProductOrderVO> updateProductOrder(ProductOrderVO productOrderVO, List<AgreementVO> agreementVOS, List<RelatedPartyTmfVO> relatedPartyTmfVOS) {
256
                return Mono.zipDelayError(
×
257
                                agreementVOS.stream()
×
258
                                                .map(agreementVO ->
×
259
                                                                tmForumAdapter.createAgreement(productOrderVO.getId(), agreementVO.getDataServiceId(), agreementVO.getAgreementId(), relatedPartyTmfVOS))
×
260
                                                .toList(),
×
261
                                agreements -> {
262
                                        List<String> agreementIds = Arrays.stream(agreements)
×
263
                                                        .filter(String.class::isInstance)
×
264
                                                        .map(String.class::cast)
×
265
                                                        .toList();
×
266
                                        return tmForumAdapter.addAgreementToOrder(productOrderVO.getId(), agreementIds);
×
267
                                }).flatMap(Function.identity());
×
268

269
        }
270

271
        private Mono<HttpResponse<?>> allowIssuer(String organizationId) {
272
                return organizationResolver.getDID(organizationId)
×
273
                                .flatMap(trustedIssuersListAdapter::allowIssuer)
×
274
                                .map(issuer -> HttpResponseFactory.INSTANCE.status(HttpStatus.CREATED));
×
275
        }
276

277
        private Mono<HttpResponse<?>> deleteAgreement(ProductOrderVO productOrderVO) {
278
                List<Mono<Boolean>> deletionMonos = productOrderVO.getAgreement()
×
279
                                .stream()
×
280
                                .map(AgreementRefVO::getId)
×
281
                                .map(rainbowAdapter::deleteAgreement)
×
282
                                .toList();
×
283
                return Mono.zipDelayError(deletionMonos, deletions -> {
×
284
                        if (Set.of(deletions).contains(false)) {
×
285
                                log.warn("Was not able to delete the agreement for order {}.", productOrderVO);
×
286
                                HttpResponse.status(HttpStatus.BAD_GATEWAY);
×
287
                        }
288
                        return HttpResponse.status(HttpStatus.ACCEPTED);
×
289
                });
290
        }
291

292
        private Mono<HttpResponse<?>> denyIssuer(String organizationId) {
293
                return organizationResolver.getDID(organizationId)
×
294
                                .flatMap(trustedIssuersListAdapter::denyIssuer);
×
295
        }
296
}
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