Skip to content

Commit fab0a5d

Browse files
committed
MetadataExtractor refactoring
Remove RSocketStrategies argument from the contract to avoid having to pass them every time especially by application components, like an implementation of a Spring Security matcher. Decouple DefaultMetadataExtractor from RSocketStrategies in favor of a decoders property and an internal DataBufferFactory, which does not need to be the shared one as we're only wrapping ByteBufs.
1 parent 7c45381 commit fab0a5d

File tree

9 files changed

+260
-72
lines changed

9 files changed

+260
-72
lines changed

spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultMetadataExtractor.java

Lines changed: 110 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,26 @@
1515
*/
1616
package org.springframework.messaging.rsocket;
1717

18+
import java.nio.charset.StandardCharsets;
19+
import java.util.ArrayList;
1820
import java.util.Collections;
1921
import java.util.HashMap;
22+
import java.util.List;
2023
import java.util.Map;
2124
import java.util.function.BiConsumer;
2225

2326
import io.netty.buffer.ByteBuf;
27+
import io.netty.buffer.PooledByteBufAllocator;
2428
import io.rsocket.Payload;
2529
import io.rsocket.metadata.CompositeMetadata;
2630

2731
import org.springframework.core.ParameterizedTypeReference;
2832
import org.springframework.core.ResolvableType;
2933
import org.springframework.core.codec.Decoder;
30-
import org.springframework.core.io.buffer.DataBuffer;
31-
import org.springframework.core.io.buffer.DataBufferFactory;
34+
import org.springframework.core.io.buffer.NettyDataBuffer;
3235
import org.springframework.core.io.buffer.NettyDataBufferFactory;
3336
import org.springframework.lang.Nullable;
37+
import org.springframework.util.Assert;
3438
import org.springframework.util.MimeType;
3539

