Skip to content

Commit

Permalink
Add register methods to CodecConfigurer.CustomCodecs
Browse files Browse the repository at this point in the history
The new register methods replace the now deprecated
encoder, decoder, reader, and writer methods, and also offer a choice
to opt into default properties such maxInMemorySize, if configured.

See gh-24124
  • Loading branch information
rstoyanchev committed Dec 12, 2019
1 parent 9d65830 commit 11e321b
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 68 deletions.
Expand Up @@ -191,34 +191,88 @@ interface DefaultCodecs {
*/
interface CustomCodecs {

/**
* Register a custom codec. This is expected to be one of the following:
* <ul>
* <li>{@link HttpMessageReader}
* <li>{@link HttpMessageWriter}
* <li>{@link Encoder} (wrapped internally with {@link EncoderHttpMessageWriter})
* <li>{@link Decoder} (wrapped internally with {@link DecoderHttpMessageReader})
* </ul>
* @param codec the codec to register
* @since 5.2.3
*/
void register(Object codec);

/**
* Variant of {@link #register(Object)} that also applies the below
* properties, if configured, via {@link #defaultCodecs()}:
* <ul>
* <li>{@link CodecConfigurer.DefaultCodecs#maxInMemorySize(int) maxInMemorySize}
* <li>{@link CodecConfigurer.DefaultCodecs#enableLoggingRequestDetails(boolean) enableLoggingRequestDetails}
* </ul>
* <p>The properties are applied every time {@link #getReaders()} or
* {@link #getWriters()} are used to obtain the list of configured
* readers or writers.
* @param codec the codec to register and apply default config to
* @since 5.2.3
*/
void registerWithDefaultConfig(Object codec);

/**
* Variant of {@link #register(Object)} that also allows the caller to
* apply the properties from {@link DefaultCodecConfig} to the given
* codec. If you want to apply all the properties, prefer using
* {@link #registerWithDefaultConfig(Object)}.
* <p>The consumer is called every time {@link #getReaders()} or
* {@link #getWriters()} are used to obtain the list of configured
* readers or writers.
* @param codec the codec to register
* @param configConsumer consumer of the default config
* @since 5.2.3
*/
void registerWithDefaultConfig(Object codec, Consumer<DefaultCodecConfig> configConsumer);

/**
* Add a custom {@code Decoder} internally wrapped with
* {@link DecoderHttpMessageReader}).
* @param decoder the decoder to add
* @deprecated as of 5.1.13, use {@link #register(Object)} or
* {@link #registerWithDefaultConfig(Object)} instead.
*/
@Deprecated
void decoder(Decoder<?> decoder);

/**
* Add a custom {@code Encoder}, internally wrapped with
* {@link EncoderHttpMessageWriter}.
* @param encoder the encoder to add
* @deprecated as of 5.1.13, use {@link #register(Object)} or
* {@link #registerWithDefaultConfig(Object)} instead.
*/
@Deprecated
void encoder(Encoder<?> encoder);

/**
* Add a custom {@link HttpMessageReader}. For readers of type
* {@link DecoderHttpMessageReader} consider using the shortcut
* {@link #decoder(Decoder)} instead.
* @param reader the reader to add
* @deprecated as of 5.1.13, use {@link #register(Object)} or
* {@link #registerWithDefaultConfig(Object)} instead.
*/
@Deprecated
void reader(HttpMessageReader<?> reader);

/**
* Add a custom {@link HttpMessageWriter}. For writers of type
* {@link EncoderHttpMessageWriter} consider using the shortcut
* {@link #encoder(Encoder)} instead.
* @param writer the writer to add
* @deprecated as of 5.1.13, use {@link #register(Object)} or
* {@link #registerWithDefaultConfig(Object)} instead.
*/
@Deprecated
void writer(HttpMessageWriter<?> writer);

/**
Expand All @@ -227,16 +281,21 @@ interface CustomCodecs {
* guidelines applied to default ones, such as logging details and limiting
* the amount of buffered data.
* @param codecsConfigConsumer the default codecs configuration callback
* @since 5.1.12
* @deprecated as of 5.1.13, use {@link #registerWithDefaultConfig(Object)}
* or {@link #registerWithDefaultConfig(Object, Consumer)} instead.
*/
@Deprecated
void withDefaultCodecConfig(Consumer<DefaultCodecConfig> codecsConfigConsumer);
}


/**
* Common options applied to default codecs and passed in a callback to custom codecs
* so they get a chance to align their behavior on the default ones.
* Exposes the values of properties configured through
* {@link #defaultCodecs()} that are applied to default codecs.
* The main purpose of this interface is to provide access to them so they
* can also be applied to custom codecs if needed.
* @since 5.1.12
* @see CustomCodecs#registerWithDefaultConfig(Object, Consumer)
*/
interface DefaultCodecConfig {

Expand Down
Expand Up @@ -17,7 +17,9 @@
package org.springframework.http.codec.support;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import org.springframework.core.ResolvableType;
Expand All @@ -40,8 +42,6 @@
*/
abstract class BaseCodecConfigurer implements CodecConfigurer {

protected boolean customCodecsInitialized;

protected final BaseDefaultCodecs defaultCodecs;

protected final DefaultCustomCodecs customCodecs;
Expand Down Expand Up @@ -91,21 +91,20 @@ public CustomCodecs customCodecs() {

@Override
public List<HttpMessageReader<?>> getReaders() {
initializeCustomCodecs();
List<HttpMessageReader<?>> result = new ArrayList<>();
this.defaultCodecs.applyDefaultConfig(this.customCodecs);

result.addAll(this.customCodecs.getTypedReaders());
List<HttpMessageReader<?>> result = new ArrayList<>();
result.addAll(this.customCodecs.getTypedReaders().keySet());
result.addAll(this.defaultCodecs.getTypedReaders());

result.addAll(this.customCodecs.getObjectReaders());
result.addAll(this.customCodecs.getObjectReaders().keySet());
result.addAll(this.defaultCodecs.getObjectReaders());

result.addAll(this.defaultCodecs.getCatchAllReaders());
return result;
}

@Override
public List<HttpMessageWriter<?>> getWriters() {
this.defaultCodecs.applyDefaultConfig(this.customCodecs);
return getWritersInternal(false);
}

Expand All @@ -117,13 +116,12 @@ public List<HttpMessageWriter<?>> getWriters() {
* same except for the multipart writer itself.
*/
protected List<HttpMessageWriter<?>> getWritersInternal(boolean forMultipart) {
initializeCustomCodecs();
List<HttpMessageWriter<?>> result = new ArrayList<>();

result.addAll(this.customCodecs.getTypedWriters());
result.addAll(this.customCodecs.getTypedWriters().keySet());
result.addAll(this.defaultCodecs.getTypedWriters(forMultipart));

result.addAll(this.customCodecs.getObjectWriters());
result.addAll(this.customCodecs.getObjectWriters().keySet());
result.addAll(this.defaultCodecs.getObjectWriters(forMultipart));

result.addAll(this.defaultCodecs.getCatchAllWriters());
Expand All @@ -133,28 +131,21 @@ protected List<HttpMessageWriter<?>> getWritersInternal(boolean forMultipart) {
@Override
public abstract CodecConfigurer clone();

private void initializeCustomCodecs() {
if(!this.customCodecsInitialized) {
this.customCodecs.configConsumers.forEach(consumer -> consumer.accept(this.defaultCodecs));
this.customCodecsInitialized = true;
}
}


/**
* Default implementation of {@code CustomCodecs}.
*/
protected static final class DefaultCustomCodecs implements CustomCodecs {

private final List<HttpMessageReader<?>> typedReaders = new ArrayList<>();
private final Map<HttpMessageReader<?>, Boolean> typedReaders = new LinkedHashMap<>(4);

private final List<HttpMessageWriter<?>> typedWriters = new ArrayList<>();
private final Map<HttpMessageWriter<?>, Boolean> typedWriters = new LinkedHashMap<>(4);

private final List<HttpMessageReader<?>> objectReaders = new ArrayList<>();
private final Map<HttpMessageReader<?>, Boolean> objectReaders = new LinkedHashMap<>(4);

private final List<HttpMessageWriter<?>> objectWriters = new ArrayList<>();
private final Map<HttpMessageWriter<?>, Boolean> objectWriters = new LinkedHashMap<>(4);

private final List<Consumer<DefaultCodecConfig>> configConsumers = new ArrayList<>();
private final List<Consumer<DefaultCodecConfig>> defaultConfigConsumers = new ArrayList<>(4);

DefaultCustomCodecs() {
}
Expand All @@ -164,56 +155,103 @@ protected static final class DefaultCustomCodecs implements CustomCodecs {
* @since 5.1.12
*/
DefaultCustomCodecs(DefaultCustomCodecs other) {
other.typedReaders.addAll(this.typedReaders);
other.typedWriters.addAll(this.typedWriters);
other.objectReaders.addAll(this.objectReaders);
other.objectWriters.addAll(this.objectWriters);
other.typedReaders.putAll(this.typedReaders);
other.typedWriters.putAll(this.typedWriters);
other.objectReaders.putAll(this.objectReaders);
other.objectWriters.putAll(this.objectWriters);
}

@Override
public void register(Object codec) {
addCodec(codec, false);
}

@Override
public void registerWithDefaultConfig(Object codec) {
addCodec(codec, true);
}

@Override
public void registerWithDefaultConfig(Object codec, Consumer<DefaultCodecConfig> configConsumer) {
addCodec(codec, false);
this.defaultConfigConsumers.add(configConsumer);
}

@SuppressWarnings("deprecation")
@Override
public void decoder(Decoder<?> decoder) {
reader(new DecoderHttpMessageReader<>(decoder));
addCodec(decoder, false);
}

@SuppressWarnings("deprecation")
@Override
public void encoder(Encoder<?> encoder) {
writer(new EncoderHttpMessageWriter<>(encoder));
addCodec(encoder, false);
}

@SuppressWarnings("deprecation")
@Override
public void reader(HttpMessageReader<?> reader) {
boolean canReadToObject = reader.canRead(ResolvableType.forClass(Object.class), null);
(canReadToObject ? this.objectReaders : this.typedReaders).add(reader);
addCodec(reader, false);
}

@SuppressWarnings("deprecation")
@Override
public void writer(HttpMessageWriter<?> writer) {
boolean canWriteObject = writer.canWrite(ResolvableType.forClass(Object.class), null);
(canWriteObject ? this.objectWriters : this.typedWriters).add(writer);
addCodec(writer, false);
}

@SuppressWarnings("deprecation")
@Override
public void withDefaultCodecConfig(Consumer<DefaultCodecConfig> codecsConfigConsumer) {
this.configConsumers.add(codecsConfigConsumer);
this.defaultConfigConsumers.add(codecsConfigConsumer);
}

private void addCodec(Object codec, boolean applyDefaultConfig) {

if (codec instanceof Decoder) {
codec = new DecoderHttpMessageReader<>((Decoder<?>) codec);
}
else if (codec instanceof Encoder) {
codec = new EncoderHttpMessageWriter<>((Encoder<?>) codec);
}

if (codec instanceof HttpMessageReader) {
HttpMessageReader<?> reader = (HttpMessageReader<?>) codec;
boolean canReadToObject = reader.canRead(ResolvableType.forClass(Object.class), null);
(canReadToObject ? this.objectReaders : this.typedReaders).put(reader, applyDefaultConfig);
}
else if (codec instanceof HttpMessageWriter) {
HttpMessageWriter<?> writer = (HttpMessageWriter<?>) codec;
boolean canWriteObject = writer.canWrite(ResolvableType.forClass(Object.class), null);
(canWriteObject ? this.objectWriters : this.typedWriters).put(writer, applyDefaultConfig);
}
else {
throw new IllegalArgumentException("Unexpected codec type: " + codec.getClass().getName());
}
}

// Package private accessors...

List<HttpMessageReader<?>> getTypedReaders() {
Map<HttpMessageReader<?>, Boolean> getTypedReaders() {
return this.typedReaders;
}

List<HttpMessageWriter<?>> getTypedWriters() {
Map<HttpMessageWriter<?>, Boolean> getTypedWriters() {
return this.typedWriters;
}

List<HttpMessageReader<?>> getObjectReaders() {
Map<HttpMessageReader<?>, Boolean> getObjectReaders() {
return this.objectReaders;
}

List<HttpMessageWriter<?>> getObjectWriters() {
Map<HttpMessageWriter<?>, Boolean> getObjectWriters() {
return this.objectWriters;
}

List<Consumer<DefaultCodecConfig>> getDefaultConfigConsumers() {
return this.defaultConfigConsumers;
}
}

}
Expand Up @@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.springframework.core.codec.AbstractDataBufferDecoder;
import org.springframework.core.codec.ByteArrayDecoder;
Expand Down Expand Up @@ -433,6 +434,21 @@ List<HttpMessageWriter<?>> getCatchAllWriters() {
return result;
}

void applyDefaultConfig(BaseCodecConfigurer.DefaultCustomCodecs customCodecs) {
applyDefaultConfig(customCodecs.getTypedReaders());
applyDefaultConfig(customCodecs.getObjectReaders());
applyDefaultConfig(customCodecs.getTypedWriters());
applyDefaultConfig(customCodecs.getObjectWriters());
customCodecs.getDefaultConfigConsumers().forEach(consumer -> consumer.accept(this));
}

private void applyDefaultConfig(Map<?, Boolean> readers) {
readers.entrySet().stream()
.filter(Map.Entry::getValue)
.map(Map.Entry::getKey)
.forEach(this::initCodec);
}


// Accessors for use in subclasses...

Expand Down

0 comments on commit 11e321b

Please sign in to comment.