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

FIWARE / contract-management / #102

30 Sep 2026 10:28AM UTC coverage: 4.485% (+0.7%) from 3.796%
#102

Pull #28

vramperez
Remove stack trace floods from connection errors

- Micronaut's DefaultHttpClient logged every failed connection at ERROR with a
  ~125 line stack trace, duplicating the one-line WARN of the failed call. It is
  off by default now (LOG_LEVEL_MICRONAUT_HTTP_CLIENT to bring it back)
- The subscription retry warning names the event and the reason instead of
  attaching the stack trace on every retry
- Suppress logback's status dump on startup

Without a reachable TM Forum API this reduces 25s of logs from ~1100 to ~60 lines.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pull Request #28: Add failure reasons, downstream error descriptions and configurable log output

229 of 453 new or added lines in 31 files covered. (50.55%)

11 existing lines in 7 files now uncovered.

1608 of 35855 relevant lines covered (4.48%)

0.04 hits per line

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

58.82
/src/main/java/org/fiware/iam/tmforum/OrganizationResolver.java
1
package org.fiware.iam.tmforum;
2

3
import com.fasterxml.jackson.databind.ObjectMapper;
4
import io.micronaut.context.annotation.Requires;
5
import io.micronaut.context.annotation.Value;
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.configuration.GeneralProperties;
11
import org.fiware.iam.domain.ContractManagement;
12
import io.micronaut.http.client.exceptions.HttpClientResponseException;
13
import org.fiware.iam.exception.FailureReason;
14
import org.fiware.iam.exception.TMForumException;
15
import org.fiware.iam.tmforum.party.api.OrganizationApiClient;
16
import org.fiware.iam.tmforum.party.model.CharacteristicVO;
17
import org.fiware.iam.tmforum.party.model.ExternalReferenceVO;
18
import org.fiware.iam.tmforum.party.model.OrganizationVO;
19
import reactor.core.publisher.Mono;
20

21
import java.util.List;
22
import java.util.Optional;
23

