Skip to content

Commit dc0c730

Browse files
committed
DefaultMetadataExtractor refactoring
1 parent 2aa3363 commit dc0c730

File tree

5 files changed

+65
-153
lines changed

5 files changed

+65
-153
lines changed

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

Lines changed: 44 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.nio.charset.StandardCharsets;
1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.Collections;
2122
import java.util.HashMap;
2223
import java.util.List;
@@ -34,7 +35,6 @@
3435
import org.springframework.core.io.buffer.NettyDataBuffer;
3536
import org.springframework.core.io.buffer.NettyDataBufferFactory;
3637
import org.springframework.lang.Nullable;
37-
import org.springframework.util.Assert;
3838
import org.springframework.util.MimeType;
3939

4040
/**
@@ -51,46 +51,28 @@
5151
*/
5252
public class DefaultMetadataExtractor implements MetadataExtractor {
5353

54-
private final List<Decoder<?>> decoders = new ArrayList<>();
54+
private final List<Decoder<?>> decoders;
5555

56-
private final Map<String, MetadataProcessor<?>> processors = new HashMap<>();
56+
private final Map<String, EntryExtractor<?>> registrations = new HashMap<>();
5757

5858

5959
/**
60-
* Configure the decoders to use for de-serializing metadata entries.
61-
* <p>By default this is not set.
60+
* Constructor with decoders for de-serializing metadata entries.
6261
*/
63-
public void setDecoders(List<? extends Decoder<?>> decoders) {
64-
this.decoders.clear();
65-
if (!decoders.isEmpty()) {
66-
this.decoders.addAll(decoders);
67-
updateProcessors();
68-
}
62+
public DefaultMetadataExtractor(Decoder<?>... decoders) {
63+
this(Arrays.asList(decoders));
6964
}
7065

71-
@SuppressWarnings("unchecked")
72-
private <T> void updateProcessors() {
73-
for (MetadataProcessor<?> info : this.processors.values()) {
74-
Decoder<T> decoder = decoderFor(info.mimeType(), info.targetType());
75-
Assert.isTrue(decoder != null, "No decoder for " + info);
76-
info = ((MetadataProcessor<T>) info).setDecoder(decoder);
77-
this.processors.put(info.mimeType().toString(), info);
78-
}
66+
/**
67+
* Constructor with list of decoders for de-serializing metadata entries.
68+
*/
69+
public DefaultMetadataExtractor(List<Decoder<?>> decoders) {
70+
this.decoders = Collections.unmodifiableList(new ArrayList<>(decoders));
7971
}
8072

81-
@Nullable
82-
@SuppressWarnings("unchecked")
83-
private <T> Decoder<T> decoderFor(MimeType mimeType, ResolvableType type) {
84-
for (Decoder<?> decoder : this.decoders) {
85-
if (decoder.canDecode(type, mimeType)) {
86-
return (Decoder<T>) decoder;
87-
}
88-
}
89-
return null;
90-
}
9173

9274
/**
93-
* Return the {@link #setDecoders(List) configured} decoders.
75+
* Return a read-only list with the configured decoders.
9476
*/
9577
public List<? extends Decoder<?>> getDecoders() {
9678
return this.decoders;
@@ -106,9 +88,7 @@ public List<? extends Decoder<?>> getDecoders() {
10688
* @param name assign a name for the decoded value; if not provided, then
10789
* the mime type is used as the key
10890
*/
109-
public void metadataToExtract(
110-
MimeType mimeType, Class<?> targetType, @Nullable String name) {
111-
91+
public void metadataToExtract(MimeType mimeType, Class<?> targetType, @Nullable String name) {
11292
String key = name != null ? name : mimeType.toString();
11393
metadataToExtract(mimeType, targetType, (value, map) -> map.put(key, value));
11494
}
@@ -117,6 +97,8 @@ public void metadataToExtract(
11797
* Variant of {@link #metadataToExtract(MimeType, Class, String)} that accepts
11898
* {@link ParameterizedTypeReference} instead of {@link Class} for
11999
* specifying a target type with generic parameters.
100+
* @param mimeType the mime type of metadata entries to extract
101+
* @param targetType the target value type to decode to
120102
*/
121103
public void metadataToExtract(
122104
MimeType mimeType, ParameterizedTypeReference<?> targetType, @Nullable String name) {
@@ -137,32 +119,36 @@ public void metadataToExtract(
137119
public <T> void metadataToExtract(
138120
MimeType mimeType, Class<T> targetType, BiConsumer<T, Map<String, Object>> mapper) {
139121

140-
metadataToExtract(mimeType, mapper, ResolvableType.forClass(targetType));
122+
registerMetadata(mimeType, ResolvableType.forClass(targetType), mapper);
141123
}
142124

143125
/**
144126
* Variant of {@link #metadataToExtract(MimeType, Class, BiConsumer)} that
145127
* accepts {@link ParameterizedTypeReference} instead of {@link Class} for
146128
* specifying a target type with generic parameters.
147129
* @param mimeType the mime type of metadata entries to extract
148-
* @param targetType the target value type to decode to
130+
* @param type the target value type to decode to
149131
* @param mapper custom logic to add the decoded value to the output map
150132
* @param <T> the target value type
151133
*/
152134
public <T> void metadataToExtract(
153-
MimeType mimeType, ParameterizedTypeReference<T> targetType,
154-
BiConsumer<T, Map<String, Object>> mapper) {
135+
MimeType mimeType, ParameterizedTypeReference<T> type, BiConsumer<T, Map<String, Object>> mapper) {
155136

156-
metadataToExtract(mimeType, mapper, ResolvableType.forType(targetType));
137+
registerMetadata(mimeType, ResolvableType.forType(type), mapper);
157138
}
158139

159-
private <T> void metadataToExtract(
160-
MimeType mimeType, BiConsumer<T, Map<String, Object>> mapper, ResolvableType elementType) {
140+
@SuppressWarnings("unchecked")
141+
private <T> void registerMetadata(
142+
MimeType mimeType, ResolvableType targetType, BiConsumer<T, Map<String, Object>> mapper) {
161143

162-
Decoder<T> decoder = decoderFor(mimeType, elementType);
163-
Assert.isTrue(this.decoders.isEmpty() || decoder != null, () -> "No decoder for " + mimeType);
164-
MetadataProcessor<T> info = new MetadataProcessor<>(mimeType, elementType, mapper, decoder);
165-
this.processors.put(mimeType.toString(), info);
144+
for (Decoder<?> decoder : this.decoders) {
145+
if (decoder.canDecode(targetType, mimeType)) {
146+
this.registrations.put(mimeType.toString(),
147+
new EntryExtractor<>((Decoder<T>) decoder, mimeType, targetType, mapper));
148+
return;
149+
}
150+
}
151+
throw new IllegalArgumentException("No decoder for " + mimeType + " and " + targetType);
166152
}
167153

168154

@@ -171,20 +157,19 @@ public Map<String, Object> extract(Payload payload, MimeType metadataMimeType) {
171157
Map<String, Object> result = new HashMap<>();
172158
if (metadataMimeType.equals(COMPOSITE_METADATA)) {
173159
for (CompositeMetadata.Entry entry : new CompositeMetadata(payload.metadata(), false)) {
174-
processEntry(entry.getContent(), entry.getMimeType(), result);
160+
extractEntry(entry.getContent(), entry.getMimeType(), result);
175161
}
176162
}
177163
else {
178-
processEntry(payload.metadata(), metadataMimeType.toString(), result);
164+
extractEntry(payload.metadata(), metadataMimeType.toString(), result);
179165
}
180166
return result;
181167
}
182168

183-
@SuppressWarnings("unchecked")
184-
private <T> void processEntry(ByteBuf content, @Nullable String mimeType, Map<String, Object> result) {
185-
MetadataProcessor<T> info = (MetadataProcessor<T>) this.processors.get(mimeType);
186-
if (info != null) {
187-
info.process(content, result);
169+
private void extractEntry(ByteBuf content, @Nullable String mimeType, Map<String, Object> result) {
170+
EntryExtractor<?> extractor = this.registrations.get(mimeType);
171+
if (extractor != null) {
172+
extractor.extract(content, result);
188173
return;
189174
}
190175
if (MetadataExtractor.ROUTING.toString().equals(mimeType)) {
@@ -194,56 +179,32 @@ private <T> void processEntry(ByteBuf content, @Nullable String mimeType, Map<St
194179
}
195180

196181

197-
private static class MetadataProcessor<T> {
182+
private static class EntryExtractor<T> {
198183

199184
private final static NettyDataBufferFactory bufferFactory =
200185
new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT);
201186

202187

188+
private final Decoder<T> decoder;
189+
203190
private final MimeType mimeType;
204191

205192
private final ResolvableType targetType;
206193

207194
private final BiConsumer<T, Map<String, Object>> accumulator;
208195

209-
@Nullable
210-
private final Decoder<T> decoder;
211-
212196

213-
MetadataProcessor(MimeType mimeType, ResolvableType targetType,
214-
BiConsumer<T, Map<String, Object>> accumulator, @Nullable Decoder<T> decoder) {
197+
EntryExtractor(Decoder<T> decoder, MimeType mimeType, ResolvableType targetType,
198+
BiConsumer<T, Map<String, Object>> accumulator) {
215199

200+
this.decoder = decoder;
216201
this.mimeType = mimeType;
217202
this.targetType = targetType;
218203
this.accumulator = accumulator;
219-
this.decoder = decoder;
220-
}
221-
222-
MetadataProcessor(MetadataProcessor<T> other, Decoder<T> decoder) {
223-
this.mimeType = other.mimeType;
224-
this.targetType = other.targetType;
225-
this.accumulator = other.accumulator;
226-
this.decoder = decoder;
227204
}
228205

229206

230-
public MimeType mimeType() {
231-
return this.mimeType;
232-
}
233-
234-
public ResolvableType targetType() {
235-
return this.targetType;
236-
}
237-
238-
public MetadataProcessor<T> setDecoder(Decoder<T> decoder) {
239-
return this.decoder != decoder ? new MetadataProcessor<>(this, decoder) : this;
240-
}
241-
242-
243-
public void process(ByteBuf content, Map<String, Object> result) {
244-
if (this.decoder == null) {
245-
throw new IllegalStateException("No decoder for " + this);
246-
}
207+
public void extract(ByteBuf content, Map<String, Object> result) {
247208
NettyDataBuffer dataBuffer = bufferFactory.wrap(content.retain());
248209
T value = this.decoder.decode(dataBuffer, this.targetType, this.mimeType, Collections.emptyMap());
249210
this.accumulator.accept(value, result);
@@ -252,7 +213,7 @@ public void process(ByteBuf content, Map<String, Object> result) {
252213

253214
@Override
254215
public String toString() {
255-
return "MetadataProcessor mimeType=" + this.mimeType + ", targetType=" + this.targetType;
216+
return "mimeType=" + this.mimeType + ", targetType=" + this.targetType;
256217
}
257218
}
258219

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -205,29 +205,27 @@ public Builder dataBufferFactory(@Nullable DataBufferFactory bufferFactory) {
205205

206206
@Override
207207
public RSocketStrategies build() {
208+
209+
RouteMatcher matcher = this.routeMatcher != null ? this.routeMatcher : initRouteMatcher();
210+
211+
MetadataExtractor extractor = this.metadataExtractor != null ?
212+
this.metadataExtractor : new DefaultMetadataExtractor(this.decoders);
213+
214+
DataBufferFactory factory = this.bufferFactory != null ?
215+
this.bufferFactory : new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT);
216+
217+
ReactiveAdapterRegistry registry = this.adapterRegistry != null ?
218+
this.adapterRegistry : ReactiveAdapterRegistry.getSharedInstance();
219+
208220
return new DefaultRSocketStrategies(
209-
this.encoders, this.decoders,
210-
this.routeMatcher != null ? this.routeMatcher : initRouteMatcher(),
211-
this.metadataExtractor != null ? this.metadataExtractor : initMetadataExtractor(),
212-
this.bufferFactory != null ? this.bufferFactory : initBufferFactory(),
213-
this.adapterRegistry != null ? this.adapterRegistry : ReactiveAdapterRegistry.getSharedInstance());
221+
this.encoders, this.decoders, matcher, extractor, factory, registry);
214222
}
215223

216224
private RouteMatcher initRouteMatcher() {
217225
AntPathMatcher pathMatcher = new AntPathMatcher();
218226
pathMatcher.setPathSeparator(".");
219227
return new SimpleRouteMatcher(pathMatcher);
220228
}
221-
222-
private MetadataExtractor initMetadataExtractor() {
223-
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor();
224-
extractor.setDecoders(this.decoders);
225-
return extractor;
226-
}
227-
228-
private DataBufferFactory initBufferFactory() {
229-
return new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT);
230-
}
231229
}
232230

233231
}

spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultMetadataExtractorTests.java

Lines changed: 6 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737

3838
import static org.assertj.core.api.Assertions.assertThat;
3939
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
40-
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
4140
import static org.springframework.messaging.rsocket.MetadataExtractor.COMPOSITE_METADATA;
4241
import static org.springframework.messaging.rsocket.MetadataExtractor.ROUTE_KEY;
4342
import static org.springframework.messaging.rsocket.MetadataExtractor.ROUTING;
@@ -71,8 +70,7 @@ public void setUp() {
7170
this.captor = ArgumentCaptor.forClass(Payload.class);
7271
BDDMockito.when(this.rsocket.fireAndForget(captor.capture())).thenReturn(Mono.empty());
7372

74-
this.extractor = new DefaultMetadataExtractor();
75-
this.extractor.setDecoders(Collections.singletonList(StringDecoder.allMimeTypes()));
73+
this.extractor = new DefaultMetadataExtractor(StringDecoder.allMimeTypes());
7674
}
7775

7876
@After
@@ -165,56 +163,14 @@ public void routeWithCustomFormatting() {
165163
}
166164

167165
@Test
168-
public void addMetadataToExtractBeforeDecoders() {
169-
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor();
170-
extractor.metadataToExtract(TEXT_PLAIN, String.class, "key");
171-
extractor.setDecoders(Collections.singletonList(StringDecoder.allMimeTypes()));
172-
173-
requester(TEXT_PLAIN).metadata("meta entry", null).data("data").send().block();
174-
Payload payload = this.captor.getValue();
175-
Map<String, Object> result = extractor.extract(payload, TEXT_PLAIN);
176-
payload.release();
177-
178-
assertThat(result).hasSize(1).containsEntry("key", "meta entry");
179-
}
180-
181-
@Test
182-
public void noDecoderExceptionWhenSettingDecoders() {
183-
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor();
184-
extractor.metadataToExtract(TEXT_PLAIN, String.class, "key");
185-
186-
assertThatIllegalArgumentException()
187-
.isThrownBy(() -> extractor.setDecoders(Collections.singletonList(new ByteArrayDecoder())))
188-
.withMessage("No decoder for MetadataProcessor mimeType=text/plain, targetType=java.lang.String");
189-
}
190-
191-
@Test
192-
public void noDecoderExceptionWhenRegisteringMetadataToExtract() {
193-
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor();
194-
extractor.setDecoders(Collections.singletonList(new ByteArrayDecoder()));
166+
public void noDecoder() {
167+
DefaultMetadataExtractor extractor =
168+
new DefaultMetadataExtractor(Collections.singletonList(new ByteArrayDecoder())
169+
);
195170

196171
assertThatIllegalArgumentException()
197172
.isThrownBy(() -> extractor.metadataToExtract(TEXT_PLAIN, String.class, "key"))
198-
.withMessage("No decoder for text/plain");
199-
}
200-
201-
@Test
202-
public void decodersNotSet() {
203-
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor();
204-
extractor.metadataToExtract(TEXT_PLAIN, String.class, "key");
205-
206-
assertThatIllegalStateException()
207-
.isThrownBy(() -> {
208-
requester(TEXT_PLAIN).metadata("meta entry", null).data("data").send().block();
209-
Payload payload = this.captor.getValue();
210-
try {
211-
extractor.extract(payload, TEXT_PLAIN);
212-
}
213-
finally {
214-
payload.release();
215-
}
216-
})
217-
.withMessage("No decoder for MetadataProcessor mimeType=text/plain, targetType=java.lang.String");
173+
.withMessage("No decoder for text/plain and java.lang.String");
218174
}
219175

220176

spring-messaging/src/test/java/org/springframework/messaging/rsocket/RSocketServerToClientIntegrationTests.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.messaging.rsocket;
1818

1919
import java.time.Duration;
20-
import java.util.Collections;
2120

2221
import io.rsocket.RSocketFactory;
2322
import io.rsocket.SocketAcceptor;
@@ -267,8 +266,7 @@ public RSocketMessageHandler serverMessageHandler() {
267266

268267
@Bean
269268
public RSocketStrategies rsocketStrategies() {
270-
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor();
271-
extractor.setDecoders(Collections.singletonList(StringDecoder.allMimeTypes()));
269+
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(StringDecoder.allMimeTypes());
272270
extractor.metadataToExtract(MimeTypeUtils.TEXT_PLAIN, String.class, MetadataExtractor.ROUTE_KEY);
273271
return RSocketStrategies.builder().metadataExtractor(extractor).build();
274272
}

spring-messaging/src/test/java/org/springframework/messaging/rsocket/annotation/support/RSocketMessageHandlerTests.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,7 @@ public void getRSocketStrategiesReflectsCurrentState() {
129129

130130
@Test
131131
public void metadataExtractorWithExplicitlySetDecoders() {
132-
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor();
133-
extractor.setDecoders(Collections.singletonList(StringDecoder.allMimeTypes()));
132+
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor(StringDecoder.allMimeTypes());
134133

135134
RSocketMessageHandler handler = new RSocketMessageHandler();
136135
handler.setDecoders(Arrays.asList(new ByteArrayDecoder(), new ByteBufferDecoder()));

0 commit comments

Comments
 (0)