Skip to content

Commit 68bb034

Browse files
ORybak5adrianclay
andauthored
NIAD-3228: removing Jackson limit of maximum size of data that it can process (#980)
* removing Jackson limit of maximum size of data that it can process * changelog * covering ObjecMapper bean class with unit tests * checkstyle * adding more tests to confirm that objectMapper has unlimited readability * configure objectMapper to ignore unknown properties * adding javaTimeModule that is required by Jackson * narrow down the scope where unknown Json property is more tolerated * making Jackson mapper more tolerable * removing data limit in Spring-instantiated objectMapper * additional tests to cover setup in ObjectMapperPostProcessorConfig * small code optimization * removing an unnecessary dependency * making objectMapper to be instantiated by Spring DI * Move dependency construction into separate class Using a separate bean construction method is less surprising than a Bean Post Processor. * Remove 20MB file from version control, and generate it as part of test instead. --------- Co-authored-by: Adrian Clay <[email protected]>
1 parent b1faad8 commit 68bb034

File tree

11 files changed

+85
-12
lines changed

11 files changed

+85
-12
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3030
* When mapping `Immunizations` which contain a `NOPAT` `meta.security` tag, the resultant XML for that resource
3131
will contain a `NOPAT` `confidentialityCode` element.
3232

33+
### Fixed
34+
* Removed the 20 MB data processing limit to enable the GP2GP Adaptor to handle larger documents.
35+
3336
## [2.1.3] - 2014-10-25
3437

3538
### Fixed

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/status/model/EhrStatusRequest.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.status.model;
22

33
import java.time.Instant;
4-
54
import lombok.Builder;
65
import lombok.Data;
76

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
@Component
1919
@AllArgsConstructor(onConstructor = @__(@Autowired))
2020
public class DocumentToMHSTranslator {
21-
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
2221

22+
private final ObjectMapper objectMapper;
2323
private final EhrDocumentMapper ehrDocumentMapper;
2424

2525
public String translateGpcResponseToMhsOutboundRequestData(
@@ -66,6 +66,6 @@ private String prepareOutboundMessage(
6666
.attachments(attachments)
6767
.build();
6868

69-
return OBJECT_MAPPER.writeValueAsString(outboundMessage);
69+
return objectMapper.writeValueAsString(outboundMessage);
7070
}
7171
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package uk.nhs.adaptors.gp2gp.common.configuration;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import org.junit.jupiter.api.Test;
5+
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
public class ObjectMapperBeanTest {
10+
11+
@Test
12+
public void createsObjectMapperWithUnlimitedStringLengthConstraint() {
13+
// The way we use JSON to store serialized XML and base64 encoded attachments means that the size of individual
14+
// strings can get very large. We've set the max string size below to be 2GB.
15+
// Ideal solution would be to avoid using JSON to store very large strings of data in the first place,
16+
// and if we must then not use the ObjectMapper to extract that information.
17+
ObjectMapperBean objectMapperBean = new ObjectMapperBean();
18+
ObjectMapper objectMapper = objectMapperBean.objectMapper(new Jackson2ObjectMapperBuilder());
19+
20+
assertThat(objectMapper.getFactory().streamReadConstraints().getMaxStringLength()).isEqualTo(Integer.MAX_VALUE);
21+
}
22+
}

service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/SendDocumentTaskExecutorTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,7 @@ public void setup() {
202202
new ObjectMapper(),
203203
this.detectDocumentsSentService,
204204
gp2gpConfiguration,
205-
new EhrDocumentMapper(new TimestampService(), new RandomIdGeneratorServiceStub()),
206-
new TimestampService()
205+
new EhrDocumentMapper(new TimestampService(), new RandomIdGeneratorServiceStub())
207206
);
208207
}
209208

0 commit comments

Comments
 (0)