Skip to content

Commit b978804

Browse files
authored
Merge branch 'main' into NIAD-3217
2 parents 71cdc9f + 0b3c27c commit b978804

21 files changed

+376
-31
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## Fixed
10+
11+
* When mapping an `Observation` related to a diagnostic report which does not contain a `code` element, the adaptor will
12+
now throw an error reporting that an observation requires a code element and provide the affected resource ID.
13+
* When mapping a `valueQuantity` contained in an `Observation`, the produced XML `<value>` element now correctly escapes
14+
any contained XML characters.
15+
916
## Added
1017

1118
* When mapping a `DocumentReference` which contains a `NOPAT` `meta.security` or `NOPAT` `securityLabel` tag the resultant XML for that resource
@@ -25,6 +32,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2532
* When mapping `Immunizations` which contain a `NOPAT` `meta.security` tag, the resultant XML for that resource
2633
will contain a `NOPAT` `confidentialityCode` element.
2734

35+
### Fixed
36+
* Removed the 20 MB data processing limit to enable the GP2GP Adaptor to handle larger documents.
37+
38+
## [2.1.3] - 2014-10-25
39+
2840
### Fixed
2941

3042
* Fix a malformed XML GP2GP message created when mapping a `MedicationRequest` with `intent` of `plan` and is stopped,

mock-mhs-adaptor/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ dependencies {
2020
implementation 'org.springframework.boot:spring-boot-starter-web'
2121
implementation 'org.springframework:spring-jms'
2222
implementation 'org.apache.qpid:qpid-jms-client:2.5.0'
23-
implementation 'org.apache.commons:commons-lang3:3.16.0'
23+
implementation 'org.apache.commons:commons-lang3:3.17.0'
2424
testImplementation 'org.springframework.boot:spring-boot-starter-test'
2525
}
2626

