Skip to content

Commit bde8dcc

Browse files
Adrian ClayORybak5
andauthored
Create endpoint for resending an EhrExtract (#990)
--------- Co-authored-by: hospel <[email protected]>
1 parent bf88ffd commit bde8dcc

File tree

4 files changed

+443
-1
lines changed

4 files changed

+443
-1
lines changed

service/src/main/java/uk/nhs/adaptors/gp2gp/common/service/FhirParseService.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
import org.hl7.fhir.instance.model.api.IBaseResource;
44
import org.springframework.stereotype.Service;
5-
65
import ca.uhn.fhir.context.FhirContext;
76
import ca.uhn.fhir.parser.IParser;
87
import ca.uhn.fhir.parser.StrictErrorHandler;
98
import uk.nhs.adaptors.gp2gp.common.exception.FhirValidationException;
109

1110
@Service
1211
public class FhirParseService {
12+
1313
private final IParser jsonParser = prepareParser();
1414

1515
public <T extends IBaseResource> T parseResource(String body, Class<T> fhirClass) {
@@ -20,6 +20,10 @@ public <T extends IBaseResource> T parseResource(String body, Class<T> fhirClass
2020
}
2121
}
2222

23+
public String encodeToJson(IBaseResource resource) {
24+
return jsonParser.setPrettyPrint(true).encodeResourceToString(resource);
25+
}
26+
2327
private IParser prepareParser() {
2428
FhirContext ctx = FhirContext.forDstu3();
2529
ctx.newJsonParser();
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package uk.nhs.adaptors.gp2gp.ehr;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.hl7.fhir.dstu3.model.CodeableConcept;
6+
import org.hl7.fhir.dstu3.model.Coding;
7+
import org.hl7.fhir.dstu3.model.Meta;
8+
import org.hl7.fhir.dstu3.model.OperationOutcome;
9+
import org.hl7.fhir.dstu3.model.UriType;
10+
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.http.HttpStatus;
12+
import org.springframework.http.ResponseEntity;
13+
import org.springframework.web.bind.annotation.PathVariable;
14+
import org.springframework.web.bind.annotation.PostMapping;
15+
import org.springframework.web.bind.annotation.RequestMapping;
16+
import org.springframework.web.bind.annotation.RestController;
17+
import uk.nhs.adaptors.gp2gp.common.service.FhirParseService;
18+
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
19+
import uk.nhs.adaptors.gp2gp.common.service.TimestampService;
20+
import uk.nhs.adaptors.gp2gp.common.task.TaskDispatcher;
21+
import uk.nhs.adaptors.gp2gp.ehr.model.EhrExtractStatus;
22+
import uk.nhs.adaptors.gp2gp.gpc.GetGpcStructuredTaskDefinition;
23+
24+
import java.util.Collections;
25+
26+
@Slf4j
27+
@RestController
28+
@AllArgsConstructor(onConstructor = @__(@Autowired))
29+
@RequestMapping(path = "/ehr-resend")
30+
public class EhrResendController {
31+
32+
private static final String OPERATION_OUTCOME_URL = "https://fhir.nhs.uk/STU3/StructureDefinition/GPConnect-OperationOutcome-1";
33+
private static final String PRECONDITION_FAILED = "PRECONDITION_FAILED";
34+
private static final String INVALID_IDENTIFIER_VALUE = "INVALID_IDENTIFIER_VALUE";
35+
36+
private EhrExtractStatusRepository ehrExtractStatusRepository;
37+
private TaskDispatcher taskDispatcher;
38+
private RandomIdGeneratorService randomIdGeneratorService;
39+
private final TimestampService timestampService;
40+
private final FhirParseService fhirParseService;
41+
42+
@PostMapping("/{conversationId}")
43+
public ResponseEntity<String> scheduleEhrExtractResend(@PathVariable String conversationId) {
44+
EhrExtractStatus ehrExtractStatus = ehrExtractStatusRepository.findByConversationId(conversationId).orElseGet(() -> null);
45+
46+
if (ehrExtractStatus == null) {
47+
var details = getCodeableConcept(INVALID_IDENTIFIER_VALUE);
48+
var diagnostics = "Provide a conversationId that exists and retry the operation";
49+
50+
var operationOutcome = createOperationOutcome(OperationOutcome.IssueType.VALUE,
51+
OperationOutcome.IssueSeverity.ERROR,
52+
details,
53+
diagnostics);
54+
var errorBody = fhirParseService.encodeToJson(operationOutcome);
55+
56+
return new ResponseEntity<>(errorBody, HttpStatus.NOT_FOUND);
57+
}
58+
59+
if (hasNoErrorsInEhrReceivedAcknowledgement(ehrExtractStatus) && ehrExtractStatus.getError() == null) {
60+
61+
var details = getCodeableConcept(PRECONDITION_FAILED);
62+
var diagnostics = "The current resend operation is still in progress. Please wait for it to complete before retrying";
63+
var operationOutcome = createOperationOutcome(OperationOutcome.IssueType.BUSINESSRULE,
64+
OperationOutcome.IssueSeverity.ERROR,
65+
details,
66+
diagnostics);
67+
var errorBody = fhirParseService.encodeToJson(operationOutcome);
68+
return new ResponseEntity<>(errorBody, HttpStatus.CONFLICT);
69+
}
70+
71+
var updatedEhrExtractStatus = prepareEhrExtractStatusForNewResend(ehrExtractStatus);
72+
ehrExtractStatusRepository.save(updatedEhrExtractStatus);
73+
createGetGpcStructuredTask(updatedEhrExtractStatus);
74+
LOGGER.info("Scheduled GetGpcStructuredTask for resend of ConversationId: {}", conversationId);
75+
76+
return new ResponseEntity<>(HttpStatus.ACCEPTED);
77+
}
78+
79+
private static CodeableConcept getCodeableConcept(String codeableConceptCode) {
80+
return new CodeableConcept().addCoding(
81+
new Coding("https://fhir.nhs.uk/STU3/ValueSet/Spine-ErrorOrWarningCode-1", codeableConceptCode, null));
82+
}
83+
84+
private static boolean hasNoErrorsInEhrReceivedAcknowledgement(EhrExtractStatus ehrExtractStatus) {
85+
var ehrReceivedAcknowledgement = ehrExtractStatus.getEhrReceivedAcknowledgement();
86+
if (ehrReceivedAcknowledgement == null) {
87+
return true;
88+
}
89+
90+
var errors = ehrReceivedAcknowledgement.getErrors();
91+
if (errors == null || errors.isEmpty()) {
92+
return true;
93+
}
94+
return false;
95+
}
96+
97+
private EhrExtractStatus prepareEhrExtractStatusForNewResend(EhrExtractStatus ehrExtractStatus) {
98+
99+
var now = timestampService.now();
100+
ehrExtractStatus.setUpdatedAt(now);
101+
ehrExtractStatus.setMessageTimestamp(now);
102+
ehrExtractStatus.setEhrExtractCorePending(null);
103+
ehrExtractStatus.setGpcAccessDocument(null);
104+
ehrExtractStatus.setEhrContinue(null);
105+
ehrExtractStatus.setEhrReceivedAcknowledgement(null);
106+
107+
return ehrExtractStatus;
108+
}
109+
110+
private void createGetGpcStructuredTask(EhrExtractStatus ehrExtractStatus) {
111+
var getGpcStructuredTaskDefinition = GetGpcStructuredTaskDefinition.getGetGpcStructuredTaskDefinition(randomIdGeneratorService,
112+
ehrExtractStatus);
113+
taskDispatcher.createTask(getGpcStructuredTaskDefinition);
114+
}
115+
116+
public static OperationOutcome createOperationOutcome(
117+
OperationOutcome.IssueType type, OperationOutcome.IssueSeverity severity, CodeableConcept details, String diagnostics) {
118+
var operationOutcome = new OperationOutcome();
119+
Meta meta = new Meta();
120+
meta.setProfile(Collections.singletonList(new UriType(OPERATION_OUTCOME_URL)));
121+
operationOutcome.setMeta(meta);
122+
operationOutcome.addIssue()
123+
.setCode(type)
124+
.setSeverity(severity)
125+
.setDetails(details)
126+
.setDiagnostics(diagnostics);
127+
return operationOutcome;
128+
}
129+
130+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package uk.nhs.adaptors.gp2gp.common.service;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import org.hl7.fhir.dstu3.model.CodeableConcept;
7+
import org.hl7.fhir.dstu3.model.Coding;
8+
import org.hl7.fhir.dstu3.model.OperationOutcome;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.Test;
11+
import org.junit.jupiter.api.extension.ExtendWith;
12+
import org.mockito.junit.jupiter.MockitoExtension;
13+
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
14+
import uk.nhs.adaptors.gp2gp.common.configuration.ObjectMapperBean;
15+
import uk.nhs.adaptors.gp2gp.ehr.EhrResendController;
16+
import java.util.List;
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
19+
20+
@ExtendWith(MockitoExtension.class)
21+
class FhirParseServiceTest {
22+
23+
public static final String INVALID_IDENTIFIER_VALUE = "INVALID_IDENTIFIER_VALUE";
24+
private static final String OPERATION_OUTCOME_URL = "https://fhir.nhs.uk/STU3/StructureDefinition/GPConnect-OperationOutcome-1";
25+
private OperationOutcome operationOutcome;
26+
private ObjectMapper objectMapper;
27+
28+
@BeforeEach
29+
void setUp() {
30+
ObjectMapperBean objectMapperBean = new ObjectMapperBean();
31+
objectMapper = objectMapperBean.objectMapper(new Jackson2ObjectMapperBuilder());
32+
33+
var details = getCodeableConcept();
34+
var diagnostics = "Provide a conversationId that exists and retry the operation";
35+
operationOutcome = EhrResendController.createOperationOutcome(OperationOutcome.IssueType.VALUE,
36+
OperationOutcome.IssueSeverity.ERROR,
37+
details,
38+
diagnostics);
39+
}
40+
41+
@Test
42+
void ableToEncodeOperationOutcomeToJson() throws JsonProcessingException {
43+
FhirParseService fhirParseService = new FhirParseService();
44+
45+
String convertedToJsonOperationOutcome = fhirParseService.encodeToJson(operationOutcome);
46+
47+
JsonNode rootNode = objectMapper.readTree(convertedToJsonOperationOutcome);
48+
String code =
49+
rootNode.path("issue").get(0).path("details").path("coding").get(0).path("code").asText();
50+
String operationOutcomeUrl = rootNode.path("meta").path("profile").get(0).asText();
51+
52+
assertEquals(INVALID_IDENTIFIER_VALUE, code);
53+
assertEquals(OPERATION_OUTCOME_URL, operationOutcomeUrl);
54+
}
55+
56+
private static CodeableConcept getCodeableConcept() {
57+
var details = new CodeableConcept();
58+
var codeableConceptCoding = new Coding();
59+
codeableConceptCoding.setSystem("http://fhir.nhs.net/ValueSet/gpconnect-error-or-warning-code-1");
60+
codeableConceptCoding.setCode(FhirParseServiceTest.INVALID_IDENTIFIER_VALUE);
61+
details.setCoding(List.of(codeableConceptCoding));
62+
return details;
63+
}
64+
}

0 commit comments

Comments
 (0)