Skip to content

Commit e8920d5

Browse files
Validated XML before sending
1 parent a6017f5 commit e8920d5

File tree

6 files changed

+90
-2
lines changed

6 files changed

+90
-2
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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import uk.nhs.adaptors.gp2gp.ehr.utils.ResourceExtractor;
2929
import uk.nhs.adaptors.gp2gp.mhs.model.Identifier;
3030
import uk.nhs.adaptors.gp2gp.mhs.model.OutboundMessage;
31+
import uk.nhs.adaptors.gp2gp.transformjsontoxmltool.XmlSchemaValidator;
3132

3233
import javax.xml.parsers.DocumentBuilder;
3334
import javax.xml.parsers.DocumentBuilderFactory;
@@ -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,8 @@ public String mapStructuredRecordToEhrExtractXml(GetGpcStructuredTaskDefinition
151153
.mapBundleToEhrFhirExtractParams(structuredTaskDefinition, bundle);
152154
String ehrExtractContent = ehrExtractMapper.mapEhrExtractToXml(ehrExtractTemplateParameters);
153155

156+
ehrExtractMapper.validateXmlAgainstSchema(ehrExtractContent);
157+
154158
ehrExtractStatusService.saveEhrExtractMessageId(structuredTaskDefinition.getConversationId(),
155159
ehrExtractTemplateParameters.getEhrExtractId());
156160

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 validateXmlAgainstSchema_shouldThrow_whenInvalidXml_andRedactionInteraction() {
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 validateXmlAgainstSchema_shouldThrow_whenInvalidXml_andNonRedactionInteraction() {
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/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());

0 commit comments

Comments
 (0)