Browse Source

Allow registration of RSocket metadata extractors

Prior to this commit, customizing the extraction of RSocket metadata
from frames would require developers to override the default
`MetadataExtractor` while configuring `RSocketStrategies`.
This touches on many infrastructure parts, whereas the goal is just to
configure an extra metadata entry extractor using already configured
codecs.

This commit adds a way to register metadata entry extractors on the
`RSocketStrategies` builder with a `Consumer`-based API.

Closes gh-23645
pull/23645/head
Brian Clozel 5 years ago
parent
commit
848804a227
  1. 51
      spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultMetadataExtractor.java
  2. 13
      spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketStrategies.java
  3. 1
      spring-messaging/src/main/java/org/springframework/messaging/rsocket/MetadataExtractor.java
  4. 90
      spring-messaging/src/main/java/org/springframework/messaging/rsocket/MetadataExtractorRegistry.java
  5. 6
      spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketStrategies.java
  6. 23
      spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketStrategiesTests.java

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

@ -49,7 +49,7 @@ import org.springframework.util.MimeType; @@ -49,7 +49,7 @@ import org.springframework.util.MimeType;
* @author Rossen Stoyanchev
* @since 5.2
*/
public class DefaultMetadataExtractor implements MetadataExtractor {
public class DefaultMetadataExtractor implements MetadataExtractor, MetadataExtractorRegistry {
private final List<Decoder<?>> decoders;
@ -78,59 +78,14 @@ public class DefaultMetadataExtractor implements MetadataExtractor { @@ -78,59 +78,14 @@ public class DefaultMetadataExtractor implements MetadataExtractor {
return this.decoders;
}
/**
* Decode metadata entries with the given {@link MimeType} to the specified
* target class, and store the decoded value in the output map under the
* given name.
* @param mimeType the mime type of metadata entries to extract
* @param targetType the target value type to decode to
* @param name assign a name for the decoded value; if not provided, then
* the mime type is used as the key
*/
public void metadataToExtract(MimeType mimeType, Class<?> targetType, @Nullable String name) {
String key = name != null ? name : mimeType.toString();
metadataToExtract(mimeType, targetType, (value, map) -> map.put(key, value));
}
/**
* Variant of {@link #metadataToExtract(MimeType, Class, String)} that accepts
* {@link ParameterizedTypeReference} instead of {@link Class} for
* 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(
MimeType mimeType, ParameterizedTypeReference<?> targetType, @Nullable String name) {
String key = name != null ? name : mimeType.toString();
metadataToExtract(mimeType, targetType, (value, map) -> map.put(key, value));
}
/**
* Variant of {@link #metadataToExtract(MimeType, Class, String)} that allows
* custom logic to be used to map the decoded value to any number of values
* in the output map.
* @param mimeType the mime type of metadata entries to extract
* @param targetType the target value type to decode to
* @param mapper custom logic to add the decoded value to the output map
* @param <T> the target value type
*/
@Override
public <T> void metadataToExtract(
MimeType mimeType, Class<T> targetType, BiConsumer<T, Map<String, Object>> mapper) {
registerMetadata(mimeType, ResolvableType.forClass(targetType), mapper);
}
/**
* Variant of {@link #metadataToExtract(MimeType, Class, BiConsumer)} that
* accepts {@link ParameterizedTypeReference} instead of {@link Class} for
* specifying a target type with generic parameters.
* @param mimeType the mime type of metadata entries to extract
* @param type the target value type to decode to
* @param mapper custom logic to add the decoded value to the output map
* @param <T> the target value type
*/
@Override
public <T> void metadataToExtract(
MimeType mimeType, ParameterizedTypeReference<T> type, BiConsumer<T, Map<String, Object>> mapper) {

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

@ -46,6 +46,7 @@ import org.springframework.util.SimpleRouteMatcher; @@ -46,6 +46,7 @@ import org.springframework.util.SimpleRouteMatcher;
* Default implementation of {@link RSocketStrategies}.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 5.2
*/
final class DefaultRSocketStrategies implements RSocketStrategies {
@ -128,6 +129,8 @@ final class DefaultRSocketStrategies implements RSocketStrategies { @@ -128,6 +129,8 @@ final class DefaultRSocketStrategies implements RSocketStrategies {
@Nullable
private MetadataExtractor metadataExtractor;
private final List<Consumer<MetadataExtractorRegistry>> metadataExtractors = new ArrayList<>();
DefaultRSocketStrategiesBuilder() {
this.encoders.add(CharSequenceEncoder.allMimeTypes());
this.encoders.add(new ByteBufferEncoder());
@ -200,6 +203,12 @@ final class DefaultRSocketStrategies implements RSocketStrategies { @@ -200,6 +203,12 @@ final class DefaultRSocketStrategies implements RSocketStrategies {
return this;
}
@Override
public Builder metadataExtractors(Consumer<MetadataExtractorRegistry> consumer) {
this.metadataExtractors.add(consumer);
return this;
}
@Override
public RSocketStrategies build() {
RouteMatcher matcher = (this.routeMatcher != null ? this.routeMatcher : initRouteMatcher());
@ -213,6 +222,10 @@ final class DefaultRSocketStrategies implements RSocketStrategies { @@ -213,6 +222,10 @@ final class DefaultRSocketStrategies implements RSocketStrategies {
MetadataExtractor extractor = (this.metadataExtractor != null ?
this.metadataExtractor : new DefaultMetadataExtractor(this.decoders));
if (extractor instanceof MetadataExtractorRegistry) {
this.metadataExtractors.forEach(consumer -> consumer.accept((MetadataExtractorRegistry) extractor));
}
return new DefaultRSocketStrategies(
this.encoders, this.decoders, matcher, registry, factory, extractor);
}

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

@ -31,6 +31,7 @@ import org.springframework.util.MimeType; @@ -31,6 +31,7 @@ import org.springframework.util.MimeType;
*
* @author Rossen Stoyanchev
* @since 5.2
* @see MetadataExtractorRegistry
*/
public interface MetadataExtractor {

90
spring-messaging/src/main/java/org/springframework/messaging/rsocket/MetadataExtractorRegistry.java

@ -0,0 +1,90 @@ @@ -0,0 +1,90 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.messaging.rsocket;
import java.util.Map;
import java.util.function.BiConsumer;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.lang.Nullable;
import org.springframework.util.MimeType;
/**
* Stores registrations of extractors for metadata entries.
* Each metadata entry is decoded based on its {@code MimeType} and a name
* is assigned to the decoded value.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 5.2
* @see MetadataExtractor
*/
public interface MetadataExtractorRegistry {
/**
* Decode metadata entries with the given {@link MimeType} to the specified
* target class, and store the decoded value in the output map under the
* given name.
* @param mimeType the mime type of metadata entries to extract
* @param targetType the target value type to decode to
* @param name assign a name for the decoded value; if not provided, then
* the mime type is used as the key
*/
default void metadataToExtract(MimeType mimeType, Class<?> targetType, @Nullable String name) {
String key = name != null ? name : mimeType.toString();
metadataToExtract(mimeType, targetType, (value, map) -> map.put(key, value));
}
/**
* Variant of {@link #metadataToExtract(MimeType, Class, String)} that accepts
* {@link ParameterizedTypeReference} instead of {@link Class} for
* 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
*/
default void metadataToExtract(
MimeType mimeType, ParameterizedTypeReference<?> targetType, @Nullable String name) {
String key = name != null ? name : mimeType.toString();
metadataToExtract(mimeType, targetType, (value, map) -> map.put(key, value));
}
/**
* Variant of {@link #metadataToExtract(MimeType, Class, String)} that allows
* custom logic to be used to map the decoded value to any number of values
* in the output map.
* @param mimeType the mime type of metadata entries to extract
* @param targetType the target value type to decode to
* @param mapper custom logic to add the decoded value to the output map
* @param <T> the target value type
*/
<T> void metadataToExtract(
MimeType mimeType, Class<T> targetType, BiConsumer<T, Map<String, Object>> mapper);
/**
* Variant of {@link #metadataToExtract(MimeType, Class, BiConsumer)} that
* accepts {@link ParameterizedTypeReference} instead of {@link Class} for
* specifying a target type with generic parameters.
* @param mimeType the mime type of metadata entries to extract
* @param type the target value type to decode to
* @param mapper custom logic to add the decoded value to the output map
* @param <T> the target value type
*/
<T> void metadataToExtract(
MimeType mimeType, ParameterizedTypeReference<T> type, BiConsumer<T, Map<String, Object>> mapper);
}

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

@ -219,6 +219,12 @@ public interface RSocketStrategies { @@ -219,6 +219,12 @@ public interface RSocketStrategies {
*/
Builder metadataExtractor(@Nullable MetadataExtractor metadataExtractor);
/**
* Apply the consumer to the {@link MetadataExtractorRegistry} in order
* to register extra metadata entry extractors.
*/
Builder metadataExtractors(Consumer<MetadataExtractorRegistry> extractorRegistry);
/**
* Build the {@code RSocketStrategies} instance.
*/

23
spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketStrategiesTests.java

@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
*/
package org.springframework.messaging.rsocket;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.core.ReactiveAdapterRegistry;
@ -30,16 +32,20 @@ import org.springframework.util.AntPathMatcher; @@ -30,16 +32,20 @@ import org.springframework.util.AntPathMatcher;
import org.springframework.util.SimpleRouteMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Unit tests for {@link RSocketStrategies}.
* @author Rossen Stoyanchev
* @since 5.2
*/
public class DefaultRSocketStrategiesTests {
class DefaultRSocketStrategiesTests {
@Test
public void defaultSettings() {
void defaultSettings() {
RSocketStrategies strategies = RSocketStrategies.create();
assertThat(strategies.encoders()).hasSize(4).hasOnlyElementsOfTypes(
@ -62,8 +68,7 @@ public class DefaultRSocketStrategiesTests { @@ -62,8 +68,7 @@ public class DefaultRSocketStrategiesTests {
}
@Test
public void explicitValues() {
void explicitValues() {
SimpleRouteMatcher matcher = new SimpleRouteMatcher(new AntPathMatcher());
DefaultMetadataExtractor extractor = new DefaultMetadataExtractor();
ReactiveAdapterRegistry registry = new ReactiveAdapterRegistry();
@ -90,7 +95,7 @@ public class DefaultRSocketStrategiesTests { @@ -90,7 +95,7 @@ public class DefaultRSocketStrategiesTests {
}
@Test
public void copyConstructor() {
void copyConstructor() {
RSocketStrategies strategies1 = RSocketStrategies.create();
RSocketStrategies strategies2 = strategies1.mutate().build();
@ -101,4 +106,12 @@ public class DefaultRSocketStrategiesTests { @@ -101,4 +106,12 @@ public class DefaultRSocketStrategiesTests {
assertThat(strategies1.reactiveAdapterRegistry()).isSameAs(strategies2.reactiveAdapterRegistry());
}
@Test
@SuppressWarnings("unchecked")
void applyMetadataExtractors() {
Consumer<MetadataExtractorRegistry> consumer = (Consumer<MetadataExtractorRegistry>) mock(Consumer.class);
RSocketStrategies strategies = RSocketStrategies.builder().metadataExtractors(consumer).build();
verify(consumer, times(1)).accept(any());
}
}

Loading…
Cancel
Save