|
|
|
@ -17,6 +17,7 @@ package org.springframework.messaging.rsocket; |
|
|
|
|
|
|
|
|
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.ArrayList; |
|
|
|
|
|
|
|
import java.util.Arrays; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
@ -34,7 +35,6 @@ import org.springframework.core.codec.Decoder; |
|
|
|
import org.springframework.core.io.buffer.NettyDataBuffer; |
|
|
|
import org.springframework.core.io.buffer.NettyDataBuffer; |
|
|
|
import org.springframework.core.io.buffer.NettyDataBufferFactory; |
|
|
|
import org.springframework.core.io.buffer.NettyDataBufferFactory; |
|
|
|
import org.springframework.lang.Nullable; |
|
|
|
import org.springframework.lang.Nullable; |
|
|
|
import org.springframework.util.Assert; |
|
|
|
|
|
|
|
import org.springframework.util.MimeType; |
|
|
|
import org.springframework.util.MimeType; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -51,46 +51,28 @@ import org.springframework.util.MimeType; |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public class DefaultMetadataExtractor implements MetadataExtractor { |
|
|
|
public class DefaultMetadataExtractor implements MetadataExtractor { |
|
|
|
|
|
|
|
|
|
|
|
private final List<Decoder<?>> decoders = new ArrayList<>(); |
|
|
|
private final List<Decoder<?>> decoders; |
|
|
|
|
|
|
|
|
|
|
|
private final Map<String, MetadataProcessor<?>> processors = new HashMap<>(); |
|
|
|
private final Map<String, EntryExtractor<?>> registrations = new HashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Configure the decoders to use for de-serializing metadata entries. |
|
|
|
* Constructor with decoders for de-serializing metadata entries. |
|
|
|
* <p>By default this is not set. |
|
|
|
|
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setDecoders(List<? extends Decoder<?>> decoders) { |
|
|
|
public DefaultMetadataExtractor(Decoder<?>... decoders) { |
|
|
|
this.decoders.clear(); |
|
|
|
this(Arrays.asList(decoders)); |
|
|
|
if (!decoders.isEmpty()) { |
|
|
|
|
|
|
|
this.decoders.addAll(decoders); |
|
|
|
|
|
|
|
updateProcessors(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
/** |
|
|
|
private <T> void updateProcessors() { |
|
|
|
* Constructor with list of decoders for de-serializing metadata entries. |
|
|
|
for (MetadataProcessor<?> info : this.processors.values()) { |
|
|
|
*/ |
|
|
|
Decoder<T> decoder = decoderFor(info.mimeType(), info.targetType()); |
|
|
|
public DefaultMetadataExtractor(List<Decoder<?>> decoders) { |
|
|
|
Assert.isTrue(decoder != null, "No decoder for " + info); |
|
|
|
this.decoders = Collections.unmodifiableList(new ArrayList<>(decoders)); |
|
|
|
info = ((MetadataProcessor<T>) info).setDecoder(decoder); |
|
|
|
|
|
|
|
this.processors.put(info.mimeType().toString(), info); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Nullable |
|
|
|
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
|
|
|
|
private <T> Decoder<T> decoderFor(MimeType mimeType, ResolvableType type) { |
|
|
|
|
|
|
|
for (Decoder<?> decoder : this.decoders) { |
|
|
|
|
|
|
|
if (decoder.canDecode(type, mimeType)) { |
|
|
|
|
|
|
|
return (Decoder<T>) decoder; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Return the {@link #setDecoders(List) configured} decoders. |
|
|
|
* Return a read-only list with the configured decoders. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public List<? extends Decoder<?>> getDecoders() { |
|
|
|
public List<? extends Decoder<?>> getDecoders() { |
|
|
|
return this.decoders; |
|
|
|
return this.decoders; |
|
|
|
@ -106,9 +88,7 @@ public class DefaultMetadataExtractor implements MetadataExtractor { |
|
|
|
* @param name assign a name for the decoded value; if not provided, then |
|
|
|
* @param name assign a name for the decoded value; if not provided, then |
|
|
|
* the mime type is used as the key |
|
|
|
* the mime type is used as the key |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void metadataToExtract( |
|
|
|
public void metadataToExtract(MimeType mimeType, Class<?> targetType, @Nullable String name) { |
|
|
|
MimeType mimeType, Class<?> targetType, @Nullable String name) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String key = name != null ? name : mimeType.toString(); |
|
|
|
String key = name != null ? name : mimeType.toString(); |
|
|
|
metadataToExtract(mimeType, targetType, (value, map) -> map.put(key, value)); |
|
|
|
metadataToExtract(mimeType, targetType, (value, map) -> map.put(key, value)); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -117,6 +97,8 @@ public class DefaultMetadataExtractor implements MetadataExtractor { |
|
|
|
* Variant of {@link #metadataToExtract(MimeType, Class, String)} that accepts |
|
|
|
* Variant of {@link #metadataToExtract(MimeType, Class, String)} that accepts |
|
|
|
* {@link ParameterizedTypeReference} instead of {@link Class} for |
|
|
|
* {@link ParameterizedTypeReference} instead of {@link Class} for |
|
|
|
* specifying a target type with generic parameters. |
|
|
|
* specifying a target type with generic parameters. |
|
|
|
|
|
|
|
* @param mimeType the mime type of metadata entries to extract |
|
|
|
|
|
|
|
* @param targetType the target value type to decode to |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void metadataToExtract( |
|
|
|
public void metadataToExtract( |
|
|
|
MimeType mimeType, ParameterizedTypeReference<?> targetType, @Nullable String name) { |
|
|
|
MimeType mimeType, ParameterizedTypeReference<?> targetType, @Nullable String name) { |
|
|
|
@ -137,7 +119,7 @@ public class DefaultMetadataExtractor implements MetadataExtractor { |
|
|
|
public <T> void metadataToExtract( |
|
|
|
public <T> void metadataToExtract( |
|
|
|
MimeType mimeType, Class<T> targetType, BiConsumer<T, Map<String, Object>> mapper) { |
|
|
|
MimeType mimeType, Class<T> targetType, BiConsumer<T, Map<String, Object>> mapper) { |
|
|
|
|
|
|
|
|
|
|
|
metadataToExtract(mimeType, mapper, ResolvableType.forClass(targetType)); |
|
|
|
registerMetadata(mimeType, ResolvableType.forClass(targetType), mapper); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -145,24 +127,28 @@ public class DefaultMetadataExtractor implements MetadataExtractor { |
|
|
|
* accepts {@link ParameterizedTypeReference} instead of {@link Class} for |
|
|
|
* accepts {@link ParameterizedTypeReference} instead of {@link Class} for |
|
|
|
* specifying a target type with generic parameters. |
|
|
|
* specifying a target type with generic parameters. |
|
|
|
* @param mimeType the mime type of metadata entries to extract |
|
|
|
* @param mimeType the mime type of metadata entries to extract |
|
|
|
* @param targetType the target value type to decode to |
|
|
|
* @param type the target value type to decode to |
|
|
|
* @param mapper custom logic to add the decoded value to the output map |
|
|
|
* @param mapper custom logic to add the decoded value to the output map |
|
|
|
* @param <T> the target value type |
|
|
|
* @param <T> the target value type |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public <T> void metadataToExtract( |
|
|
|
public <T> void metadataToExtract( |
|
|
|
MimeType mimeType, ParameterizedTypeReference<T> targetType, |
|
|
|
MimeType mimeType, ParameterizedTypeReference<T> type, BiConsumer<T, Map<String, Object>> mapper) { |
|
|
|
BiConsumer<T, Map<String, Object>> mapper) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
metadataToExtract(mimeType, mapper, ResolvableType.forType(targetType)); |
|
|
|
registerMetadata(mimeType, ResolvableType.forType(type), mapper); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private <T> void metadataToExtract( |
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
MimeType mimeType, BiConsumer<T, Map<String, Object>> mapper, ResolvableType elementType) { |
|
|
|
private <T> void registerMetadata( |
|
|
|
|
|
|
|
MimeType mimeType, ResolvableType targetType, BiConsumer<T, Map<String, Object>> mapper) { |
|
|
|
|
|
|
|
|
|
|
|
Decoder<T> decoder = decoderFor(mimeType, elementType); |
|
|
|
for (Decoder<?> decoder : this.decoders) { |
|
|
|
Assert.isTrue(this.decoders.isEmpty() || decoder != null, () -> "No decoder for " + mimeType); |
|
|
|
if (decoder.canDecode(targetType, mimeType)) { |
|
|
|
MetadataProcessor<T> info = new MetadataProcessor<>(mimeType, elementType, mapper, decoder); |
|
|
|
this.registrations.put(mimeType.toString(), |
|
|
|
this.processors.put(mimeType.toString(), info); |
|
|
|
new EntryExtractor<>((Decoder<T>) decoder, mimeType, targetType, mapper)); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
throw new IllegalArgumentException("No decoder for " + mimeType + " and " + targetType); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -171,20 +157,19 @@ public class DefaultMetadataExtractor implements MetadataExtractor { |
|
|
|
Map<String, Object> result = new HashMap<>(); |
|
|
|
Map<String, Object> result = new HashMap<>(); |
|
|
|
if (metadataMimeType.equals(COMPOSITE_METADATA)) { |
|
|
|
if (metadataMimeType.equals(COMPOSITE_METADATA)) { |
|
|
|
for (CompositeMetadata.Entry entry : new CompositeMetadata(payload.metadata(), false)) { |
|
|
|
for (CompositeMetadata.Entry entry : new CompositeMetadata(payload.metadata(), false)) { |
|
|
|
processEntry(entry.getContent(), entry.getMimeType(), result); |
|
|
|
extractEntry(entry.getContent(), entry.getMimeType(), result); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
else { |
|
|
|
processEntry(payload.metadata(), metadataMimeType.toString(), result); |
|
|
|
extractEntry(payload.metadata(), metadataMimeType.toString(), result); |
|
|
|
} |
|
|
|
} |
|
|
|
return result; |
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
private void extractEntry(ByteBuf content, @Nullable String mimeType, Map<String, Object> result) { |
|
|
|
private <T> void processEntry(ByteBuf content, @Nullable String mimeType, Map<String, Object> result) { |
|
|
|
EntryExtractor<?> extractor = this.registrations.get(mimeType); |
|
|
|
MetadataProcessor<T> info = (MetadataProcessor<T>) this.processors.get(mimeType); |
|
|
|
if (extractor != null) { |
|
|
|
if (info != null) { |
|
|
|
extractor.extract(content, result); |
|
|
|
info.process(content, result); |
|
|
|
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
if (MetadataExtractor.ROUTING.toString().equals(mimeType)) { |
|
|
|
if (MetadataExtractor.ROUTING.toString().equals(mimeType)) { |
|
|
|
@ -194,56 +179,32 @@ public class DefaultMetadataExtractor implements MetadataExtractor { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static class MetadataProcessor<T> { |
|
|
|
private static class EntryExtractor<T> { |
|
|
|
|
|
|
|
|
|
|
|
private final static NettyDataBufferFactory bufferFactory = |
|
|
|
private final static NettyDataBufferFactory bufferFactory = |
|
|
|
new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT); |
|
|
|
new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final Decoder<T> decoder; |
|
|
|
|
|
|
|
|
|
|
|
private final MimeType mimeType; |
|
|
|
private final MimeType mimeType; |
|
|
|
|
|
|
|
|
|
|
|
private final ResolvableType targetType; |
|
|
|
private final ResolvableType targetType; |
|
|
|
|
|
|
|
|
|
|
|
private final BiConsumer<T, Map<String, Object>> accumulator; |
|
|
|
private final BiConsumer<T, Map<String, Object>> accumulator; |
|
|
|
|
|
|
|
|
|
|
|
@Nullable |
|
|
|
|
|
|
|
private final Decoder<T> decoder; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MetadataProcessor(MimeType mimeType, ResolvableType targetType, |
|
|
|
EntryExtractor(Decoder<T> decoder, MimeType mimeType, ResolvableType targetType, |
|
|
|
BiConsumer<T, Map<String, Object>> accumulator, @Nullable Decoder<T> decoder) { |
|
|
|
BiConsumer<T, Map<String, Object>> accumulator) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.decoder = decoder; |
|
|
|
this.mimeType = mimeType; |
|
|
|
this.mimeType = mimeType; |
|
|
|
this.targetType = targetType; |
|
|
|
this.targetType = targetType; |
|
|
|
this.accumulator = accumulator; |
|
|
|
this.accumulator = accumulator; |
|
|
|
this.decoder = decoder; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MetadataProcessor(MetadataProcessor<T> other, Decoder<T> decoder) { |
|
|
|
|
|
|
|
this.mimeType = other.mimeType; |
|
|
|
|
|
|
|
this.targetType = other.targetType; |
|
|
|
|
|
|
|
this.accumulator = other.accumulator; |
|
|
|
|
|
|
|
this.decoder = decoder; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public MimeType mimeType() { |
|
|
|
public void extract(ByteBuf content, Map<String, Object> result) { |
|
|
|
return this.mimeType; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public ResolvableType targetType() { |
|
|
|
|
|
|
|
return this.targetType; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public MetadataProcessor<T> setDecoder(Decoder<T> decoder) { |
|
|
|
|
|
|
|
return this.decoder != decoder ? new MetadataProcessor<>(this, decoder) : this; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void process(ByteBuf content, Map<String, Object> result) { |
|
|
|
|
|
|
|
if (this.decoder == null) { |
|
|
|
|
|
|
|
throw new IllegalStateException("No decoder for " + this); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
NettyDataBuffer dataBuffer = bufferFactory.wrap(content.retain()); |
|
|
|
NettyDataBuffer dataBuffer = bufferFactory.wrap(content.retain()); |
|
|
|
T value = this.decoder.decode(dataBuffer, this.targetType, this.mimeType, Collections.emptyMap()); |
|
|
|
T value = this.decoder.decode(dataBuffer, this.targetType, this.mimeType, Collections.emptyMap()); |
|
|
|
this.accumulator.accept(value, result); |
|
|
|
this.accumulator.accept(value, result); |
|
|
|
@ -252,7 +213,7 @@ public class DefaultMetadataExtractor implements MetadataExtractor { |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public String toString() { |
|
|
|
public String toString() { |
|
|
|
return "MetadataProcessor mimeType=" + this.mimeType + ", targetType=" + this.targetType; |
|
|
|
return "mimeType=" + this.mimeType + ", targetType=" + this.targetType; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|