From cfd03f89a0c81b00f8cd56582761e4dbdfc6d700 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 27 Apr 2020 15:42:45 -0400 Subject: [PATCH] Use a SimpleJsonSerializer in the FileSplitter (#3262) * Use a SimpleJsonSerializer in the FileSplitter * To avoid extra dependency for Jackson when we serialize `FileSplitter.FileMarker` to JSON, use a `SimpleJsonSerializer` instead. * Fix `SimpleJsonSerializer` to escape a `\` symbol from property values since it is used for quoting string values in the final JSON * * Document the change --- .../json/SimpleJsonSerializer.java | 3 ++- .../file/splitter/FileSplitter.java | 22 ++++++------------- .../file/splitter/FileSplitterTests.java | 6 ++++- src/reference/asciidoc/file.adoc | 4 ++-- src/reference/asciidoc/whats-new.adoc | 7 ++++++ 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/SimpleJsonSerializer.java b/spring-integration-core/src/main/java/org/springframework/integration/json/SimpleJsonSerializer.java index fe1eecfbc7..8547b7ed8d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/json/SimpleJsonSerializer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/SimpleJsonSerializer.java @@ -22,6 +22,7 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import java.util.regex.Matcher; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -99,7 +100,7 @@ public final class SimpleJsonSerializer { return result.toString(); } else { - return "\"" + (result == null ? "null" : result.toString()) + "\""; + return "\"" + (result == null ? "null" : Matcher.quoteReplacement(result.toString())) + "\""; } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/splitter/FileSplitter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/splitter/FileSplitter.java index 9e43cc6141..6b79875aee 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/splitter/FileSplitter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/splitter/FileSplitter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2019 the original author or authors. + * Copyright 2015-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,11 +37,11 @@ import java.util.NoSuchElementException; import org.springframework.integration.StaticMessageHeaderAccessor; import org.springframework.integration.file.FileHeaders; import org.springframework.integration.file.splitter.FileSplitter.FileMarker.Mark; +import org.springframework.integration.json.SimpleJsonSerializer; import org.springframework.integration.splitter.AbstractMessageSplitter; import org.springframework.integration.support.AbstractIntegrationMessageBuilder; -import org.springframework.integration.support.json.JsonObjectMapper; -import org.springframework.integration.support.json.JsonObjectMapperProvider; import org.springframework.integration.util.CloseableIterator; +import org.springframework.lang.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandlingException; import org.springframework.util.Assert; @@ -75,15 +75,13 @@ import org.springframework.util.StringUtils; */ public class FileSplitter extends AbstractMessageSplitter { - private static final JsonObjectMapper OBJECT_MAPPER = - JsonObjectMapperProvider.jsonAvailable() ? JsonObjectMapperProvider.newInstance() : null; - private final boolean returnIterator; private final boolean markers; private final boolean markersJson; + @Nullable private Charset charset; private String firstLineHeaderName; @@ -129,9 +127,7 @@ public class FileSplitter extends AbstractMessageSplitter { * @param iterator true to return an iterator, false to return a list of lines. * @param markers true to emit start of file/end of file marker messages before/after * the data. - * @param markersJson when true, markers are represented as JSON - requires a - * supported JSON implementation on the classpath. See - * {@link JsonObjectMapperProvider} for supported implementations. + * @param markersJson when true, markers are represented as JSON. * @since 4.2.7 */ public FileSplitter(boolean iterator, boolean markers, boolean markersJson) { @@ -139,9 +135,6 @@ public class FileSplitter extends AbstractMessageSplitter { this.markers = markers; if (markers) { setApplySequence(false); - if (markersJson) { - Assert.notNull(OBJECT_MAPPER, "'markersJson' requires an object mapper"); - } } this.markersJson = markersJson; } @@ -151,7 +144,7 @@ public class FileSplitter extends AbstractMessageSplitter { * charset is required. * @param charset the charset. */ - public void setCharset(Charset charset) { + public void setCharset(@Nullable Charset charset) { this.charset = charset; } @@ -410,7 +403,7 @@ public class FileSplitter extends AbstractMessageSplitter { Object payload; if (FileSplitter.this.markersJson) { try { - payload = OBJECT_MAPPER.toJson(fileMarker); + payload = SimpleJsonSerializer.toJson(fileMarker); } catch (Exception e) { throw new MessageHandlingException(this.message, "Failed to convert marker to JSON", e); @@ -489,7 +482,6 @@ public class FileSplitter extends AbstractMessageSplitter { } } - } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/splitter/FileSplitterTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/splitter/FileSplitterTests.java index 1356bd725d..bc3d29f869 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/splitter/FileSplitterTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/splitter/FileSplitterTests.java @@ -31,6 +31,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Date; +import java.util.List; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -50,6 +51,7 @@ import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.file.FileHeaders; import org.springframework.integration.file.splitter.FileSplitter.FileMarker; import org.springframework.integration.handler.advice.IdempotentReceiverInterceptor; +import org.springframework.integration.json.JsonPathUtils; import org.springframework.integration.metadata.ConcurrentMetadataStore; import org.springframework.integration.metadata.SimpleMetadataStore; import org.springframework.integration.selector.MetadataStoreSelector; @@ -248,6 +250,7 @@ public class FileSplitterTests { } @Test + @SuppressWarnings("unchecked") void testMarkersJson() throws Exception { JsonObjectMapper objectMapper = JsonObjectMapperProvider.newInstance(); QueueChannel outputChannel = new QueueChannel(); @@ -260,7 +263,8 @@ public class FileSplitterTests { assertThat(received.getHeaders().get(FileHeaders.MARKER)).isEqualTo("START"); assertThat(received.getPayload()).isInstanceOf(String.class); String payload = (String) received.getPayload(); - assertThat(payload).contains("\"mark\":\"START\",\"lineCount\":0"); + assertThat((List) JsonPathUtils.evaluate(payload, "$..mark")).hasSize(1).contains("START"); + assertThat((List) JsonPathUtils.evaluate(payload, "$..lineCount")).hasSize(1).contains(0); FileMarker fileMarker = objectMapper.fromJson(payload, FileSplitter.FileMarker.class); assertThat(fileMarker.getMark()).isEqualTo(FileSplitter.FileMarker.Mark.START); assertThat(fileMarker.getFilePath()).isEqualTo(file.getAbsolutePath()); diff --git a/src/reference/asciidoc/file.adoc b/src/reference/asciidoc/file.adoc index e9ba68c9d3..75b34a4642 100644 --- a/src/reference/asciidoc/file.adoc +++ b/src/reference/asciidoc/file.adoc @@ -1016,7 +1016,7 @@ The default is `false`. When `true`, `apply-sequence` is `false` by default. See also `markers-json` (the next attribute). <4> When `markers` is true, set this to `true` to have the `FileMarker` objects be converted to a JSON string. -Requires a supported JSON processor library (e.g. Jackson) on the classpath. +(Uses a `SimpleJsonSerializer` underneath). <5> Set to `false` to disable the inclusion of `sequenceSize` and `sequenceNumber` headers in messages. The default is `true`, unless `markers` is `true`. When `true` and `markers` is `true`, the markers are included in the sequencing. @@ -1050,7 +1050,7 @@ public FileSplitter(boolean iterator, boolean markers, boolean markersJson) ---- ==== -When `markersJson` is true, the markers are represented as a JSON string, as long as a suitable JSON processor library (e.g Jackson) is on the classpath. +When `markersJson` is true, the markers are represented as a JSON string (using a `SimpleJsonSerializer`). Version 5.0 introduced the `firstLineAsHeader` option to specify that the first line of content is a header (such as column names in a CSV file). The argument passed to this property is the header name under which the first line is carried as a header in the messages emitted for the remaining lines. diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 0906e4a9fe..99e3601188 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -158,3 +158,10 @@ See <<./mqtt.adoc#mqtt-events,MQTT Events>>. The `FileTransferringMessageHandler` (for FTP and SFTP, for example) in addition to `File`, `byte[]`, `String` and `InputStream` now also supports an `org.springframework.core.io.Resource`. See <<./sftp.adoc#sftp,SFTP Support>> and <<./ftp.adoc#ftp,FTP Support>> for more information. + +[[x5.3-file]] +=== File Changes + +The `FileSplitter` doesn't require a Jackson processor (or similar) dependency any more for the `markersJson` mode. +It uses a `SimpleJsonSerializer` for a straightforward string representation of the `FileSplitter.FileMarker` instances. +See <<./file.adoc#file-splitter,FileSplitter>> for more information.