Skip to content

Commit 1639290

Browse files
authored
jackson stream read constraints: code-based defaults (#16854)
* Revert "Apply Jackson stream read constraints defaults at runtime (#16832)" This reverts commit cc608eb. * jackson stream read constraints: code-based defaults refactors stream read constraints to couple default values with their associated overrides, which allows us to have more descriptive logging that includes provenance of the value that has been applied.
1 parent dae7fd9 commit 1639290

File tree

4 files changed

+108
-71
lines changed

4 files changed

+108
-71
lines changed

config/jvm.options

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@
7171
# text values with sizes less than or equal to this limit will be treated as invalid.
7272
# This value should be higher than `logstash.jackson.stream-read-constraints.max-number-length`.
7373
# The jackson library defaults to 20000000 or 20MB, whereas Logstash defaults to 200MB or 200000000 characters.
74-
-Dlogstash.jackson.stream-read-constraints.max-string-length=200000000
74+
#-Dlogstash.jackson.stream-read-constraints.max-string-length=200000000
7575
#
7676
# Sets the maximum number length (in chars or bytes, depending on input context).
7777
# The jackson library defaults to 1000, whereas Logstash defaults to 10000.
78-
-Dlogstash.jackson.stream-read-constraints.max-number-length=10000
78+
#-Dlogstash.jackson.stream-read-constraints.max-number-length=10000
7979
#
8080
# Sets the maximum nesting depth. The depth is a count of objects and arrays that have not
8181
# been closed, `{` and `[` respectively.

logstash-core/src/main/java/org/logstash/jackson/StreamReadConstraintsUtil.java

+52-31
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@
55
import org.apache.logging.log4j.LogManager;
66
import org.apache.logging.log4j.Logger;
77

8-
import java.util.*;
8+
import javax.annotation.Nullable;
9+
import java.util.ArrayList;
10+
import java.util.Arrays;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.Objects;
14+
import java.util.Optional;
15+
import java.util.Properties;
16+
import java.util.Set;
917
import java.util.function.BiConsumer;
10-
import java.util.function.BiFunction;
11-
import java.util.function.Function;
1218
import java.util.stream.Collectors;
1319

1420
public class StreamReadConstraintsUtil {
@@ -18,38 +24,51 @@ public class StreamReadConstraintsUtil {
1824

1925
private StreamReadConstraints configuredStreamReadConstraints;
2026

21-
// Provide default values for Jackson constraints in the case they are
22-
// not specified in configuration file.
23-
private static final Map<Override, Integer> JACKSON_DEFAULTS = Map.of(
24-
Override.MAX_STRING_LENGTH, 200_000_000,
25-
Override.MAX_NUMBER_LENGTH, 10_000,
26-
Override.MAX_NESTING_DEPTH, 1_000
27-
);
28-
2927
enum Override {
30-
MAX_STRING_LENGTH(StreamReadConstraints.Builder::maxStringLength, StreamReadConstraints::getMaxStringLength),
31-
MAX_NUMBER_LENGTH(StreamReadConstraints.Builder::maxNumberLength, StreamReadConstraints::getMaxNumberLength),
32-
MAX_NESTING_DEPTH(StreamReadConstraints.Builder::maxNestingDepth, StreamReadConstraints::getMaxNestingDepth),
28+
MAX_STRING_LENGTH(200_000_000, StreamReadConstraints.Builder::maxStringLength, StreamReadConstraints::getMaxStringLength),
29+
MAX_NUMBER_LENGTH(10_000, StreamReadConstraints.Builder::maxNumberLength, StreamReadConstraints::getMaxNumberLength),
30+
MAX_NESTING_DEPTH(1_000, StreamReadConstraints.Builder::maxNestingDepth, StreamReadConstraints::getMaxNestingDepth),
3331
;
3432

3533
static final String PROP_PREFIX = "logstash.jackson.stream-read-constraints.";
3634

3735
final String propertyName;
36+
final int defaultValue;
3837
private final IntValueApplicator applicator;
3938
private final IntValueObserver observer;
4039

41-
Override(final IntValueApplicator applicator,
40+
Override(final int defaultValue,
41+
final IntValueApplicator applicator,
4242
final IntValueObserver observer) {
4343
this.propertyName = PROP_PREFIX + this.name().toLowerCase().replace('_', '-');
44+
this.defaultValue = defaultValue;
4445
this.applicator = applicator;
4546
this.observer = observer;
4647
}
4748

49+
int resolve(final Integer specifiedValue) {
50+
return Objects.requireNonNullElse(specifiedValue, defaultValue);
51+
}
52+
53+
void apply(final StreamReadConstraints.Builder constraintsBuilder,
54+
@Nullable final Integer specifiedValue) {
55+
this.applicator.applyIntValue(constraintsBuilder, resolve(specifiedValue));
56+
}
57+
58+
int observe(final StreamReadConstraints constraints) {
59+
return this.observer.observeIntValue(constraints);
60+
}
61+
4862
@FunctionalInterface
49-
interface IntValueObserver extends Function<StreamReadConstraints, Integer> {}
63+
interface IntValueObserver {
64+
int observeIntValue(final StreamReadConstraints constraints);
65+
}
5066

5167
@FunctionalInterface
52-
interface IntValueApplicator extends BiFunction<StreamReadConstraints.Builder, Integer, StreamReadConstraints.Builder> {}
68+
interface IntValueApplicator {
69+
@SuppressWarnings("UnusedReturnValue")
70+
StreamReadConstraints.Builder applyIntValue(final StreamReadConstraints.Builder constraintsBuilder, final int value);
71+
}
5372
}
5473

5574
/**
@@ -86,9 +105,7 @@ StreamReadConstraints get() {
86105
if (configuredStreamReadConstraints == null) {
87106
final StreamReadConstraints.Builder builder = StreamReadConstraints.defaults().rebuild();
88107

89-
// Apply the Jackson defaults first, then the overrides from config
90-
JACKSON_DEFAULTS.forEach((override, value) -> override.applicator.apply(builder, value));
91-
eachOverride((override, value) -> override.applicator.apply(builder, value));
108+
eachOverride((override, specifiedValue) -> override.apply(builder, specifiedValue));
92109

93110
this.configuredStreamReadConstraints = builder.build();
94111
}
@@ -106,11 +123,14 @@ public void validateIsGlobalDefault() {
106123
private void validate(final StreamReadConstraints streamReadConstraints) {
107124
final List<String> fatalIssues = new ArrayList<>();
108125
eachOverride((override, specifiedValue) -> {
109-
final Integer effectiveValue = override.observer.apply(streamReadConstraints);
110-
if (Objects.equals(specifiedValue, effectiveValue)) {
111-
logger.info("Jackson default value override `{}` configured to `{}`", override.propertyName, specifiedValue);
126+
final int effectiveValue = override.observe(streamReadConstraints);
127+
final int expectedValue = override.resolve(specifiedValue);
128+
129+
if (!Objects.equals(effectiveValue, expectedValue)) {
130+
fatalIssues.add(String.format("`%s` (expected: `%s`, actual: `%s`)", override.propertyName, expectedValue, effectiveValue));
112131
} else {
113-
fatalIssues.add(String.format("`%s` (expected: `%s`, actual: `%s`)", override.propertyName, specifiedValue, effectiveValue));
132+
final String reason = Objects.nonNull(specifiedValue) ? "system properties" : "logstash default";
133+
logger.info("Jackson default value override `{}` configured to `{}` ({})", override.propertyName, effectiveValue, reason);
114134
}
115135
});
116136
for (String unsupportedProperty : getUnsupportedProperties()) {
@@ -124,13 +144,14 @@ private void validate(final StreamReadConstraints streamReadConstraints) {
124144
void eachOverride(BiConsumer<Override,Integer> overrideIntegerBiConsumer) {
125145
for (Override override : Override.values()) {
126146
final String propValue = this.propertyOverrides.get(override.propertyName);
127-
if (propValue != null) {
128-
try {
129-
int intValue = Integer.parseInt(propValue);
130-
overrideIntegerBiConsumer.accept(override, intValue);
131-
} catch (IllegalArgumentException e) {
132-
throw new IllegalArgumentException(String.format("System property `%s` must be positive integer value. Received: `%s`", override.propertyName, propValue), e);
133-
}
147+
148+
try {
149+
final Integer explicitValue = Optional.ofNullable(propValue)
150+
.map(Integer::parseInt)
151+
.orElse(null);
152+
overrideIntegerBiConsumer.accept(override, explicitValue);
153+
} catch (IllegalArgumentException e) {
154+
throw new IllegalArgumentException(String.format("System property `%s` must be positive integer value. Received: `%s`", override.propertyName, propValue), e);
134155
}
135156
}
136157
}

logstash-core/src/test/java/org/logstash/ObjectMappersTest.java

+5
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,9 @@ public void testLog4jOMRegisterRubyBasicObjectSerializersFirst() {
4949
assertNotNull(found);
5050
assertTrue("RubyBasicObjectSerializer must be registered before others non-default serializers", found instanceof RubyBasicObjectSerializer);
5151
}
52+
53+
@Test
54+
public void testStreamReadConstraintsApplication() {
55+
ObjectMappers.CONFIGURED_STREAM_READ_CONSTRAINTS.validateIsGlobalDefault();
56+
}
5257
}

logstash-core/src/test/java/org/logstash/jackson/StreamReadConstraintsUtilTest.java

+49-38
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
import org.apache.logging.log4j.test.appender.ListAppender;
88
import org.junit.Before;
99
import org.junit.ClassRule;
10+
import org.junit.Rule;
1011
import org.junit.Test;
1112

1213
import java.util.Arrays;
1314
import java.util.List;
15+
import java.util.Objects;
1416
import java.util.Properties;
1517
import java.util.function.BiConsumer;
18+
1619
import org.apache.logging.log4j.Level;
20+
import org.logstash.ObjectMappers;
1721

1822
import static org.assertj.core.api.Assertions.*;
1923
import static org.logstash.jackson.StreamReadConstraintsUtil.Override.*;
@@ -23,13 +27,12 @@ public class StreamReadConstraintsUtilTest {
2327
@ClassRule
2428
public static final LoggerContextRule LOGGER_CONTEXT_RULE = new LoggerContextRule("log4j2-log-stream-read-constraints.xml");
2529

30+
@Rule
31+
public final DefaultsRestorer defaultsRestorer = new DefaultsRestorer();
32+
2633
private ListAppender listAppender;
2734
private Logger observedLogger;
2835

29-
private static final int DEFAULT_MAX_STRING_LENGTH = 200_000_000;
30-
private static final int DEFAULT_MAX_NUMBER_LENGTH = 10_000;
31-
private static final int DEFAULT_MAX_NESTING_DEPTH = 1_000;
32-
3336
@Before
3437
public void setUpLoggingListAppender() {
3538
int i = 1+16;
@@ -39,6 +42,7 @@ public void setUpLoggingListAppender() {
3942

4043
@Test
4144
public void configuresDefaultsByDefault() {
45+
Objects.requireNonNull(ObjectMappers.CBOR_MAPPER); // force static init
4246
StreamReadConstraintsUtil.fromSystemProperties().validateIsGlobalDefault();
4347
}
4448

@@ -55,8 +59,8 @@ public void configuresMaxStringLength() {
5559
assertThat(configuredConstraints).as("inherited defaults")
5660
.returns(defaults.getMaxDocumentLength(), from(StreamReadConstraints::getMaxDocumentLength))
5761
.returns(defaults.getMaxNameLength(), from(StreamReadConstraints::getMaxNameLength))
58-
.returns(DEFAULT_MAX_NESTING_DEPTH, from(StreamReadConstraints::getMaxNestingDepth))
59-
.returns(DEFAULT_MAX_NUMBER_LENGTH, from(StreamReadConstraints::getMaxNumberLength));
62+
.returns(MAX_NESTING_DEPTH.defaultValue, from(StreamReadConstraints::getMaxNestingDepth))
63+
.returns(MAX_NUMBER_LENGTH.defaultValue, from(StreamReadConstraints::getMaxNumberLength));
6064

6165
assertThatThrownBy(configuredUtil::validateIsGlobalDefault).isInstanceOf(IllegalStateException.class).hasMessageContaining(MAX_STRING_LENGTH.propertyName);
6266

@@ -98,8 +102,8 @@ public void configuresMaxNumberLength() {
98102
assertThat(configuredConstraints).as("inherited defaults")
99103
.returns(defaults.getMaxDocumentLength(), from(StreamReadConstraints::getMaxDocumentLength))
100104
.returns(defaults.getMaxNameLength(), from(StreamReadConstraints::getMaxNameLength))
101-
.returns(DEFAULT_MAX_NESTING_DEPTH, from(StreamReadConstraints::getMaxNestingDepth))
102-
.returns(DEFAULT_MAX_STRING_LENGTH, from(StreamReadConstraints::getMaxStringLength));
105+
.returns(MAX_NESTING_DEPTH.defaultValue, from(StreamReadConstraints::getMaxNestingDepth))
106+
.returns(MAX_STRING_LENGTH.defaultValue, from(StreamReadConstraints::getMaxStringLength));
103107

104108
assertThatThrownBy(configuredUtil::validateIsGlobalDefault).isInstanceOf(IllegalStateException.class).hasMessageContaining(MAX_NUMBER_LENGTH.propertyName);
105109

@@ -141,8 +145,8 @@ public void configuresMaxNestingDepth() {
141145
assertThat(configuredConstraints).as("inherited defaults")
142146
.returns(defaults.getMaxDocumentLength(), from(StreamReadConstraints::getMaxDocumentLength))
143147
.returns(defaults.getMaxNameLength(), from(StreamReadConstraints::getMaxNameLength))
144-
.returns(DEFAULT_MAX_STRING_LENGTH, from(StreamReadConstraints::getMaxStringLength))
145-
.returns(DEFAULT_MAX_NUMBER_LENGTH, from(StreamReadConstraints::getMaxNumberLength));
148+
.returns(MAX_STRING_LENGTH.defaultValue, from(StreamReadConstraints::getMaxStringLength))
149+
.returns(MAX_NUMBER_LENGTH.defaultValue, from(StreamReadConstraints::getMaxNumberLength));
146150

147151
assertThatThrownBy(configuredUtil::validateIsGlobalDefault).isInstanceOf(IllegalStateException.class).hasMessageContaining(MAX_NESTING_DEPTH.propertyName);
148152

@@ -187,8 +191,6 @@ public void validatesApplication() {
187191
configuredUtil.validateIsGlobalDefault();
188192
});
189193

190-
System.out.format("OK%n");
191-
192194
assertLogObserved(Level.INFO, "override `" + MAX_NESTING_DEPTH.propertyName + "` configured to `1010`");
193195
assertLogObserved(Level.INFO, "override `" + MAX_STRING_LENGTH.propertyName + "` configured to `1011`");
194196
assertLogObserved(Level.INFO, "override `" + MAX_NUMBER_LENGTH.propertyName + "` configured to `1110`");
@@ -198,28 +200,17 @@ public void validatesApplication() {
198200
}
199201

200202
@Test
201-
public void usesJacksonDefaultsWhenNoConfig() {
202-
StreamReadConstraintsUtil util = new StreamReadConstraintsUtil(new Properties(), this.observedLogger);
203-
StreamReadConstraints constraints = util.get();
204-
205-
assertThat(constraints)
206-
.returns(DEFAULT_MAX_STRING_LENGTH, from(StreamReadConstraints::getMaxStringLength))
207-
.returns(DEFAULT_MAX_NUMBER_LENGTH, from(StreamReadConstraints::getMaxNumberLength))
208-
.returns(DEFAULT_MAX_NESTING_DEPTH, from(StreamReadConstraints::getMaxNestingDepth));
209-
}
210-
211-
@Test
212-
public void configOverridesDefault() {
213-
Properties props = new Properties();
214-
props.setProperty("logstash.jackson.stream-read-constraints.max-string-length", "100");
203+
public void validatesApplicationWithDefaults() {
204+
final Properties properties = new Properties(); // empty -- no overrides
215205

216-
StreamReadConstraintsUtil util = new StreamReadConstraintsUtil(props, this.observedLogger);
217-
StreamReadConstraints constraints = util.get();
206+
fromProperties(properties, (configuredUtil, defaults) -> {
207+
configuredUtil.applyAsGlobalDefault();
208+
configuredUtil.validateIsGlobalDefault();
218209

219-
assertThat(constraints)
220-
.returns(100, from(StreamReadConstraints::getMaxStringLength))
221-
.returns(DEFAULT_MAX_NUMBER_LENGTH, from(StreamReadConstraints::getMaxNumberLength))
222-
.returns(DEFAULT_MAX_NESTING_DEPTH, from(StreamReadConstraints::getMaxNestingDepth));
210+
assertLogObserved(Level.INFO, "override `" + MAX_NESTING_DEPTH.propertyName + "` configured to `" + MAX_NESTING_DEPTH.defaultValue + "` (logstash default)");
211+
assertLogObserved(Level.INFO, "override `" + MAX_STRING_LENGTH.propertyName + "` configured to `" + MAX_STRING_LENGTH.defaultValue + "` (logstash default)");
212+
assertLogObserved(Level.INFO, "override `" + MAX_NUMBER_LENGTH.propertyName + "` configured to `" + MAX_NUMBER_LENGTH.defaultValue + "` (logstash default)");
213+
});
223214
}
224215

225216
private void assertLogObserved(final Level level, final String... messageFragments) {
@@ -230,13 +221,33 @@ private void assertLogObserved(final Level level, final String... messageFragmen
230221
.anySatisfy(logEvent -> assertThat(logEvent.getMessage().getFormattedMessage()).contains(messageFragments));
231222
}
232223

233-
private synchronized void fromProperties(final Properties properties, BiConsumer<StreamReadConstraintsUtil, StreamReadConstraints> defaultsConsumer) {
224+
private synchronized void fromProperties(final Properties properties,
225+
final BiConsumer<StreamReadConstraintsUtil, StreamReadConstraints> defaultsConsumer) {
234226
final StreamReadConstraints defaults = StreamReadConstraints.defaults();
235-
try {
236-
final StreamReadConstraintsUtil util = new StreamReadConstraintsUtil(properties, this.observedLogger);
237-
defaultsConsumer.accept(util, defaults);
238-
} finally {
239-
StreamReadConstraints.overrideDefaultStreamReadConstraints(defaults);
227+
final StreamReadConstraintsUtil util = new StreamReadConstraintsUtil(properties, this.observedLogger);
228+
229+
defaultsConsumer.accept(util, defaults);
230+
}
231+
232+
/**
233+
* A TestRule to snapshot the current defaults from jackson prior to test execution, and to
234+
* ensure that snapshot is restored after test execution has completed.
235+
*/
236+
public static class DefaultsRestorer extends org.junit.rules.ExternalResource {
237+
private StreamReadConstraints snapshot;
238+
239+
public StreamReadConstraints getSnapshot() {
240+
return this.snapshot;
241+
}
242+
243+
@Override
244+
protected void before() throws Throwable {
245+
this.snapshot = StreamReadConstraints.defaults();
246+
}
247+
248+
@Override
249+
protected void after() {
250+
StreamReadConstraints.overrideDefaultStreamReadConstraints(this.snapshot);
240251
}
241252
}
242253
}

0 commit comments

Comments
 (0)