Skip to content

Commit c3fca3b

Browse files
Validated XML before sending
Changed wording
1 parent cc14997 commit c3fca3b

File tree

8 files changed

+131
-7
lines changed

8 files changed

+131
-7
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package uk.nhs.adaptors.gp2gp.ehr.exception;
2+
3+
public class XmlSchemaValidationException extends RuntimeException {
4+
5+
public XmlSchemaValidationException(String message, Throwable cause) {
6+
super(message, cause);
7+
}
8+
}

service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapper.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
import org.springframework.beans.factory.annotation.Value;
1212
import org.springframework.stereotype.Component;
1313

14+
import uk.nhs.adaptors.gp2gp.common.configuration.RedactionsContext;
1415
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
1516
import uk.nhs.adaptors.gp2gp.common.service.TimestampService;
17+
import uk.nhs.adaptors.gp2gp.ehr.exception.XmlSchemaValidationException;
1618
import uk.nhs.adaptors.gp2gp.ehr.mapper.parameters.EhrExtractTemplateParameters;
1719
import uk.nhs.adaptors.gp2gp.ehr.mapper.parameters.SkeletonComponentTemplateParameters;
1820
import uk.nhs.adaptors.gp2gp.ehr.utils.DateFormatUtil;
@@ -23,17 +25,33 @@
2325

2426
import uk.nhs.adaptors.gp2gp.ehr.exception.EhrValidationException;
2527

28+
import javax.xml.validation.Schema;
29+
import javax.xml.validation.SchemaFactory;
30+
import javax.xml.validation.Validator;
31+
import javax.xml.transform.stream.StreamSource;
32+
import org.xml.sax.SAXException;
33+
34+
import java.io.File;
35+
import java.io.IOException;
36+
import java.io.StringReader;
2637
import java.util.List;
2738
import java.util.Optional;
2839
import java.util.stream.Collectors;
2940