24
@Requires(condition = GeneralProperties.TmForumCondition.class)
25
@Singleton
26
@Slf4j
1 ✔
27
@RequiredArgsConstructor
28
public class OrganizationResolver {
29

30
    @Value("${general.organization.provider.role:provider}")
31
    private String PROVIDER_ROLE;
32

33
    private static final String PARTY_CHARACTERISTIC_DID = "did";
34
    private static final String FIELD_NAME_CONTRACT_MANAGEMENT = "contractManagement";
35
    private static final String EXTERNAL_REFERENCE_IDM_ID = "idm_id";
36
    private static final String DID = "did";
37

38
    private final GeneralProperties generalProperties;
39
    private final ObjectMapper objectMapper;
40
    private final OrganizationApiClient apiClient;
41

42
    //TODO Cache me if you can
43
    public Mono<String> getDID(String organizationId) {
44
        return retrieveOrganization(organizationId)
1 ✔
45
                .map(ovo -> getDid(organizationId, ovo));
1 ✔
46
    }
47

48
    public Mono<ContractManagement> getContractManagement(String organizationId) {
NEW
49
        return retrieveOrganization(organizationId)
×
NEW
50
                .map(ovo -> {
×
NEW
51
                    if (getDid(organizationId, ovo).equals(generalProperties.getDid())) {
×
NEW
52
                        return new ContractManagement(true);
×
53
                    }
NEW
54
                    return Optional.ofNullable(ovo.getPartyCharacteristic())
×
NEW
55
                            .orElse(List.of())
×
NEW
56
                            .stream()
×
NEW
57
                            .filter(pc -> FIELD_NAME_CONTRACT_MANAGEMENT.equals(pc.getName()))
×
NEW
58
                            .map(CharacteristicVO::getValue)
×
NEW
59
                            .map(pcv -> objectMapper.convertValue(pcv, ContractManagement.class))
×
NEW
60
                            .findAny()
×
NEW
61
                            .orElse(new ContractManagement(true));
×
62
                });
63
    }
64

65
    private Mono<OrganizationVO> retrieveOrganization(String organizationId) {
66
        return apiClient.retrieveOrganization(organizationId, null)
1 ✔
67
                .onErrorMap(HttpClientResponseException.class, e -> new TMForumException(FailureReason.ORGANIZATION_NOT_FOUND,
1 ✔
68
                        "Organization %s could not be retrieved from the TM Forum party API.".formatted(organizationId), e))
1 ✔
69
                .map(response -> {
1 ✔
70
                    if (!response.getStatus().equals(HttpStatus.OK) || response.body() == null) {
1 ✔
71
                        throw new TMForumException(FailureReason.ORGANIZATION_NOT_FOUND,
1 ✔
72
                                "Organization %s could not be retrieved from the TM Forum party API, it answered with status %s and %s.".formatted(
1 ✔
73
                                        organizationId, response.getStatus().getCode(), response.body() == null ? "no body" : "a body"));
1 ✔
74
                    }
75
                    return response.body();
1 ✔
76
                })
77
                .switchIfEmpty(Mono.error(() -> new TMForumException(FailureReason.ORGANIZATION_NOT_FOUND,
1 ✔
NEW
78
                        "Organization %s could not be retrieved from the TM Forum party API, the response was empty.".formatted(organizationId))));
×
79
    }
80

81
    private String getDid(String organizationId, OrganizationVO ovo) {
82
        String did = getDidFromExternalReference(ovo.getExternalReference())
1 ✔
83
                .or(() -> getDidFromPartyCharacteristics(ovo.getPartyCharacteristic()))
1 ✔
84
                .orElseThrow(() -> new TMForumException(FailureReason.ORGANIZATION_DID_MISSING,
1 ✔
85
                        ("Organization %s has no DID: expected an externalReference of type '%s' or a partyCharacteristic '%s' " +
86
                                "holding a did:<method>:<id>.").formatted(organizationId, EXTERNAL_REFERENCE_IDM_ID, PARTY_CHARACTERISTIC_DID)));
1 ✔
87
        log.debug("Organization {} has DID {}", organizationId, did);
1 ✔
88
        return did;
1 ✔
89
    }
90

91
    public boolean hasProviderRole(String role) {
92
        return PROVIDER_ROLE.equalsIgnoreCase(role);
×
93
    }
94

95
    private Optional<String> getDidFromPartyCharacteristics(List<CharacteristicVO> characteristicVOS) {
96
        if (characteristicVOS == null) {
1 ✔
97
            return Optional.empty();
1 ✔
98
        }
99
        return characteristicVOS.stream()
×
100
                .filter(entry -> PARTY_CHARACTERISTIC_DID.equals(entry.getName()))
×
101
                .map(CharacteristicVO::getValue)
×
102
                .filter(e -> e instanceof String)
×
103
                .map(e -> (String) e)
×
104
                .filter(this::isDid)
×
105
                .findAny();
×
106
    }
107

108
    private Optional<String> getDidFromExternalReference(List<ExternalReferenceVO> externalReferenceVOList) {
109
        if (externalReferenceVOList == null) {
1 ✔
110
            return Optional.empty();
1 ✔
111
        }
112
        return externalReferenceVOList.stream()
1 ✔
113
                .filter(ervo -> EXTERNAL_REFERENCE_IDM_ID.equals(ervo.getExternalReferenceType()))
1 ✔
114
                .map(ExternalReferenceVO::getName)
1 ✔
115
                .filter(this::isDid)
1 ✔
116
                .findFirst();
1 ✔
117
    }
118

119
    private boolean isDid(String id) {
120
        String[] idParts = id.split(":");
1 ✔
121
        return idParts.length >= 3 && idParts[0].equals(DID);
1 ✔
122
    }
123
}
124

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