Skip to content

Commit 3201151

Browse files
jrkkppkoivisto
authored andcommitted
OTR(Backend & Frontend) OPHOTRKEH-248: NPE/Enum korjaus kun ONR-tiedot muuttuneet (#587)
* OTR(Backend) OPHOTRKEH-248: NPE fix when interpreter is not found from cache * OTR(Backend) OPHOTRKEH-248: NPE fix when not found from cache. Also fix unknown enum in contact source * OTR(Backend) OPHOTRKEH-248: test for ONR response deserialization * OTR(Backend) OPHOTRKEH-248: test for ONR response deserialization * OTR(Frontend) OPHOTRKEH-248: show alert if interpreters are not synchronized between OTR and ONR [deploy] * OTR(Frontend) OPHOTRKEH-248: mock-up endpoint for missing interpreters [deploy] * OTR(Frontend) OPHOTRKEH-248: error handler for missing interpreters [deploy]
1 parent 73f9029 commit 3201151

File tree

15 files changed

+338
-49
lines changed

15 files changed

+338
-49
lines changed

backend/otr/src/main/java/fi/oph/otr/api/clerk/ClerkInterpreterController.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ public List<ClerkInterpreterDTO> listInterpreters() {
4242
return clerkInterpreterService.list();
4343
}
4444

45+
@GetMapping(path = "/missing")
46+
@Operation(tags = TAG_INTERPRETER, summary = "List interpreters that are missing from ONR")
47+
public List<String> listMissingInterpreters() {
48+
return clerkInterpreterService.listMissing();
49+
}
50+
4551
@Operation(tags = TAG_INTERPRETER, summary = "Create new interpreter")
4652
@PostMapping(consumes = APPLICATION_JSON_VALUE)
4753
@ResponseStatus(HttpStatus.CREATED)

backend/otr/src/main/java/fi/oph/otr/onr/OnrOperationApiImpl.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package fi.oph.otr.onr;
22

33
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.fasterxml.jackson.databind.DeserializationFeature;
45
import com.fasterxml.jackson.databind.ObjectMapper;
56
import fi.oph.otr.config.Constants;
67
import fi.oph.otr.onr.dto.ContactDetailsGroupDTO;
@@ -12,7 +13,6 @@
1213
import java.util.Map;
1314
import java.util.Optional;
1415
import java.util.concurrent.TimeUnit;
15-
import lombok.RequiredArgsConstructor;
1616
import net.minidev.json.JSONArray;
1717
import org.asynchttpclient.Request;
1818
import org.asynchttpclient.RequestBuilder;
@@ -21,7 +21,6 @@
2121
import org.springframework.http.HttpStatus;
2222
import org.springframework.http.MediaType;
2323

24-
@RequiredArgsConstructor
2524
public class OnrOperationApiImpl implements OnrOperationApi {
2625

2726
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@@ -30,6 +29,13 @@ public class OnrOperationApiImpl implements OnrOperationApi {
3029

3130
private final String onrServiceUrl;
3231

32+
public OnrOperationApiImpl(final CasClient onrClient, final String onrServiceUrl) {
33+
this.onrClient = onrClient;
34+
this.onrServiceUrl = onrServiceUrl;
35+
36+
OBJECT_MAPPER.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
37+
}
38+
3339
@Override
3440
public Map<String, PersonalData> fetchPersonalDatas(final List<String> onrIds) throws Exception {
3541
// /henkilo/masterHenkilosByOidList might be usable as an endpoint for fetching master person data for persons

backend/otr/src/main/java/fi/oph/otr/onr/dto/ContactDetailsGroupSource.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package fi.oph.otr.onr.dto;
22

3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
34
import com.fasterxml.jackson.annotation.JsonProperty;
45

6+
@JsonIgnoreProperties(ignoreUnknown = true)
57
public enum ContactDetailsGroupSource {
68
@JsonProperty("alkupera1")
79
VTJ,

backend/otr/src/main/java/fi/oph/otr/onr/dto/ContactDetailsGroupType.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package fi.oph.otr.onr.dto;
22

3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
34
import com.fasterxml.jackson.annotation.JsonProperty;
45

6+
@JsonIgnoreProperties(ignoreUnknown = true)
57
public enum ContactDetailsGroupType {
68
@JsonProperty("yhteystietotyyppi1")
79
KOTIOSOITE,

backend/otr/src/main/java/fi/oph/otr/service/ClerkInterpreterService.java

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@
3434
import java.util.Comparator;
3535
import java.util.List;
3636
import java.util.Map;
37+
import java.util.Optional;
3738
import java.util.function.Function;
3839
import java.util.stream.Collectors;
3940
import java.util.stream.Stream;
4041
import lombok.RequiredArgsConstructor;
42+
import org.slf4j.Logger;
43+
import org.slf4j.LoggerFactory;
4144
import org.springframework.stereotype.Service;
4245
import org.springframework.transaction.annotation.Transactional;
4346

@@ -69,13 +72,24 @@ public class ClerkInterpreterService {
6972
@Resource
7073
private final AuditService auditService;
7174

75+
private static final Logger LOG = LoggerFactory.getLogger(ClerkInterpreterService.class);
76+
7277
@Transactional(readOnly = true)
7378
public List<ClerkInterpreterDTO> list() {
7479
final List<ClerkInterpreterDTO> result = listWithoutAudit();
7580
auditService.logOperation(OtrOperation.LIST_INTERPRETERS);
7681
return result;
7782
}
7883

84+
@Transactional(readOnly = true)
85+
public List<String> listMissing() {
86+
final List<Interpreter> interpreters = interpreterRepository.findExistingInterpreters();
87+
final Map<String, PersonalData> personalDatas = onrService.getCachedPersonalDatas();
88+
auditService.logOperation(OtrOperation.LIST_INTERPRETERS);
89+
90+
return interpreters.stream().map(Interpreter::getOnrId).filter(onrId -> personalDatas.get(onrId) == null).toList();
91+
}
92+
7993
private List<ClerkInterpreterDTO> listWithoutAudit() {
8094
final Map<Long, List<InterpreterRegionProjection>> interpreterRegionProjections = regionRepository
8195
.listInterpreterRegionProjections()
@@ -105,16 +119,22 @@ private List<ClerkInterpreterDTO> listWithoutAudit() {
105119

106120
return createClerkInterpreterDTO(interpreter, personalData, qualifications, regionProjections);
107121
})
122+
.filter(Optional::isPresent)
123+
.map(Optional::get)
108124
.sorted(Comparator.comparing(ClerkInterpreterDTO::lastName).thenComparing(ClerkInterpreterDTO::nickName))
109125
.toList();
110126
}
111127

112-
private ClerkInterpreterDTO createClerkInterpreterDTO(
128+
private Optional<ClerkInterpreterDTO> createClerkInterpreterDTO(
113129
final Interpreter interpreter,
114130
final PersonalData personalData,
115131
final List<Qualification> qualifications,
116132
final List<InterpreterRegionProjection> regionProjections
117133
) {
134+
if (personalData == null) {
135+
LOG.error("Personal data by onr id {} not found", interpreter.getOnrId());
136+
return Optional.empty();
137+
}
118138
final List<String> regions = regionProjections.stream().map(InterpreterRegionProjection::code).toList();
119139

120140
final List<ClerkQualificationDTO> qualificationDTOs = qualifications
@@ -127,30 +147,32 @@ private ClerkInterpreterDTO createClerkInterpreterDTO(
127147
.toList();
128148
final ClerkInterpreterQualificationsDTO interpreterQualificationsDTO = splitQualificationDTOs(qualificationDTOs);
129149

130-
return ClerkInterpreterDTO
131-
.builder()
132-
.id(interpreter.getId())
133-
.version(interpreter.getVersion())
134-
.isIndividualised(personalData.getIndividualised())
135-
.hasIndividualisedAddress(personalData.getHasIndividualisedAddress())
136-
.identityNumber(personalData.getIdentityNumber())
137-
.lastName(personalData.getLastName())
138-
.firstName(personalData.getFirstName())
139-
.nickName(personalData.getNickName())
140-
.email(personalData.getEmail())
141-
.permissionToPublishEmail(interpreter.isPermissionToPublishEmail())
142-
.phoneNumber(personalData.getPhoneNumber())
143-
.permissionToPublishPhone(interpreter.isPermissionToPublishPhone())
144-
.otherContactInfo(interpreter.getOtherContactInformation())
145-
.permissionToPublishOtherContactInfo(interpreter.isPermissionToPublishOtherContactInfo())
146-
.street(personalData.getStreet())
147-
.postalCode(personalData.getPostalCode())
148-
.town(personalData.getTown())
149-
.country(personalData.getCountry())
150-
.extraInformation(interpreter.getExtraInformation())
151-
.regions(regions)
152-
.qualifications(interpreterQualificationsDTO)
153-
.build();
150+
return Optional.of(
151+
ClerkInterpreterDTO
152+
.builder()
153+
.id(interpreter.getId())
154+
.version(interpreter.getVersion())
155+
.isIndividualised(personalData.getIndividualised())
156+
.hasIndividualisedAddress(personalData.getHasIndividualisedAddress())
157+
.identityNumber(personalData.getIdentityNumber())
158+
.lastName(personalData.getLastName())
159+
.firstName(personalData.getFirstName())
160+
.nickName(personalData.getNickName())
161+
.email(personalData.getEmail())
162+
.permissionToPublishEmail(interpreter.isPermissionToPublishEmail())
163+
.phoneNumber(personalData.getPhoneNumber())
164+
.permissionToPublishPhone(interpreter.isPermissionToPublishPhone())
165+
.otherContactInfo(interpreter.getOtherContactInformation())
166+
.permissionToPublishOtherContactInfo(interpreter.isPermissionToPublishOtherContactInfo())
167+
.street(personalData.getStreet())
168+
.postalCode(personalData.getPostalCode())
169+
.town(personalData.getTown())
170+
.country(personalData.getCountry())
171+
.extraInformation(interpreter.getExtraInformation())
172+
.regions(regions)
173+
.qualifications(interpreterQualificationsDTO)
174+
.build()
175+
);
154176
}
155177

156178
private ClerkQualificationDTO createQualificationDTO(final Qualification qualification) {
@@ -341,7 +363,7 @@ public ClerkInterpreterDTO getInterpreter(final long interpreterId) {
341363

342364
private ClerkInterpreterDTO getInterpreterWithoutAudit(final long interpreterId) {
343365
// This could be optimized, by fetching only one interpreter and it's data, but is it worth of the programming work?
344-
for (ClerkInterpreterDTO i : listWithoutAudit()) {
366+
for (final ClerkInterpreterDTO i : listWithoutAudit()) {
345367
if (i.id() == interpreterId) {
346368
return i;
347369
}

backend/otr/src/main/java/fi/oph/otr/service/PublicInterpreterService.java

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
import java.util.Collections;
1616
import java.util.List;
1717
import java.util.Map;
18+
import java.util.Optional;
1819
import java.util.stream.Collectors;
1920
import lombok.RequiredArgsConstructor;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
2023
import org.springframework.stereotype.Service;
2124
import org.springframework.transaction.annotation.Transactional;
2225

@@ -36,6 +39,8 @@ public class PublicInterpreterService {
3639
@Resource
3740
private final OnrService onrService;
3841

42+
private static final Logger LOG = LoggerFactory.getLogger(PublicInterpreterService.class);
43+
3944
@Transactional(readOnly = true)
4045
public List<InterpreterDTO> list() {
4146
final Map<Long, List<InterpreterRegionProjection>> interpreterRegionProjections = regionRepository
@@ -65,37 +70,45 @@ public List<InterpreterDTO> list() {
6570

6671
return toDTO(interpreter, personalData, regionProjections, qualificationProjections);
6772
})
73+
.filter(Optional::isPresent)
74+
.map(Optional::get)
6875
.collect(Collectors.toCollection(ArrayList::new));
6976

7077
Collections.shuffle(interpreterDTOS);
7178
return interpreterDTOS;
7279
}
7380

74-
private InterpreterDTO toDTO(
81+
private Optional<InterpreterDTO> toDTO(
7582
final Interpreter interpreter,
7683
final PersonalData personalData,
7784
final List<InterpreterRegionProjection> regionProjections,
7885
final List<InterpreterQualificationProjection> qualificationProjections
7986
) {
87+
if (personalData == null) {
88+
LOG.error("Personal data by onr id {} not found", interpreter.getOnrId());
89+
return Optional.empty();
90+
}
8091
final List<String> regions = regionProjections.stream().map(InterpreterRegionProjection::code).toList();
8192

8293
final List<LanguagePairDTO> languagePairs = qualificationProjections
8394
.stream()
8495
.map(qp -> LanguagePairDTO.builder().from(qp.fromLang()).to(qp.toLang()).build())
8596
.toList();
8697

87-
return InterpreterDTO
88-
.builder()
89-
.id(interpreter.getId())
90-
.firstName(personalData.getNickName())
91-
.lastName(personalData.getLastName())
92-
.email(interpreter.isPermissionToPublishEmail() ? personalData.getEmail() : null)
93-
.phoneNumber(interpreter.isPermissionToPublishPhone() ? personalData.getPhoneNumber() : null)
94-
.otherContactInfo(
95-
interpreter.isPermissionToPublishOtherContactInfo() ? interpreter.getOtherContactInformation() : null
96-
)
97-
.regions(regions)
98-
.languages(languagePairs)
99-
.build();
98+
return Optional.of(
99+
InterpreterDTO
100+
.builder()
101+
.id(interpreter.getId())
102+
.firstName(personalData.getNickName())
103+
.lastName(personalData.getLastName())
104+
.email(interpreter.isPermissionToPublishEmail() ? personalData.getEmail() : null)
105+
.phoneNumber(interpreter.isPermissionToPublishPhone() ? personalData.getPhoneNumber() : null)
106+
.otherContactInfo(
107+
interpreter.isPermissionToPublishOtherContactInfo() ? interpreter.getOtherContactInformation() : null
108+
)
109+
.regions(regions)
110+
.languages(languagePairs)
111+
.build()
112+
);
100113
}
101114
}

backend/otr/src/main/java/fi/oph/otr/service/email/ClerkEmailService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private void createQualificationExpiryData(final Qualification qualification) {
9090

9191
createQualificationReminder(qualification, email);
9292
} else {
93-
LOG.warn("Personal data by onr id {} not found", interpreter.getOnrId());
93+
LOG.error("Personal data by onr id {} not found", interpreter.getOnrId());
9494
}
9595
}
9696

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package fi.oph.otr.onr;
2+
3+
import static org.junit.jupiter.api.Assertions.assertTrue;
4+
import static org.mockito.ArgumentMatchers.any;
5+
import static org.mockito.Mockito.mock;
6+
import static org.mockito.Mockito.when;
7+
8+
import fi.oph.otr.onr.model.PersonalData;
9+
import fi.vm.sade.javautils.nio.cas.CasClient;
10+
import java.io.IOException;
11+
import java.util.Optional;
12+
import org.asynchttpclient.Response;
13+
import org.junit.jupiter.api.Test;
14+
import org.springframework.beans.factory.annotation.Value;
15+
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
16+
import org.springframework.security.test.context.support.WithMockUser;
17+
18+
@WithMockUser
19+
@DataJpaTest
20+
class OnrRequestTest {
21+
22+
@Value("classpath:json/henkilo-hetu-response.json")
23+
private org.springframework.core.io.Resource hetuRequestResponse;
24+
25+
@Test
26+
void testFindPersonByIdentityNumberDeserializes() throws Exception {
27+
final Response response = mock(Response.class);
28+
final CasClient casClient = mock(CasClient.class);
29+
final OnrOperationApiImpl onrOperationApi = new OnrOperationApiImpl(casClient, "http://localhost");
30+
31+
when(casClient.executeBlocking(any())).thenReturn(response);
32+
when(response.getStatusCode()).thenReturn(200);
33+
when(response.getResponseBody()).thenReturn(getHetuMockJsonResponse());
34+
35+
final Optional<PersonalData> personalDataOptional = onrOperationApi.findPersonalDataByIdentityNumber("54321-54312");
36+
37+
assertTrue(personalDataOptional.isPresent());
38+
}
39+
40+
private String getHetuMockJsonResponse() throws IOException {
41+
return new String(hetuRequestResponse.getInputStream().readAllBytes());
42+
}
43+
}

0 commit comments

Comments
 (0)