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
This commit is contained in:
Artem Bilan
2020-04-27 15:42:45 -04:00
committed by GitHub
parent 620b9bf52d
commit cfd03f89a0
5 changed files with 23 additions and 19 deletions

View File

@@ -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())) + "\"";
}
}

View File

@@ -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 {
}
}
}
}

View File

@@ -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<String>) JsonPathUtils.evaluate(payload, "$..mark")).hasSize(1).contains("START");
assertThat((List<Integer>) 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());

View File

@@ -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.

View File

@@ -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.