41+
42+
3043
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
3144
@Component
3245
@Slf4j
3346
public class EhrExtractMapper {
47+
private final RedactionsContext redactionsContext;
3448
private static final Mustache EHR_EXTRACT_TEMPLATE = TemplateUtils.loadTemplate("ehr_extract_template.mustache");
3549
private static final Mustache SKELETON_COMPONENT_TEMPLATE = TemplateUtils.loadTemplate("ehr_skeleton_component_template.mustache");
3650
private static final String CONSULTATION_LIST_CODE = "325851000000107";
51+
private static final String SCHEMA_PATH = "../service/src/test/resources/mim/Schemas/";
52+
53+
private static final String RCMR_IN030000UK06_SCHEMA_PATH = SCHEMA_PATH + RedactionsContext.NON_REDACTION_INTERACTION_ID + ".xsd";
54+
private static final String RCMR_IN030000UK07_SCHEMA_PATH = SCHEMA_PATH + RedactionsContext.REDACTION_INTERACTION_ID + ".xsd";
3755

3856
private final RandomIdGeneratorService randomIdGeneratorService;
3957
private final TimestampService timestampService;
@@ -49,6 +67,27 @@ public String mapEhrExtractToXml(EhrExtractTemplateParameters ehrExtractTemplate
4967
return TemplateUtils.fillTemplate(EHR_EXTRACT_TEMPLATE, ehrExtractTemplateParameters);
5068
}
5169

70+
71+
public void validateXmlAgainstSchema(String xml) {
72+
String schemaPath = RedactionsContext.REDACTION_INTERACTION_ID.equals(redactionsContext.ehrExtractInteractionId())
73+
? RCMR_IN030000UK07_SCHEMA_PATH
74+
: RCMR_IN030000UK06_SCHEMA_PATH;
75+
76+
try {
77+
SchemaFactory factory = SchemaFactory.newDefaultInstance();
78+
Schema schema = factory.newSchema(new File(schemaPath));
79+
Validator validator = schema.newValidator();
80+
81+
validator.validate(new StreamSource(new StringReader(xml)));
82+
83+
LOGGER.info("XML successfully validated against schema: {}", schemaPath);
84+
} catch (SAXException | IOException e) {
85+
LOGGER.error("XML validation failed: {}", e.getMessage(), e);
86+
throw new XmlSchemaValidationException("XML schema validation failed", e);
87+
}
88+
}
89+
90+
5291
public EhrExtractTemplateParameters mapBundleToEhrFhirExtractParams(
5392
GetGpcStructuredTaskDefinition getGpcStructuredTaskDefinition, Bundle bundle) {
5493
var ehrExtractTemplateParameters = setSharedExtractParams(getGpcStructuredTaskDefinition);

service/src/main/java/uk/nhs/adaptors/gp2gp/gpc/StructuredRecordMappingService.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import uk.nhs.adaptors.gp2gp.common.configuration.Gp2gpConfiguration;
2121
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
2222
import uk.nhs.adaptors.gp2gp.ehr.EhrExtractStatusService;
23+
import uk.nhs.adaptors.gp2gp.ehr.exception.XmlSchemaValidationException;
2324
import uk.nhs.adaptors.gp2gp.ehr.mapper.EhrExtractMapper;
2425
import uk.nhs.adaptors.gp2gp.ehr.mapper.MessageContext;
2526
import uk.nhs.adaptors.gp2gp.ehr.mapper.OutputMessageWrapperMapper;
@@ -58,6 +59,7 @@ public class StructuredRecordMappingService {
5859
private final SupportedContentTypes supportedContentTypes;
5960
private final EhrExtractStatusService ehrExtractStatusService;
6061

62+
6163
private DocumentBuilder documentBuilder;
6264

6365
public static final String DEFAULT_ATTACHMENT_CONTENT_TYPE = "text/plain";
@@ -151,6 +153,13 @@ public String mapStructuredRecordToEhrExtractXml(GetGpcStructuredTaskDefinition
151153
.mapBundleToEhrFhirExtractParams(structuredTaskDefinition, bundle);
152154
String ehrExtractContent = ehrExtractMapper.mapEhrExtractToXml(ehrExtractTemplateParameters);
153155

156+
try {
157+
ehrExtractMapper.validateXmlAgainstSchema(ehrExtractContent);
158+
} catch (XmlSchemaValidationException e) {
159+
LOGGER.error("EHR Extract XML validation failed: {}", e.getMessage());
160+
161+
}
162+
154163
ehrExtractStatusService.saveEhrExtractMessageId(structuredTaskDefinition.getConversationId(),
155164
ehrExtractTemplateParameters.getEhrExtractId());
156165

service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperComponentTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.mockito.junit.jupiter.MockitoExtension;
1717
import org.mockito.junit.jupiter.MockitoSettings;
1818
import org.mockito.quality.Strictness;
19+
import uk.nhs.adaptors.gp2gp.common.configuration.RedactionsContext;
1920
import uk.nhs.adaptors.gp2gp.common.service.ConfidentialityService;
2021
import uk.nhs.adaptors.gp2gp.common.service.FhirParseService;
2122
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
@@ -101,6 +102,8 @@ class EhrExtractMapperComponentTest {
101102
private CodeableConceptCdMapper codeableConceptCdMapper;
102103
@Mock
103104
private ConfidentialityService confidentialityService;
105+
@Mock
106+
private RedactionsContext redactionsContext;
104107

105108
private NonConsultationResourceMapper nonConsultationResourceMapper;
106109
private EhrExtractMapper ehrExtractMapper;
@@ -218,7 +221,7 @@ codeableConceptCdMapper, new ParticipantMapper(), confidentialityService),
218221
new BloodPressureValidator()
219222
);
220223

221-
ehrExtractMapper = new EhrExtractMapper(randomIdGeneratorService,
224+
ehrExtractMapper = new EhrExtractMapper(redactionsContext, randomIdGeneratorService,
222225
timestampService,
223226
new EncounterMapper(messageContext, encounterComponentsMapper, confidentialityService),
224227
nonConsultationResourceMapper,

service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
import org.mockito.junit.jupiter.MockitoExtension;
2222
import org.springframework.test.util.ReflectionTestUtils;
2323

24+
import uk.nhs.adaptors.gp2gp.common.configuration.RedactionsContext;
2425
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
2526
import uk.nhs.adaptors.gp2gp.common.service.TimestampService;
2627
import uk.nhs.adaptors.gp2gp.ehr.exception.EhrValidationException;
28+
import uk.nhs.adaptors.gp2gp.ehr.exception.XmlSchemaValidationException;
2729
import uk.nhs.adaptors.gp2gp.gpc.GetGpcStructuredTaskDefinition;
2830

2931
@ExtendWith(MockitoExtension.class)
@@ -52,6 +54,8 @@ class EhrExtractMapperTest {
5254
private EhrFolderEffectiveTime ehrFolderEffectiveTime;
5355
@InjectMocks
5456
private EhrExtractMapper ehrExtractMapper;
57+
@Mock
58+
private RedactionsContext redactionsContext;
5559

5660
@Test
5761
void When_NhsOverrideNumberProvided_Expect_OverrideToBeUsed() {
@@ -155,4 +159,34 @@ void When_BuildEhrCompositionForSkeletonEhrExtract_Expect_ExpectedComponentBuilt
155159

156160
assertThat(actual).isEqualTo(expected);
157161
}
162+
163+
@Test
164+
void When_ValidateXmlAgainstSchemaWithInvalidXmlAndRedactionID_Expect_XmlSchemaValidationExceptionIsThrown() {
165+
String invalidXml = "<invalid><xml>";
166+
167+
when(redactionsContext.ehrExtractInteractionId())
168+
.thenReturn(RedactionsContext.REDACTION_INTERACTION_ID);
169+
170+
XmlSchemaValidationException ex = assertThrows(XmlSchemaValidationException.class, () -> {
171+
ehrExtractMapper.validateXmlAgainstSchema(invalidXml);
172+
});
173+
174+
assertThat(ex.getMessage()).contains("XML schema validation failed");
175+
}
176+
177+
@Test
178+
void When_ValidateXmlAgainstSchemaWithInvalidXmlAndNonRedactionID_Expect_XmlSchemaValidationExceptionIsThrown() {
179+
String invalidXml = "<invalid><xml>";
180+
181+
when(redactionsContext.ehrExtractInteractionId())
182+
.thenReturn("non-redaction-id");
183+
184+
XmlSchemaValidationException ex = assertThrows(XmlSchemaValidationException.class, () -> {
185+
ehrExtractMapper.validateXmlAgainstSchema(invalidXml);
186+
});
187+
188+
assertThat(ex.getMessage()).contains("XML schema validation failed");
189+
}
190+
191+
158192
}

service/src/test/java/uk/nhs/adaptors/gp2gp/gpc/StructuredRecordMappingServiceTest.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
import static org.assertj.core.api.Assertions.assertThat;
44
import static org.mockito.ArgumentMatchers.any;
55
import static org.mockito.ArgumentMatchers.anyString;
6-
import static org.mockito.Mockito.lenient;
7-
import static org.mockito.Mockito.mock;
8-
import static org.mockito.Mockito.verify;
9-
import static org.mockito.Mockito.when;
106

7+
import static org.mockito.Mockito.*;
118
import static uk.nhs.adaptors.gp2gp.utils.IdUtil.buildIdType;
129

1310
import java.util.Arrays;
@@ -30,6 +27,7 @@
3027
import uk.nhs.adaptors.gp2gp.common.configuration.Gp2gpConfiguration;
3128
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
3229
import uk.nhs.adaptors.gp2gp.ehr.EhrExtractStatusService;
30+
import uk.nhs.adaptors.gp2gp.ehr.exception.XmlSchemaValidationException;
3331
import uk.nhs.adaptors.gp2gp.ehr.mapper.EhrExtractMapper;
3432
import uk.nhs.adaptors.gp2gp.ehr.mapper.IdMapper;
3533
import uk.nhs.adaptors.gp2gp.ehr.mapper.MessageContext;
@@ -280,6 +278,40 @@ void When_BuildingSkeletonForEhrExtractWithoutChildComponentNodesToReplace_Expec
280278
assertXMLEquals(skeletonEhrExtract, expectedSkeletonEhrExtract);
281279
}
282280

281+
@Test
282+
void When_XmlSchemaValidationFails_Expect_ErrorLoggedAndProcessingContinues() {
283+
var structuredTaskDefinition = mock(GetGpcStructuredTaskDefinition.class);
284+
var bundle = mock(Bundle.class);
285+
var ehrExtractTemplateParameters = mock(EhrExtractTemplateParameters.class);
286+
287+
var ehrExtractContent = "<EhrExtract>some invalid content</EhrExtract>";
288+
var expectedHL7 = "some wrapped hl7 message";
289+
var conversationId = "conversation-id";
290+
var ehrExtractId = "ehr-extract-id";
291+
292+
when(ehrExtractMapper.mapBundleToEhrFhirExtractParams(structuredTaskDefinition, bundle))
293+
.thenReturn(ehrExtractTemplateParameters);
294+
when(ehrExtractMapper.mapEhrExtractToXml(ehrExtractTemplateParameters)).thenReturn(ehrExtractContent);
295+
when(ehrExtractTemplateParameters.getEhrExtractId()).thenReturn(ehrExtractId);
296+
when(structuredTaskDefinition.getConversationId()).thenReturn(conversationId);
297+
when(outputMessageWrapperMapper.map(structuredTaskDefinition, ehrExtractContent))
298+
.thenReturn(expectedHL7);
299+
300+
doThrow(new XmlSchemaValidationException("Invalid XML", new RuntimeException("Invalid XML")))
301+
.when(ehrExtractMapper).validateXmlAgainstSchema(ehrExtractContent);
302+
303+
var actualHL7 = structuredRecordMappingService.mapStructuredRecordToEhrExtractXml(structuredTaskDefinition, bundle);
304+
305+
verify(ehrExtractMapper).mapBundleToEhrFhirExtractParams(structuredTaskDefinition, bundle);
306+
verify(ehrExtractMapper).mapEhrExtractToXml(ehrExtractTemplateParameters);
307+
verify(ehrExtractMapper).validateXmlAgainstSchema(ehrExtractContent);
308+
verify(outputMessageWrapperMapper).map(structuredTaskDefinition, ehrExtractContent);
309+
verify(ehrExtractStatusService).saveEhrExtractMessageId(conversationId, ehrExtractId);
310+
311+
assertThat(actualHL7).isEqualTo(expectedHL7);
312+
}
313+
314+
283315
public static void assertXMLEquals(String actualXML, String expectedXML) throws Exception {
284316
XMLUnit.setIgnoreWhitespace(true);
285317

service/src/test/java/uk/nhs/adaptors/gp2gp/uat/EhrExtractUATTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public void setUp() {
157157
final NonConsultationResourceMapper nonConsultationResourceMapper =
158158
new NonConsultationResourceMapper(messageContext, randomIdGeneratorService, encounterComponentsMapper,
159159
new BloodPressureValidator());
160-
ehrExtractMapper = new EhrExtractMapper(randomIdGeneratorService, timestampService, encounterMapper,
160+
ehrExtractMapper = new EhrExtractMapper(redactionsContext, randomIdGeneratorService, timestampService, encounterMapper,
161161
nonConsultationResourceMapper, agentDirectoryMapper, messageContext);
162162
lenient().when(confidentialityService.generateConfidentialityCode(any()))
163163
.thenReturn(Optional.empty());

service/src/test/resources/uat/output/TC4/9465701262_Meyers_full_20210119.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
<?xml version="1.0" encoding="UTF-8"?>
21
<RCMR_IN030000UK06 xmlns="urn:hl7-org:v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:hl7-org:v3 ..\Schemas\RCMR_IN030000UK06.xsd">
32
<id root="0D07B623-3926-4245-80AF-97F1098D90C1" />
43
<creationTime value="20200101010101" />

0 commit comments

Comments
 (0)