3640
/**
@@ -47,15 +51,53 @@
4751
*/
4852
public class DefaultMetadataExtractor implements MetadataExtractor {
4953

50-
private final Map<String, EntryProcessor<?>> entryProcessors = new HashMap<>();
54+
private final List<Decoder<?>> decoders = new ArrayList<>();
55+
56+
private final Map<String, MetadataProcessor<?>> processors = new HashMap<>();
5157

5258

5359
/**
54-
* Default constructor with {@link RSocketStrategies}.
60+
* Configure the decoders to use for de-serializing metadata entries.
61+
* <p>By default this is not set.
62+
* <p>When this extractor is passed into {@link RSocketStrategies.Builder} or
63+
* {@link org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler
64+
* RSocketMessageHandler}, the decoders may be left not set, and they will
65+
* be initialized from the decoders already configured there.
5566
*/
56-
public DefaultMetadataExtractor() {
57-
// TODO: remove when rsocket-core API available
58-
metadataToExtract(MetadataExtractor.ROUTING, String.class, ROUTE_KEY);
67+
public void setDecoders(List<? extends Decoder<?>> decoders) {
68+
this.decoders.clear();
69+
if (!decoders.isEmpty()) {
70+
this.decoders.addAll(decoders);
71+
updateProcessors();
72+
}
73+
}
74+
75+
@SuppressWarnings("unchecked")
76+
private <T> void updateProcessors() {
77+
for (MetadataProcessor<?> info : this.processors.values()) {
78+
Decoder<T> decoder = decoderFor(info.mimeType(), info.targetType());
79+
Assert.isTrue(decoder != null, "No decoder for " + info);
80+
info = ((MetadataProcessor<T>) info).setDecoder(decoder);
81+
this.processors.put(info.mimeType().toString(), info);
82+
}
83+
}
84+
85+
@Nullable
86+
@SuppressWarnings("unchecked")
87+
private <T> Decoder<T> decoderFor(MimeType mimeType, ResolvableType type) {
88+
for (Decoder<?> decoder : this.decoders) {
89+
if (decoder.canDecode(type, mimeType)) {
90+
return (Decoder<T>) decoder;
91+
}
92+
}
93+
return null;
94+
}
95+
96+
/**
97+
* Return the {@link #setDecoders(List) configured} decoders.
98+
*/
99+
public List<? extends Decoder<?>> getDecoders() {
100+
return this.decoders;
59101
}
60102

61103

@@ -97,11 +139,9 @@ public void metadataToExtract(
97139
* @param <T> the target value type
98140
*/
99141
public <T> void metadataToExtract(
100-
MimeType mimeType, Class<T> targetType,
101-
BiConsumer<T, Map<String, Object>> mapper) {
142+
MimeType mimeType, Class<T> targetType, BiConsumer<T, Map<String, Object>> mapper) {
102143

103-
EntryProcessor<T> spec = new EntryProcessor<>(mimeType, targetType, mapper);
104-
this.entryProcessors.put(mimeType.toString(), spec);
144+
metadataToExtract(mimeType, mapper, ResolvableType.forClass(targetType));
105145
}
106146

107147
/**
@@ -117,87 +157,107 @@ public <T> void metadataToExtract(
117157
MimeType mimeType, ParameterizedTypeReference<T> targetType,
118158
BiConsumer<T, Map<String, Object>> mapper) {
119159

120-
EntryProcessor<T> spec = new EntryProcessor<>(mimeType, targetType, mapper);
121-
this.entryProcessors.put(mimeType.toString(), spec);
160+
metadataToExtract(mimeType, mapper, ResolvableType.forType(targetType));
161+
}
162+
163+
private <T> void metadataToExtract(
164+
MimeType mimeType, BiConsumer<T, Map<String, Object>> mapper, ResolvableType elementType) {
165+
166+
Decoder<T> decoder = decoderFor(mimeType, elementType);
167+
Assert.isTrue(this.decoders.isEmpty() || decoder != null, () -> "No decoder for " + mimeType);
168+
MetadataProcessor<T> info = new MetadataProcessor<>(mimeType, elementType, mapper, decoder);
169+
this.processors.put(mimeType.toString(), info);
122170
}
123171

124172

125173
@Override
126-
public Map<String, Object> extract(Payload payload, MimeType metadataMimeType, RSocketStrategies strategies) {
174+
public Map<String, Object> extract(Payload payload, MimeType metadataMimeType) {
127175
Map<String, Object> result = new HashMap<>();
128176
if (metadataMimeType.equals(COMPOSITE_METADATA)) {
129177
for (CompositeMetadata.Entry entry : new CompositeMetadata(payload.metadata(), false)) {
130-
processEntry(entry.getContent(), entry.getMimeType(), result, strategies);
178+
processEntry(entry.getContent(), entry.getMimeType(), result);
131179
}
132180
}
133181
else {
134-
processEntry(payload.metadata(), metadataMimeType.toString(), result, strategies);
182+
processEntry(payload.metadata(), metadataMimeType.toString(), result);
135183
}
136184
return result;
137185
}
138186

139-
private void processEntry(ByteBuf content,
140-
@Nullable String mimeType, Map<String, Object> result, RSocketStrategies strategies) {
141-
142-
EntryProcessor<?> entryProcessor = this.entryProcessors.get(mimeType);
143-
if (entryProcessor != null) {
144-
content.retain();
145-
entryProcessor.process(content, result, strategies);
187+
@SuppressWarnings("unchecked")
188+
private <T> void processEntry(ByteBuf content, @Nullable String mimeType, Map<String, Object> result) {
189+
MetadataProcessor<T> info = (MetadataProcessor<T>) this.processors.get(mimeType);
190+
if (info != null) {
191+
info.process(content, result);
146192
return;
147193
}
148194
if (MetadataExtractor.ROUTING.toString().equals(mimeType)) {
149195
// TODO: use rsocket-core API when available
196+
result.put(MetadataExtractor.ROUTE_KEY, content.toString(StandardCharsets.UTF_8));
150197
}
151198
}
152199

153200

154-
/**
155-
* Helps to decode a metadata entry and add the resulting value to the
156-
* output map.
157-
*/
158-
private class EntryProcessor<T> {
201+
private static class MetadataProcessor<T> {
202+
203+
private final static NettyDataBufferFactory bufferFactory =
204+
new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT);
205+
159206

160207
private final MimeType mimeType;
161208

162209
private final ResolvableType targetType;
163210

164211
private final BiConsumer<T, Map<String, Object>> accumulator;
165212

213+
@Nullable
214+
private final Decoder<T> decoder;
166215

167-
public EntryProcessor(
168-
MimeType mimeType, Class<T> targetType,
169-
BiConsumer<T, Map<String, Object>> accumulator) {
170216

171-
this(mimeType, ResolvableType.forClass(targetType), accumulator);
172-
}
217+
MetadataProcessor(MimeType mimeType, ResolvableType targetType,
218+
BiConsumer<T, Map<String, Object>> accumulator, @Nullable Decoder<T> decoder) {
173219

174-
public EntryProcessor(
175-
MimeType mimeType, ParameterizedTypeReference<T> targetType,
176-
BiConsumer<T, Map<String, Object>> accumulator) {
220+
this.mimeType = mimeType;
221+
this.targetType = targetType;
222+
this.accumulator = accumulator;
223+
this.decoder = decoder;
224+
}
177225

178-
this(mimeType, ResolvableType.forType(targetType), accumulator);
226+
MetadataProcessor(MetadataProcessor<T> other, Decoder<T> decoder) {
227+
this.mimeType = other.mimeType;
228+
this.targetType = other.targetType;
229+
this.accumulator = other.accumulator;
230+
this.decoder = decoder;
179231
}
180232

181-
private EntryProcessor(
182-
MimeType mimeType, ResolvableType targetType,
183-
BiConsumer<T, Map<String, Object>> accumulator) {
184233

185-
this.mimeType = mimeType;
186-
this.targetType = targetType;
187-
this.accumulator = accumulator;
234+
public MimeType mimeType() {
235+
return this.mimeType;
188236
}
189237

238+
public ResolvableType targetType() {
239+
return this.targetType;
240+
}
241+
242+
public MetadataProcessor<T> setDecoder(Decoder<T> decoder) {
243+
return this.decoder != decoder ? new MetadataProcessor<>(this, decoder) : this;
244+
}
190245

191-
public void process(ByteBuf byteBuf, Map<String, Object> result, RSocketStrategies strategies) {
192-
DataBufferFactory factory = strategies.dataBufferFactory();
193-
DataBuffer buffer = factory instanceof NettyDataBufferFactory ?
194-
((NettyDataBufferFactory) factory).wrap(byteBuf) :
195-
factory.wrap(byteBuf.nioBuffer());
196246

197-
Decoder<T> decoder = strategies.decoder(this.targetType, this.mimeType);
198-
T value = decoder.decode(buffer, this.targetType, this.mimeType, Collections.emptyMap());
247+
public void process(ByteBuf content, Map<String, Object> result) {
248+
if (this.decoder == null) {
249+
throw new IllegalStateException("No decoder for " + this);
250+
}
251+
NettyDataBuffer dataBuffer = bufferFactory.wrap(content.retain());
252+
T value = this.decoder.decode(dataBuffer, this.targetType, this.mimeType, Collections.emptyMap());
199253
this.accumulator.accept(value, result);
200254
}
255+
256+
257+
@Override
258+
public String toString() {
259+
return "MetadataProcessor mimeType=" + this.mimeType + ", targetType=" + this.targetType;
260+
}
201261
}
202262

203263
}

spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketStrategies.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ public RSocketStrategies build() {
209209
return new DefaultRSocketStrategies(
210210
this.encoders, this.decoders,
211211
this.routeMatcher != null ? this.routeMatcher : initRouteMatcher(),
212-
this.metadataExtractor != null ? this.metadataExtractor : initMetadataExtractor(),
212+
getOrInitMetadataExtractor(),
213213
this.bufferFactory != null ? this.bufferFactory : initBufferFactory(),
214214
this.adapterRegistry != null ? this.adapterRegistry : initReactiveAdapterRegistry());
215215
}
@@ -220,10 +220,22 @@ private RouteMatcher initRouteMatcher() {
220220
return new SimpleRouteMatcher(pathMatcher);
221221
}
222222

223-
private MetadataExtractor initMetadataExtractor() {
224-
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor();
225-
extractor.metadataToExtract(MimeTypeUtils.TEXT_PLAIN, String.class, MetadataExtractor.ROUTE_KEY);
226-
return extractor;
223+
private MetadataExtractor getOrInitMetadataExtractor() {
224+
if (this.metadataExtractor != null) {
225+
if (this.metadataExtractor instanceof DefaultMetadataExtractor) {
226+
DefaultMetadataExtractor extractor = (DefaultMetadataExtractor) this.metadataExtractor;
227+
if (extractor.getDecoders().isEmpty()) {
228+
extractor.setDecoders(this.decoders);
229+
}
230+
}
231+
return this.metadataExtractor;
232+
}
233+
else {
234+
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor();
235+
extractor.setDecoders(this.decoders);
236+
extractor.metadataToExtract(MimeTypeUtils.TEXT_PLAIN, String.class, MetadataExtractor.ROUTE_KEY);
237+
return extractor;
238+
}
227239
}
228240

229241
private DataBufferFactory initBufferFactory() {

spring-messaging/src/main/java/org/springframework/messaging/rsocket/MetadataExtractor.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,8 @@ public interface MetadataExtractor {
5858
* @param payload the payload whose metadata should be read
5959
* @param metadataMimeType the mime type of the metadata; this is what was
6060
* specified by the client at the start of the RSocket connection.
61-
* @param strategies for access to codecs and a DataBufferFactory
6261
* @return a map of 0 or more decoded metadata values with assigned names
6362
*/
64-
Map<String, Object> extract(Payload payload, MimeType metadataMimeType, RSocketStrategies strategies);
63+
Map<String, Object> extract(Payload payload, MimeType metadataMimeType);
6564

6665
}

spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketStrategies.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ interface Builder {
190190
* <p>By default this is {@link DefaultMetadataExtractor} extracting a
191191
* route from {@code "message/x.rsocket.routing.v0"} or
192192
* {@code "text/plain"} metadata entries.
193+
* <p>If the extractor is a {@code DefaultMetadataExtractor}, its
194+
* {@code decoders} property will be set, if not already set, to the
195+
* {@link #decoder(Decoder[]) decoders} configured here.
193196
*/
194197
Builder metadataExtractor(@Nullable MetadataExtractor metadataExtractor);
195198

spring-messaging/src/main/java/org/springframework/messaging/rsocket/annotation/support/MessagingRSocket.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,7 @@ private MessageHeaders createHeaders(Payload payload, FrameType frameType,
192192
MessageHeaderAccessor headers = new MessageHeaderAccessor();
193193
headers.setLeaveMutable(true);
194194

195-
Map<String, Object> metadataValues =
196-
this.metadataExtractor.extract(payload, this.metadataMimeType, this.strategies);
195+
Map<String, Object> metadataValues = this.metadataExtractor.extract(payload, this.metadataMimeType);
197196

198197
metadataValues.putIfAbsent(MetadataExtractor.ROUTE_KEY, "");
199198
for (Map.Entry<String, Object> entry : metadataValues.entrySet()) {

spring-messaging/src/main/java/org/springframework/messaging/rsocket/annotation/support/RSocketMessageHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ private RSocketStrategies initRSocketStrategies() {
174174
* other metadata.
175175
* <p>By default this is {@link DefaultMetadataExtractor} extracting a
176176
* route from {@code "message/x.rsocket.routing.v0"} or {@code "text/plain"}.
177+
* <p>If the extractor is a {@code DefaultMetadataExtractor}, its
178+
* {@code decoders} property will be set, if not already set, to the
179+
* {@link #setDecoders(List)} configured here.
177180
* @param extractor the extractor to use
178181
*/
179182
public void setMetadataExtractor(MetadataExtractor extractor) {
@@ -238,6 +241,7 @@ public void afterPropertiesSet() {
238241

239242
if (getMetadataExtractor() == null) {
240243
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor();
244+
extractor.setDecoders(getDecoders());
241245
extractor.metadataToExtract(MimeTypeUtils.TEXT_PLAIN, String.class, MetadataExtractor.ROUTE_KEY);
242246
setMetadataExtractor(extractor);
243247
}

0 commit comments

Comments
 (0)