Skip to content

Commit 207df5b

Browse files
Add Schema validation to transform tool
* Add ErrorHandler for XML/XSD schema validation errors. * Add Schema validator which retrieves the configured redactions context and validates against the corresponding schema. * Add functionality to output the validation errors in a log file. * Inject and use schema validation when running the transformation process. * Update developer information.
1 parent d347866 commit 207df5b

File tree

4 files changed

+143
-3
lines changed

4 files changed

+143
-3
lines changed

nhs-england-developer-information.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ We provide a mock MHS adaptor for local development and testing.
191191

192192
### How to transform arbitrary json ASR payload files
193193

194-
This is an interoperability testing tool to transform arbitrary/ad-hoc json ASR payloads and access the outputs.
194+
This is an interoperability testing tool to transform arbitrary/ad-hoc json ASR payloads, access the outputs and validate the produced XML against the relevant schema.
195195

196196
1. Navigate to the input folder and place all Json files to convert here.
197197
`integration-adaptor-gp2gp/transformJsonToXml/input/`
@@ -203,9 +203,11 @@ This is an interoperability testing tool to transform arbitrary/ad-hoc json ASR
203203
./TransformJsonToXml.sh
204204
```
205205

206-
3. The Converted .Xml files will be located in the output folder.
206+
3. The converted XML files will be located in the output folder.
207207
`integration-adaptor-gp2gp/transformJsonToXml/output/`
208208

209+
4. Any schema validation errors will be located with the extension `.validation-errors.log` and will be located in the same output folder as the converted XML files.
210+
209211
## Troubleshooting
210212

211213
### "Invalid source release 17" error

service/src/main/java/uk/nhs/adaptors/gp2gp/transformJsonToXmlTool/TransformJsonToXml.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
matchIfMissing = false)
4949
@Component
5050
public class TransformJsonToXml implements CommandLineRunner {
51-
5251
private static final String JSON_FILE_INPUT_PATH =
5352
Paths.get("src/").toFile().getAbsoluteFile().getAbsolutePath() + "/../../transformJsonToXml/input/";
5453
private static final String XML_OUTPUT_PATH =
@@ -57,6 +56,7 @@ public class TransformJsonToXml implements CommandLineRunner {
5756
private final MessageContext messageContext;
5857
private final OutputMessageWrapperMapper outputMessageWrapperMapper;
5958
private final EhrExtractMapper ehrExtractMapper;
59+
private final uk.nhs.adaptors.gp2gp.transformjsontoxmltool.XmlSchemaValidator xmlSchemaValidator;
6060

6161
public static void main(String[] args) {
6262
SpringApplication.run(TransformJsonToXml.class, args).close();
@@ -68,6 +68,7 @@ public void run(String... args) {
6868
getFiles().forEach(file -> {
6969
String xmlResult = mapJsonToXml(file.getJsonFileInput());
7070
writeToFile(xmlResult, file.getJsonFileName());
71+
xmlSchemaValidator.validateOutputToXmlSchema(file.getJsonFileName(), xmlResult);
7172
});
7273
} catch (NHSNumberNotFound | UnreadableJsonFileException | NoJsonFileFound | Hl7TranslatedResponseError e) {
7374
LOGGER.error("error: " + e.getMessage());
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package uk.nhs.adaptors.gp2gp.transformjsontoxmltool;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.apache.commons.io.FilenameUtils;
6+
import org.springframework.stereotype.Component;
7+
import org.xml.sax.SAXException;
8+
import org.xml.sax.SAXParseException;
9+
import uk.nhs.adaptors.gp2gp.common.configuration.RedactionsContext;
10+
11+
import javax.xml.XMLConstants;
12+
import javax.xml.transform.stream.StreamSource;
13+
import javax.xml.validation.SchemaFactory;
14+
import javax.xml.validation.Validator;
15+
import java.io.BufferedWriter;
16+
import java.io.File;
17+
import java.io.FileWriter;
18+
import java.io.IOException;
19+
import java.io.StringReader;
20+
import java.nio.charset.StandardCharsets;
21+
import java.nio.file.Paths;
22+
23+
@Component
24+
@Slf4j
25+
@RequiredArgsConstructor()
26+
public class XmlSchemaValidator {
27+
private final RedactionsContext redactionsContext;
28+
29+
private static final String SCHEMA_PATH = "../service/src/test/resources/mim/Schemas/";
30+
private static final String RCMR_IN030000UK06_SCHEMA_PATH = SCHEMA_PATH + RedactionsContext.NON_REDACTION_INTERACTION_ID + ".xsd";
31+
private static final String RCMR_IN030000UK07_SCHEMA_PATH = SCHEMA_PATH + RedactionsContext.REDACTION_INTERACTION_ID + ".xsd";
32+
private static final String OUTPUT_PATH =
33+
Paths.get("src/").toFile().getAbsoluteFile().getAbsolutePath() + "/../../transformJsonToXml/output/";
34+
35+
public void validateOutputToXmlSchema(String filename, String xmlResult) {
36+
LOGGER.info("Validating {} against {} schema", filename, RedactionsContext.REDACTION_INTERACTION_ID);
37+
38+
var xsdErrorHandler = new uk.nhs.adaptors.gp2gp.transformjsontoxmltool.XsdErrorHandler();
39+
Validator xmlValidator;
40+
41+
try {
42+
xmlValidator = getXmlValidator(xsdErrorHandler);
43+
} catch (SAXException e) {
44+
LOGGER.info("Could not load schema file for {} context.", RedactionsContext.REDACTION_INTERACTION_ID);
45+
return;
46+
}
47+
48+
try {
49+
var xmlResultSource = new StreamSource(new StringReader(xmlResult));
50+
xmlValidator.validate(xmlResultSource);
51+
} catch (SAXParseException parseException) {
52+
LOGGER.info("Failed to validate {} against {} schema", filename, RedactionsContext.REDACTION_INTERACTION_ID);
53+
writeValidationExceptionsToFile(xsdErrorHandler, filename);
54+
} catch (IOException e) {
55+
LOGGER.info("Could not read from stream source for produced XML for {}", filename);
56+
return;
57+
} catch (Exception e) {
58+
throw new RuntimeException(e);
59+
}
60+
61+
if (!xsdErrorHandler.isValid()) {
62+
LOGGER.info("Failed to validate {} against {} schema", filename, RedactionsContext.REDACTION_INTERACTION_ID);
63+
writeValidationExceptionsToFile(xsdErrorHandler, filename);
64+
return;
65+
}
66+
67+
LOGGER.info("Successfully validated {} against {} schema", filename, RedactionsContext.REDACTION_INTERACTION_ID);
68+
}
69+
70+
private void writeValidationExceptionsToFile(
71+
uk.nhs.adaptors.gp2gp.transformjsontoxmltool.XsdErrorHandler xsdErrorHandler,
72+
String fileName
73+
) {
74+
String outputFileName = FilenameUtils.removeExtension(fileName) + ".validation-errors.log";
75+
try (BufferedWriter writer = new BufferedWriter(new FileWriter(OUTPUT_PATH + outputFileName, StandardCharsets.UTF_8))) {
76+
for (SAXParseException e : xsdErrorHandler.getExceptions()) {
77+
var message = String.format("[%d:%d] %s", e.getLineNumber(), e.getColumnNumber(), e.getMessage());
78+
writer.write(message);
79+
writer.newLine();
80+
}
81+
LOGGER.info("Validation errors written to {}", outputFileName);
82+
} catch (IOException e) {
83+
LOGGER.error("Could not write validation errors to {}", outputFileName, e);
84+
}
85+
}
86+
87+
private Validator getXmlValidator(uk.nhs.adaptors.gp2gp.transformjsontoxmltool.XsdErrorHandler xsdErrorHandler) throws SAXException {
88+
var schemaPath = RedactionsContext.REDACTION_INTERACTION_ID.equals(redactionsContext.ehrExtractInteractionId())
89+
? RCMR_IN030000UK07_SCHEMA_PATH
90+
: RCMR_IN030000UK06_SCHEMA_PATH;
91+
92+
var schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
93+
var schemaFileStream = new StreamSource(new File(schemaPath));
94+
var schema = schemaFactory.newSchema(schemaFileStream);
95+
var validator = schema.newValidator();
96+
97+
validator.setErrorHandler(xsdErrorHandler);
98+
99+
return validator;
100+
}
101+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package uk.nhs.adaptors.gp2gp.transformjsontoxmltool;
2+
3+
import lombok.Getter;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.xml.sax.ErrorHandler;
6+
import org.xml.sax.SAXParseException;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
11+
@Slf4j
12+
@Getter
13+
public class XsdErrorHandler implements ErrorHandler {
14+
15+
private final List<SAXParseException> exceptions = new ArrayList<>();
16+
17+
public boolean isValid() {
18+
return exceptions.isEmpty();
19+
}
20+
21+
@Override
22+
public void warning(SAXParseException exception) {
23+
exceptions.add(exception);
24+
}
25+
26+
@Override
27+
public void error(SAXParseException exception) {
28+
exceptions.add(exception);
29+
}
30+
31+
@Override
32+
public void fatalError(SAXParseException exception) throws SAXParseException {
33+
exceptions.add(exception);
34+
throw exception;
35+
}
36+
}

0 commit comments

Comments
 (0)