service/build.gradle

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
plugins {
2-
id 'org.springframework.boot' version '3.3.4'
2+
id 'org.springframework.boot' version '3.3.5'
33
id 'io.spring.dependency-management' version '1.1.6'
44
id 'java'
55
id "checkstyle"
@@ -60,15 +60,15 @@ dependencies {
6060
implementation 'org.apache.tika:tika-core:3.0.0'
6161

6262
// Fhir
63-
implementation 'ca.uhn.hapi.fhir:hapi-fhir-structures-dstu3:7.4.3'
63+
implementation 'ca.uhn.hapi.fhir:hapi-fhir-structures-dstu3:7.4.5'
6464

6565
// Test
6666
testImplementation 'org.springframework.boot:spring-boot-starter-test'
6767
testImplementation "org.assertj:assertj-core:3.26.3"
68-
testImplementation 'org.testcontainers:testcontainers:1.20.2'
68+
testImplementation 'org.testcontainers:testcontainers:1.20.3'
6969
testImplementation 'org.awaitility:awaitility:4.2.2'
7070
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.3'
71-
testImplementation 'org.wiremock:wiremock-standalone:3.9.1'
71+
testImplementation 'org.wiremock:wiremock-standalone:3.9.2'
7272
testImplementation 'com.squareup.okhttp3:okhttp:4.12.0'
7373
testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
7474

@@ -160,6 +160,10 @@ pitest {
160160
threads = project.getGradle().getStartParameter().getMaxWorkerCount()
161161
}
162162

163+
pitestGithub {
164+
deleteOldSummaries = true
165+
}
166+
163167
bootJar {
164168
exclude("**/TransformJsonToXml*")
165169
}

service/src/intTest/java/uk/nhs/adaptors/gp2gp/ehr/SendDocumentComponentTest.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.assertj.core.api.Assertions.assertThat;
44
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
5+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
56
import static org.mockito.ArgumentMatchers.any;
67
import static org.mockito.Mockito.doThrow;
78

@@ -10,6 +11,9 @@
1011
import java.io.IOException;
1112
import java.io.InputStream;
1213

14+
import com.fasterxml.jackson.databind.ObjectMapper;
15+
import org.apache.commons.io.IOUtils;
16+
import org.jetbrains.annotations.NotNull;
1317
import org.junit.jupiter.api.Test;
1418
import org.junit.jupiter.api.extension.ExtendWith;
1519
import org.junit.runner.RunWith;
@@ -21,10 +25,12 @@
2125
import org.springframework.test.context.junit4.SpringRunner;
2226

2327
import uk.nhs.adaptors.gp2gp.common.storage.LocalMockConnector;
28+
import uk.nhs.adaptors.gp2gp.common.storage.StorageDataWrapper;
2429
import uk.nhs.adaptors.gp2gp.ehr.model.EhrExtractStatus;
2530
import uk.nhs.adaptors.gp2gp.mhs.MhsClient;
2631
import uk.nhs.adaptors.gp2gp.mhs.exception.MhsConnectionException;
2732
import uk.nhs.adaptors.gp2gp.mhs.exception.MhsServerErrorException;
33+
import uk.nhs.adaptors.gp2gp.mhs.model.OutboundMessage;
2834
import uk.nhs.adaptors.gp2gp.testcontainers.ActiveMQExtension;
2935
import uk.nhs.adaptors.gp2gp.testcontainers.MongoDBExtension;
3036

@@ -34,6 +40,7 @@
3440
@DirtiesContext
3541
public class SendDocumentComponentTest {
3642
private static final String DOCUMENT_NAME = "some-conversation-id/document-name.json";
43+
public static final String OUTBOUND_MESSAGE_JSON = "src/intTest/resources/outboundMessage.json";
3744

3845
@MockBean
3946
private MhsClient mhsClient;
@@ -47,6 +54,30 @@ public class SendDocumentComponentTest {
4754
@Autowired
4855
private LocalMockConnector localMockConnector;
4956

57+
@SuppressWarnings("checkstyle:MagicNumber")
58+
@Test
59+
public void When_SendDocumentTaskRunsWithOver20MbPayloadMessage_Expect_NoException() throws IOException {
60+
InputStream inputStream = createInputStreamWithAttachmentOfSize(22 * 1024 * 1024);
61+
localMockConnector.uploadToStorage(inputStream, inputStream.available(), DOCUMENT_NAME);
62+
var ehrExtractStatus = EhrExtractStatusTestUtils.prepareEhrExtractStatus();
63+
ehrExtractStatusRepository.save(ehrExtractStatus);
64+
65+
var sendDocumentTaskDefinition = prepareTaskDefinition(ehrExtractStatus);
66+
67+
assertDoesNotThrow(() -> sendDocumentTaskExecutor.execute(sendDocumentTaskDefinition));
68+
}
69+
70+
private @NotNull InputStream createInputStreamWithAttachmentOfSize(int sizeOfAttachmentInBytes) throws IOException {
71+
var inputStream = readMessageAsInputStream();
72+
ObjectMapper mapper = new ObjectMapper();
73+
StorageDataWrapper wrapper = mapper.readValue(inputStream, StorageDataWrapper.class);
74+
OutboundMessage outboundMessage = mapper.readValue(wrapper.getData(), OutboundMessage.class);
75+
outboundMessage.getAttachments().getFirst().setPayload("1".repeat(sizeOfAttachmentInBytes));
76+
wrapper.setData(mapper.writeValueAsString(outboundMessage));
77+
78+
return IOUtils.toInputStream(mapper.writeValueAsString(wrapper), "UTF-8");
79+
}
80+
5081
@Test
5182
public void When_SendDocumentTaskRunsTwice_Expect_DatabaseOverwritesEhrExtractStatus() throws IOException {
5283
var inputStream = readMessageAsInputStream();
@@ -106,7 +137,7 @@ public void When_SendDocumentTask_WithMhsServerFailureException_Expect_Exception
106137
}
107138

108139
private InputStream readMessageAsInputStream() throws IOException {
109-
File file = new File("src/intTest/resources/outboundMessage.json");
140+
File file = new File(OUTBOUND_MESSAGE_JSON);
110141
return new FileInputStream(file);
111142
}
112143

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package uk.nhs.adaptors.gp2gp.common.configuration;
2+
3+
import com.fasterxml.jackson.core.StreamReadConstraints;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
8+
9+
@Configuration
10+
public class ObjectMapperBean {
11+
@Bean
12+
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
13+
ObjectMapper objectMapper = builder.build();
14+
objectMapper.getFactory().setStreamReadConstraints(
15+
StreamReadConstraints.builder().maxStringLength(Integer.MAX_VALUE).build()
16+
);
17+
return objectMapper;
18+
}
19+
}

service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/SendAcknowledgementTaskDefinition.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package uk.nhs.adaptors.gp2gp.ehr;
22

33
import static uk.nhs.adaptors.gp2gp.common.task.TaskType.SEND_ACKNOWLEDGEMENT;
4-
54
import lombok.EqualsAndHashCode;
65
import lombok.Getter;
76
import lombok.experimental.SuperBuilder;

service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/SendDocumentTaskExecutor.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import org.springframework.stereotype.Service;
1313
import uk.nhs.adaptors.gp2gp.common.configuration.Gp2gpConfiguration;
1414
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
15-
import uk.nhs.adaptors.gp2gp.common.service.TimestampService;
1615
import uk.nhs.adaptors.gp2gp.common.storage.StorageConnectorService;
1716
import uk.nhs.adaptors.gp2gp.common.task.TaskExecutor;
1817
import uk.nhs.adaptors.gp2gp.ehr.model.EhrExtractStatus;
@@ -39,7 +38,6 @@ public class SendDocumentTaskExecutor implements TaskExecutor<SendDocumentTaskDe
3938
private final DetectDocumentsSentService detectDocumentsSentService;
4039
private final Gp2gpConfiguration gp2gpConfiguration;
4140
private final EhrDocumentMapper ehrDocumentMapper;
42-
private final TimestampService timestampService;
4341

4442
@Override
4543
public Class<SendDocumentTaskDefinition> getTaskType() {

service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/SendEhrExtractCoreTaskExecutor.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class SendEhrExtractCoreTaskExecutor implements TaskExecutor<SendEhrExtra
4848
private final RandomIdGeneratorService randomIdGeneratorService;
4949
private final TimestampService timestampService;
5050
private final EhrDocumentMapper ehrDocumentMapper;
51+
private final ObjectMapper objectMapper;
5152

5253
@Override
5354
public Class<SendEhrExtractCoreTaskDefinition> getTaskType() {
@@ -67,7 +68,7 @@ public void execute(SendEhrExtractCoreTaskDefinition sendEhrExtractCoreTaskDefin
6768
String outboundEhrExtract = replacePlaceholders(documentObjectNameAndSize, storageDataWrapper.getData());
6869

6970
LOGGER.info("Checking EHR Extract size");
70-
final var outboundMessage = new ObjectMapper().readValue(outboundEhrExtract, OutboundMessage.class);
71+
final var outboundMessage = objectMapper.readValue(outboundEhrExtract, OutboundMessage.class);
7172
if (getBytesLengthOfString(outboundMessage.getPayload()) > gp2gpConfiguration.getLargeEhrExtractThreshold()) {
7273
LOGGER.info("EHR extract IS large");
7374
outboundEhrExtract = compressEhrExtractAndReplacePayloadWithSkeleton(sendEhrExtractCoreTaskDefinition, outboundMessage);
@@ -109,7 +110,7 @@ private String compressEhrExtractAndReplacePayloadWithSkeleton(
109110
referenceCompressedEhrExtractDocumentAsAttachmentInOutboundMessage(
110111
outboundMessage, documentId, messageId, fileName, compressedEhrExtract.length());
111112

112-
return new ObjectMapper().writeValueAsString(outboundMessage);
113+
return objectMapper.writeValueAsString(outboundMessage);
113114
}
114115

115116
private void referenceCompressedEhrExtractDocumentAsAttachmentInOutboundMessage(
@@ -145,7 +146,7 @@ private void uploadCompressedEhrExtractToStorageWrapper(
145146
String taskId,
146147
String fileName) throws JsonProcessingException {
147148

148-
String data = new ObjectMapper().writeValueAsString(
149+
String data = objectMapper.writeValueAsString(
149150
OutboundMessage.builder()
150151
.payload(
151152
ehrDocumentMapper.generateMhsPayload(

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

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package uk.nhs.adaptors.gp2gp.ehr.mapper;
22

33
import org.apache.commons.lang3.StringUtils;
4+
import org.apache.commons.text.StringEscapeUtils;
45
import org.hl7.fhir.dstu3.model.BooleanType;
56
import org.hl7.fhir.dstu3.model.Extension;
67
import org.hl7.fhir.dstu3.model.Quantity;
@@ -52,12 +53,15 @@ public static String processQuantity(Quantity valueQuantity) {
5253
}
5354

5455
private static String getPhysicalQuantityXml(Quantity valueQuantity) {
56+
var escapedCode = StringEscapeUtils.escapeXml11(valueQuantity.getCode());
57+
var escapedUnit = StringEscapeUtils.escapeXml11(valueQuantity.getUnit());
58+
5559
if (UNITS_OF_MEASURE_SYSTEM.equals(valueQuantity.getSystem()) && valueQuantity.hasCode()) {
5660
return """
5761
<value xsi:type="PQ" value="%s" unit="%s" />"""
5862
.formatted(
5963
valueQuantity.getValue(),
60-
valueQuantity.getCode()
64+
escapedCode
6165
);
6266
}
6367
if (hasValidSystem(valueQuantity) && valueQuantity.hasCode() && valueQuantity.hasUnit()) {
@@ -68,9 +72,9 @@ private static String getPhysicalQuantityXml(Quantity valueQuantity) {
6872
.formatted(
6973
valueQuantity.getValue(),
7074
valueQuantity.getValue(),
71-
valueQuantity.getCode(),
75+
escapedCode,
7276
getSystemWithoutPrefix(valueQuantity.getSystem()),
73-
valueQuantity.getUnit()
77+
escapedUnit
7478
);
7579
}
7680
if (hasValidSystem(valueQuantity) && valueQuantity.hasCode()) {
@@ -81,7 +85,7 @@ private static String getPhysicalQuantityXml(Quantity valueQuantity) {
8185
.formatted(
8286
valueQuantity.getValue(),
8387
valueQuantity.getValue(),
84-
valueQuantity.getCode(),
88+
escapedCode,
8589
getSystemWithoutPrefix(valueQuantity.getSystem())
8690
);
8791
}
@@ -95,7 +99,7 @@ private static String getPhysicalQuantityXml(Quantity valueQuantity) {
9599
.formatted(
96100
valueQuantity.getValue(),
97101
valueQuantity.getValue(),
98-
valueQuantity.getUnit()
102+
escapedUnit
99103
);
100104
}
101105

@@ -105,6 +109,9 @@ private static String getPhysicalQuantityXml(Quantity valueQuantity) {
105109
}
106110

107111
private static String getPhysicalQuantityIntervalXml(Quantity valueQuantity) {
112+
var escapedCode = StringEscapeUtils.escapeXml11(valueQuantity.getCode());
113+
var escapedUnit = StringEscapeUtils.escapeXml11(valueQuantity.getUnit());
114+
108115
if (UNITS_OF_MEASURE_SYSTEM.equals(valueQuantity.getSystem()) && valueQuantity.hasCode()) {
109116
return """
110117
<value xsi:type="IVL_PQ">%n\
@@ -113,7 +120,7 @@ private static String getPhysicalQuantityIntervalXml(Quantity valueQuantity) {
113120
.formatted(
114121
getHighOrLow(valueQuantity),
115122
valueQuantity.getValue(),
116-
valueQuantity.getCode(),
123+
escapedCode,
117124
isInclusive(valueQuantity)
118125
);
119126
}
@@ -129,9 +136,9 @@ private static String getPhysicalQuantityIntervalXml(Quantity valueQuantity) {
129136
valueQuantity.getValue(),
130137
isInclusive(valueQuantity),
131138
valueQuantity.getValue(),
132-
valueQuantity.getCode(),
139+
escapedCode,
133140
getSystemWithoutPrefix(valueQuantity.getSystem()),
134-
valueQuantity.getUnit(),
141+
escapedUnit,
135142
getHighOrLow(valueQuantity)
136143
);
137144
}
@@ -147,7 +154,7 @@ private static String getPhysicalQuantityIntervalXml(Quantity valueQuantity) {
147154
valueQuantity.getValue(),
148155
isInclusive(valueQuantity),
149156
valueQuantity.getValue(),
150-
valueQuantity.getCode(),
157+
escapedCode,
151158
getSystemWithoutPrefix(valueQuantity.getSystem()),
152159
getHighOrLow(valueQuantity)
153160
);
@@ -166,7 +173,7 @@ private static String getPhysicalQuantityIntervalXml(Quantity valueQuantity) {
166173
valueQuantity.getValue(),
167174
isInclusive(valueQuantity),
168175
valueQuantity.getValue(),
169-
valueQuantity.getUnit(),
176+
escapedUnit,
170177
getHighOrLow(valueQuantity)
171178
);
172179
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ private String prepareCodeElement(Observation observation) {
488488
return codeableConceptCdMapper.mapCodeableConceptToCd(observation.getCode());
489489
}
490490

491-
return StringUtils.EMPTY;
491+
throw new EhrMapperException("%s must contain a code element.".formatted(observation.getId()));
492492
}
493493

494494
private CompoundStatementClassCode prepareClassCode(List<MultiStatementObservationHolder> derivedObservations) {

0 commit comments

Comments
 (0)