Refactor Router to RoutingFunctions

This commit refactors the Router into a RoutingFunctions class, by:

  - Renaming the class :)
  - Moving all Configuration logic into a separate, top-level
  Configuration class with mutable builder.
This commit is contained in:
Arjen Poutsma
2016-09-13 20:13:36 +02:00
parent aaa1281809
commit 91bde2e6b2
20 changed files with 481 additions and 351 deletions

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2002-2016 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
*
* http://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.web.reactive.function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.context.ApplicationContext;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.util.Assert;
import org.springframework.web.reactive.result.view.ViewResolver;
/**
* Defines the configuration to be used for processing {@link HandlerFunction}s. An instance of
* this class is immutable, but a
*
* @author Arjen Poutsma
* @since 5.0
*/
public interface Configuration {
// Static methods
/**
* Return a mutable, empty builder for a {@code Configuration}.
* @return the builder
*/
static Builder builder() {
return new DefaultConfigurationBuilder();
}
/**
* Return a mutable builder for a {@code Configuration} with a default initialization.
* @return the builder
*/
static Builder defaultBuilder() {
DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
builder.defaultConfiguration();
return builder;
}
/**
* Return a mutable builder based on the given {@linkplain ApplicationContext application context}.
* The returned builder will search for all {@link HttpMessageReader}, {@link HttpMessageWriter},
* and {@link ViewResolver} instances in the given application context and return them for
* {@link #messageReaders()}, {@link #messageWriters()}, and {@link #viewResolvers()} in the
* built configuration respectively.
* @param applicationContext the application context to base the configuration on
* @return the builder
*/
static Builder applicationContext(ApplicationContext applicationContext) {
Assert.notNull(applicationContext, "'applicationContext' must not be null");
DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
builder.applicationContext(applicationContext);
return builder;
}
// Instance methods
/**
* Supply a {@linkplain Stream stream} of {@link HttpMessageReader}s to be used for request
* body conversion.
* @return the stream of message readers
*/
Supplier<Stream<HttpMessageReader<?>>> messageReaders();
/**
* Supply a {@linkplain Stream stream} of {@link HttpMessageWriter}s to be used for response
* body conversion.
* @return the stream of message writers
*/
Supplier<Stream<HttpMessageWriter<?>>> messageWriters();
/**
* Supply a {@linkplain Stream stream} of {@link ViewResolver}s to be used for view name
* resolution.
* @return the stream of view resolvers
*/
Supplier<Stream<ViewResolver>> viewResolvers();
/**
* A mutable builder for a {@link Configuration}.
*/
interface Builder {
/**
* Add the given message reader to this builder.
* @param messageReader the message reader to add
* @return this builder
*/
Builder messageReader(HttpMessageReader<?> messageReader);
/**
* Add the given message writer to this builder.
* @param messageWriter the message writer to add
* @return this builder
*/
Builder messageWriter(HttpMessageWriter<?> messageWriter);
/**
* Add the given view resolver to this builder.
* @param viewResolver the view resolver to add
* @return this builder
*/
Builder viewResolver(ViewResolver viewResolver);
/**
* Builds the {@link Configuration}.
* @return the built configuration
*/
Configuration build();
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright 2002-2016 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
*
* http://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.web.reactive.function;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.core.codec.ByteBufferDecoder;
import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
import org.springframework.util.ClassUtils;
import org.springframework.web.reactive.result.view.ViewResolver;
/**
* A default implementation of configuration.
* @author Arjen Poutsma
*/
class DefaultConfiguration implements Router.Configuration {
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
DefaultConfiguration.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
DefaultConfiguration.class.getClassLoader());
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder", DefaultConfiguration.class.getClassLoader());
private final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
private final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
private final List<ViewResolver> viewResolvers = new ArrayList<>();
public DefaultConfiguration() {
this.messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
this.messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder()));
this.messageWriters.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
this.messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
if (jaxb2Present) {
this.messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
this.messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
}
if (jackson2Present) {
this.messageReaders.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
this.messageWriters.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));
}
}
@Override
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
return this.messageReaders::stream;
}
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return this.messageWriters::stream;
}
@Override
public Supplier<Stream<ViewResolver>> viewResolvers() {
return this.viewResolvers::stream;
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2002-2016 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
*
* http://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.web.reactive.function;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.context.ApplicationContext;
import org.springframework.core.codec.ByteBufferDecoder;
import org.springframework.core.codec.ByteBufferEncoder;
import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.http.codec.DecoderHttpMessageReader;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.reactive.result.view.ViewResolver;
/**
* @author Arjen Poutsma
* @since 5.0
*/
class DefaultConfigurationBuilder implements Configuration.Builder {
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
DefaultConfigurationBuilder.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
DefaultConfigurationBuilder.class.getClassLoader());
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder",
DefaultConfigurationBuilder.class.getClassLoader());
private final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
private final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
private final List<ViewResolver> viewResolvers = new ArrayList<>();
public void defaultConfiguration() {
messageReader(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
messageReader(new DecoderHttpMessageReader<>(new StringDecoder()));
messageWriter(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
messageWriter(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
if (jaxb2Present) {
messageReader(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
messageWriter(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
}
if (jackson2Present) {
messageReader(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
messageWriter(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));
}
}
public void applicationContext(ApplicationContext applicationContext) {
applicationContext.getBeansOfType(HttpMessageReader.class).values().forEach(this::messageReader);
applicationContext.getBeansOfType(HttpMessageWriter.class).values().forEach(this::messageWriter);
applicationContext.getBeansOfType(ViewResolver.class).values().forEach(this::viewResolver);
}
@Override
public Configuration.Builder messageReader(HttpMessageReader<?> messageReader) {
Assert.notNull(messageReader, "'messageReader' must not be null");
this.messageReaders.add(messageReader);
return this;
}
@Override
public Configuration.Builder messageWriter(HttpMessageWriter<?> messageWriter) {
Assert.notNull(messageWriter, "'messageWriter' must not be null");
this.messageWriters.add(messageWriter);
return this;
}
@Override
public Configuration.Builder viewResolver(ViewResolver viewResolver) {
Assert.notNull(viewResolver, "'viewResolver' must not be null");
this.viewResolvers.add(viewResolver);
return this;
}
@Override
public Configuration build() {
return new DefaultConfiguration(this.messageReaders, this.messageWriters,
this.viewResolvers);
}
private static class DefaultConfiguration implements Configuration {
private final List<HttpMessageReader<?>> messageReaders;
private final List<HttpMessageWriter<?>> messageWriters;
private final List<ViewResolver> viewResolvers;
public DefaultConfiguration(
List<HttpMessageReader<?>> messageReaders,
List<HttpMessageWriter<?>> messageWriters,
List<ViewResolver> viewResolvers) {
this.messageReaders = unmodifiableCopy(messageReaders);
this.messageWriters = unmodifiableCopy(messageWriters);
this.viewResolvers = unmodifiableCopy(viewResolvers);
}
private static <T> List<T> unmodifiableCopy(List<? extends T> list) {
return Collections.unmodifiableList(new ArrayList<>(list));
}
@Override
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
return this.messageReaders::stream;
}
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return this.messageWriters::stream;
}
@Override
public Supplier<Stream<ViewResolver>> viewResolvers() {
return this.viewResolvers::stream;
}
}
}

View File

@@ -95,7 +95,7 @@ class DefaultRequest implements Request {
@Override
public Map<String, String> pathVariables() {
return this.exchange.<Map<String, String>>getAttribute(Router.URI_TEMPLATE_VARIABLES_ATTRIBUTE).
return this.exchange.<Map<String, String>>getAttribute(RoutingFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE).
orElseGet(Collections::emptyMap);
}
@@ -186,13 +186,14 @@ class DefaultRequest implements Request {
Function<HttpMessageReader<T>, S> readerFunction) {
ResolvableType elementType = ResolvableType.forClass(targetClass);
MediaType contentType = headers.contentType().orElse(MediaType.APPLICATION_OCTET_STREAM);
return messageReaderStream(exchange)
Supplier<Stream<HttpMessageReader<?>>> messageReaderStream = configuration(exchange).messageReaders();
return messageReaderStream.get()
.filter(r -> r.canRead(elementType, contentType, Collections.emptyMap()))
.findFirst()
.map(CastingUtils::<T>cast)
.map(readerFunction)
.orElseGet(() -> {
List<MediaType> supportedMediaTypes = messageReaderStream(exchange)
List<MediaType> supportedMediaTypes = messageReaderStream.get()
.flatMap(messageReader -> messageReader.getReadableMediaTypes().stream())
.collect(Collectors.toList());
return cast(
@@ -200,10 +201,11 @@ class DefaultRequest implements Request {
});
}
private Stream<HttpMessageReader<?>> messageReaderStream(ServerWebExchange exchange) {
return exchange.<Supplier<Stream<HttpMessageReader<?>>>>getAttribute(Router.HTTP_MESSAGE_READERS_ATTRIBUTE)
.orElseThrow(() -> new IllegalStateException("Could not find HttpMessageReaders in ServerWebExchange"))
.get();
private Configuration configuration(ServerWebExchange exchange) {
return exchange.<Configuration>getAttribute(
RoutingFunctions.CONFIGURATION_ATTRIBUTE)
.orElseThrow(() -> new IllegalStateException(
"Could not find Configuration in ServerWebExchange"));
}
@SuppressWarnings("unchecked")

View File

@@ -55,9 +55,9 @@ abstract class DefaultResponses {
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
DefaultConfiguration.class.getClassLoader()) &&
DefaultResponses.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
DefaultConfiguration.class.getClassLoader());
DefaultResponses.class.getClassLoader());
public static Response<Void> empty(int statusCode, HttpHeaders headers) {
@@ -134,7 +134,8 @@ abstract class DefaultResponses {
exchange -> {
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
Locale locale = Locale.ENGLISH; // TODO: resolve locale
return Flux.fromStream(viewResolverStream(exchange))
Stream<ViewResolver> viewResolverStream = configuration(exchange).viewResolvers().get();
return Flux.fromStream(viewResolverStream)
.concatMap(viewResolver -> viewResolver.resolveViewName(name, locale))
.next()
.otherwiseIfEmpty(Mono.error(new IllegalArgumentException("Could not resolve view with name '" + name +"'")))
@@ -153,9 +154,12 @@ abstract class DefaultResponses {
private static <T> Mono<Void> writeWithMessageWriters(ServerWebExchange exchange,
Publisher<T> body,
ResolvableType bodyType) {
// TODO: use ContentNegotiatingResultHandlerSupport
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
ServerHttpResponse response = exchange.getResponse();
return messageWriterStream(exchange)
Stream<HttpMessageWriter<?>> messageWriterStream = configuration(exchange).messageWriters().get();
return messageWriterStream
.filter(messageWriter -> messageWriter.canWrite(bodyType, contentType, Collections
.emptyMap()))
.findFirst()
@@ -168,20 +172,11 @@ abstract class DefaultResponses {
});
}
private static Stream<HttpMessageWriter<?>> messageWriterStream(ServerWebExchange exchange) {
return exchange.<Supplier<Stream<HttpMessageWriter<?>>>>getAttribute(
Router.HTTP_MESSAGE_WRITERS_ATTRIBUTE)
private static Configuration configuration(ServerWebExchange exchange) {
return exchange.<Configuration>getAttribute(
RoutingFunctions.CONFIGURATION_ATTRIBUTE)
.orElseThrow(() -> new IllegalStateException(
"Could not find HttpMessageWriters in ServerWebExchange"))
.get();
}
private static Stream<ViewResolver> viewResolverStream(ServerWebExchange exchange) {
return exchange.<Supplier<Stream<ViewResolver>>>getAttribute(
Router.VIEW_RESOLVERS_ATTRIBUTE)
.orElseThrow(() -> new IllegalStateException(
"Could not find ViewResolvers in ServerWebExchange"))
.get();
"Could not find Configuration in ServerWebExchange"));
}

View File

@@ -32,6 +32,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange;
import org.springframework.http.MediaType;
import org.springframework.web.client.reactive.BodyExtractor;
/**
* Represents an HTTP request, as handled by a {@linkplain HandlerFunction handler function}.
@@ -68,6 +69,7 @@ public interface Request {
* Return the body of this request.
*/
Body body();
// <T> T body(BodyExtractor<T> extractor);
/**
* Return the request attribute value if present.

View File

@@ -25,8 +25,8 @@ import org.springframework.util.Assert;
* @author Arjen Poutsma
* @since 5.0
* @see RequestPredicates
* @see Router#route(RequestPredicate, HandlerFunction)
* @see Router#subroute(RequestPredicate, RoutingFunction)
* @see RoutingFunctions#route(RequestPredicate, HandlerFunction)
* @see RoutingFunctions#subroute(RequestPredicate, RoutingFunction)
*/
@FunctionalInterface
public interface RequestPredicate {

View File

@@ -249,7 +249,7 @@ public abstract class RequestPredicates {
if (request instanceof DefaultRequest) {
DefaultRequest defaultRequest = (DefaultRequest) request;
Map<String, String> uriTemplateVariables = this.pathMatcher.extractUriTemplateVariables(this.pattern, path);
defaultRequest.exchange().getAttributes().put(Router.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
defaultRequest.exchange().getAttributes().put(RoutingFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
}
return true;
}

View File

@@ -306,6 +306,8 @@ public interface Response<T> {
*/
BodyBuilder contentType(MediaType contentType);
// <T> Response<T> body(BodyPopulator<T> populator);
/**
* Set the body of the response to the given object and return it.
*
@@ -322,6 +324,7 @@ public interface Response<T> {
* @return the built response
*/
<T, S extends Publisher<T>> Response<S> stream(S publisher, Class<T> elementClass);
// ResolvableType
/**
* Set the body of the response to the given {@link Resource} and return it.
@@ -350,6 +353,7 @@ public interface Response<T> {
* @return the built response
* @see <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events W3C recommendation</a>
*/
// remove?
<T, S extends Publisher<T>> Response<S> sse(S eventsPublisher, Class<T> eventClass);
/**

View File

@@ -18,18 +18,12 @@ package org.springframework.web.reactive.function;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.util.Assert;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
@@ -54,42 +48,27 @@ import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
* @author Arjen Poutsma
* @since 5.0
*/
public abstract class Router {
public abstract class RoutingFunctions {
private static final HandlerFunction<Void> NOT_FOUND_HANDLER = request -> Response.notFound().build();
/**
* Name of the {@link ServerWebExchange} attribute that contains the {@link Request}.
*/
public static final String REQUEST_ATTRIBUTE = Router.class.getName() + ".request";
public static final String REQUEST_ATTRIBUTE = RoutingFunctions.class.getName() + ".request";
/**
* Name of the {@link ServerWebExchange} attribute that contains a {@link Supplier} to the
* {@linkplain Stream stream} of {@link HttpMessageReader}s obtained
* from the {@linkplain Configuration#messageReaders() configuration}.
* Name of the {@link ServerWebExchange} attribute that contains the
* {@linkplain Configuration configuration} used throughout the routing and handling of the
* request.
*/
public static final String HTTP_MESSAGE_READERS_ATTRIBUTE = Router.class.getName() + ".httpMessageReaders";
/**
* Name of the {@link ServerWebExchange} attribute that contains a {@link Supplier} to the
* {@linkplain Stream stream} of {@link HttpMessageWriter}s obtained
* from the {@linkplain Configuration#messageWriters() configuration}.
*/
public static final String HTTP_MESSAGE_WRITERS_ATTRIBUTE = Router.class.getName() + ".httpMessageWriters";
public static final String CONFIGURATION_ATTRIBUTE = RoutingFunctions.class.getName() + ".configuration";
/**
* Name of the {@link ServerWebExchange} attribute that contains the URI
* templates map, mapping variable names to values.
*/
public static final String URI_TEMPLATE_VARIABLES_ATTRIBUTE = Router.class.getName() + ".uriTemplateVariables";
/**
* Name of the {@link ServerWebExchange} attribute that contains the {@link Supplier} to the
* {@linkplain Stream stream} of {@link ViewResolver}s obtained
* from the {@linkplain Configuration#viewResolvers() configuration}.
*/
public static final String VIEW_RESOLVERS_ATTRIBUTE = Router.class.getName() + ".viewResolvers";
public static final String URI_TEMPLATE_VARIABLES_ATTRIBUTE = RoutingFunctions.class.getName() + ".uriTemplateVariables";
/**
* Route to the given handler function if the given request predicate applies.
@@ -133,7 +112,7 @@ public abstract class Router {
/**
* Converts the given {@linkplain RoutingFunction routing function} into a {@link HttpHandler}.
* This conversion uses the {@linkplain #defaultConfiguration() default configuration}.
* This conversion uses the {@linkplain Configuration#defaultBuilder() default configuration}.
*
* <p>The returned {@code HttpHandler} can be adapted to run in
* {@linkplain org.springframework.http.server.reactive.ServletHttpHandlerAdapter Servlet 3.1+},
@@ -178,7 +157,7 @@ public abstract class Router {
/**
* Converts the given {@linkplain RoutingFunction routing function} into a {@link HandlerMapping}.
* This conversion uses the {@linkplain #defaultConfiguration() default configuration}.
* This conversion uses the {@linkplain Configuration#defaultBuilder() default configuration}.
*
* <p>The returned {@code HttpHandler} can be run in a
* {@link org.springframework.web.reactive.DispatcherHandler}.
@@ -217,13 +196,15 @@ public abstract class Router {
};
}
private static Configuration defaultConfiguration() {
return Configuration.defaultBuilder().build();
}
private static void addAttributes(ServerWebExchange exchange, Request request,
Configuration configuration) {
Map<String, Object> attributes = exchange.getAttributes();
attributes.put(REQUEST_ATTRIBUTE, request);
attributes.put(HTTP_MESSAGE_READERS_ATTRIBUTE, configuration.messageReaders());
attributes.put(HTTP_MESSAGE_WRITERS_ATTRIBUTE, configuration.messageWriters());
attributes.put(VIEW_RESOLVERS_ATTRIBUTE, configuration.viewResolvers());
attributes.put(CONFIGURATION_ATTRIBUTE, configuration);
}
@SuppressWarnings("unchecked")
@@ -231,69 +212,4 @@ public abstract class Router {
return (HandlerFunction<T>) NOT_FOUND_HANDLER;
}
/**
* Return the default configuration.
*/
public static Configuration defaultConfiguration() {
return new DefaultConfiguration();
}
/**
* Returns a configuration based on the given {@linkplain ApplicationContext application context}.
* This configuration will search for all {@link HttpMessageReader} and {@link HttpMessageWriter}
* instances in the given application context.
* @param applicationContext the application context to base the configuration on
* @return the configuration
*/
public static Configuration toConfiguration(ApplicationContext applicationContext) {
return new Configuration() {
@Override
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
return () -> applicationContext.getBeansOfType(HttpMessageReader.class).values().stream()
.map(CastingUtils::cast);
}
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return () -> applicationContext.getBeansOfType(HttpMessageWriter.class).values().stream()
.map(CastingUtils::cast);
}
@Override
public Supplier<Stream<ViewResolver>> viewResolvers() {
return () -> applicationContext.getBeansOfType(ViewResolver.class).values().stream();
}
};
}
/**
* Defines the configuration to be used by this {@code Router}.
*/
public interface Configuration {
/**
* Supply a {@linkplain Stream stream} of {@link HttpMessageReader}s to be used for request
* body conversion.
* @return the stream of message readers
*/
Supplier<Stream<HttpMessageReader<?>>> messageReaders();
/**
* Supply a {@linkplain Stream stream} of {@link HttpMessageWriter}s to be used for response
* body conversion.
* @return the stream of message writers
*/
Supplier<Stream<HttpMessageWriter<?>>> messageWriters();
/**
* Supply a {@linkplain Stream stream} of {@link ViewResolver}s to be used for view name
* resolution.
* @return the stream of view resolvers
*/
Supplier<Stream<ViewResolver>> viewResolvers();
}
}

View File

@@ -26,7 +26,7 @@ import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.function.HandlerFunction;
import org.springframework.web.reactive.function.Request;
import org.springframework.web.reactive.function.Response;
import org.springframework.web.reactive.function.Router;
import org.springframework.web.reactive.function.RoutingFunctions;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -58,7 +58,7 @@ public class HandlerFunctionAdapter implements HandlerAdapter {
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
Request request =
exchange.<Request>getAttribute(Router.REQUEST_ATTRIBUTE)
exchange.<Request>getAttribute(RoutingFunctions.REQUEST_ATTRIBUTE)
.orElseThrow(() -> new IllegalStateException("Could not find Request in exchange attributes"));
Response<?> response = handlerFunction.handle(request);

View File

@@ -28,7 +28,7 @@ public abstract class AbstractRoutingFunctionIntegrationTests
@Override
protected final HttpHandler createHttpHandler() {
RoutingFunction<?> routingFunction = routingFunction();
return Router.toHttpHandler(routingFunction);
return RoutingFunctions.toHttpHandler(routingFunction);
}
protected abstract RoutingFunction<?> routingFunction();

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2002-2016 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
*
* http://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.web.reactive.function;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import static org.junit.Assert.assertTrue;
/**
* @author Arjen Poutsma
*/
public class ConfigurationTests {
@Test
public void toConfiguration() throws Exception {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("messageWriter", DummyMessageWriter.class);
applicationContext.registerSingleton("messageReader", DummyMessageReader.class);
applicationContext.refresh();
Configuration configuration = Configuration.toConfiguration(applicationContext);
assertTrue(configuration.messageReaders().get()
.allMatch(r -> r instanceof DummyMessageReader));
assertTrue(configuration.messageWriters().get()
.allMatch(r -> r instanceof DummyMessageWriter));
}
private static class DummyMessageWriter implements HttpMessageWriter<Object> {
@Override
public boolean canWrite(ResolvableType type, MediaType mediaType, Map<String, Object> hints) {
return false;
}
@Override
public List<MediaType> getWritableMediaTypes() {
return Collections.emptyList();
}
@Override
public Mono<Void> write(Publisher<?> inputStream, ResolvableType type,
MediaType contentType,
ReactiveHttpOutputMessage outputMessage,
Map<String, Object> hints) {
return Mono.empty();
}
}
private static class DummyMessageReader implements HttpMessageReader<Object> {
@Override
public boolean canRead(ResolvableType type, MediaType mediaType, Map<String, Object> hints) {
return false;
}
@Override
public List<MediaType> getReadableMediaTypes() {
return Collections.emptyList();
}
@Override
public Flux<Object> read(ResolvableType type, ReactiveHttpInputMessage inputMessage,
Map<String, Object> hints) {
return Flux.empty();
}
@Override
public Mono<Object> readMono(ResolvableType type, ReactiveHttpInputMessage inputMessage,
Map<String, Object> hints) {
return Mono.empty();
}
}
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2002-2016 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
*
* http://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.web.reactive.function;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* @author Arjen Poutsma
*/
public class DefaultConfigurationTests {
private DefaultConfiguration configuration = new DefaultConfiguration();
@Test
public void messageReaders() throws Exception {
assertEquals(4, configuration.messageReaders().get().count());
}
@Test
public void messageWriters() throws Exception {
assertEquals(4, configuration.messageWriters().get().count());
}
}

View File

@@ -115,7 +115,7 @@ public class DefaultRequestTests {
@Test
public void pathVariables() throws Exception {
Map<String, String> pathVariables = Collections.singletonMap("foo", "bar");
when(mockExchange.getAttribute(Router.URI_TEMPLATE_VARIABLES_ATTRIBUTE)).thenReturn(Optional.of(pathVariables));
when(mockExchange.getAttribute(RoutingFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE)).thenReturn(Optional.of(pathVariables));
assertEquals(pathVariables, defaultRequest.pathVariables());
}
@@ -161,9 +161,10 @@ public class DefaultRequestTests {
Set<HttpMessageReader<?>> messageReaders = Collections
.singleton(new DecoderHttpMessageReader<String>(new StringDecoder()));
when(mockExchange.getAttribute(Router.HTTP_MESSAGE_READERS_ATTRIBUTE))
.thenReturn(Optional.of(
(Supplier<Stream<HttpMessageReader<?>>>) messageReaders::stream));
Configuration mockConfig = mock(Configuration.class);
when(mockConfig.messageReaders()).thenReturn(messageReaders::stream);
when(mockExchange.getAttribute(RoutingFunctions.CONFIGURATION_ATTRIBUTE))
.thenReturn(Optional.of(mockConfig));
assertEquals(body, defaultRequest.body().stream());

View File

@@ -18,7 +18,9 @@ package org.springframework.web.reactive.function;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -218,11 +220,13 @@ public class DefaultResponseBuilderTests {
MockServerHttpResponse response = new MockServerHttpResponse();
ServerWebExchange exchange =
new DefaultServerWebExchange(request, response, new MockWebSessionManager());
Set<HttpMessageWriter<?>>
messageWriters = Collections
.singleton(new EncoderHttpMessageWriter<CharSequence>(new CharSequenceEncoder()));
exchange.getAttributes().put(Router.HTTP_MESSAGE_WRITERS_ATTRIBUTE,
(Supplier<Stream<HttpMessageWriter<?>>>) messageWriters::stream);
List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<CharSequence>(new CharSequenceEncoder()));
Configuration mockConfig = mock(Configuration.class);
when(mockConfig.messageWriters()).thenReturn(messageWriters::stream);
exchange.getAttributes().put(RoutingFunctions.CONFIGURATION_ATTRIBUTE, mockConfig);
result.writeTo(exchange).block();
assertNotNull(response.getBody());
@@ -239,11 +243,13 @@ public class DefaultResponseBuilderTests {
MockServerHttpResponse response = new MockServerHttpResponse();
ServerWebExchange exchange =
new DefaultServerWebExchange(request, response, new MockWebSessionManager());
Set<HttpMessageWriter<?>>
messageWriters = Collections
.singleton(new EncoderHttpMessageWriter<CharSequence>(new CharSequenceEncoder()));
exchange.getAttributes().put(Router.HTTP_MESSAGE_WRITERS_ATTRIBUTE,
(Supplier<Stream<HttpMessageWriter<?>>>) messageWriters::stream);
List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<CharSequence>(new CharSequenceEncoder()));
Configuration mockConfig = mock(Configuration.class);
when(mockConfig.messageWriters()).thenReturn(messageWriters::stream);
exchange.getAttributes().put(RoutingFunctions.CONFIGURATION_ATTRIBUTE, mockConfig);
result.writeTo(exchange).block();
assertEquals(HttpStatus.NOT_ACCEPTABLE, response.getStatusCode());
@@ -259,10 +265,13 @@ public class DefaultResponseBuilderTests {
MockServerHttpResponse response = new MockServerHttpResponse();
ServerWebExchange exchange =
new DefaultServerWebExchange(request, response, new MockWebSessionManager());
Set<HttpMessageWriter<?>> messageWriters = Collections
.singleton(new EncoderHttpMessageWriter<CharSequence>(new CharSequenceEncoder()));
exchange.getAttributes().put(Router.HTTP_MESSAGE_WRITERS_ATTRIBUTE,
(Supplier<Stream<HttpMessageWriter<?>>>) messageWriters::stream);
List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
messageWriters.add(new EncoderHttpMessageWriter<CharSequence>(new CharSequenceEncoder()));
Configuration mockConfig = mock(Configuration.class);
when(mockConfig.messageWriters()).thenReturn(messageWriters::stream);
exchange.getAttributes().put(RoutingFunctions.CONFIGURATION_ATTRIBUTE, mockConfig);
result.writeTo(exchange).block();
assertNotNull(response.getBody());
@@ -311,10 +320,13 @@ public class DefaultResponseBuilderTests {
View view = mock(View.class);
when(viewResolver.resolveViewName("view", Locale.ENGLISH)).thenReturn(Mono.just(view));
when(view.render(model, null, exchange)).thenReturn(Mono.empty());
exchange.getAttributes().put(Router.VIEW_RESOLVERS_ATTRIBUTE,
(Supplier<Stream<ViewResolver>>) () -> Collections
.singleton(viewResolver).stream());
List<ViewResolver> viewResolvers = new ArrayList<>();
viewResolvers.add(viewResolver);
Configuration mockConfig = mock(Configuration.class);
when(mockConfig.viewResolvers()).thenReturn(viewResolvers::stream);
exchange.getAttributes().put(RoutingFunctions.CONFIGURATION_ATTRIBUTE, mockConfig);
result.writeTo(exchange).block();
}

View File

@@ -50,7 +50,7 @@ import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.junit.Assert.assertEquals;
import static org.springframework.web.reactive.function.Router.route;
import static org.springframework.web.reactive.function.RoutingFunctions.route;
/**
* Tests the use of {@link HandlerFunction} and {@link RoutingFunction} in a
@@ -119,8 +119,8 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
@Bean
public HandlerMapping handlerMapping(RoutingFunction<?> routingFunction,
ApplicationContext applicationContext) {
return Router.toHandlerMapping(routingFunction,
new Router.Configuration() {
return RoutingFunctions.toHandlerMapping(routingFunction,
new org.springframework.web.reactive.function.Configuration() {
@Override
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
return () -> getMessageReaders().stream();

View File

@@ -35,7 +35,7 @@ import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.assertEquals;
import static org.springframework.web.reactive.function.RequestPredicates.GET;
import static org.springframework.web.reactive.function.RequestPredicates.POST;
import static org.springframework.web.reactive.function.Router.route;
import static org.springframework.web.reactive.function.RoutingFunctions.route;
/**
* @author Arjen Poutsma

View File

@@ -57,7 +57,7 @@ public class RouterTests {
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(true);
RoutingFunction<Void> result = Router.route(requestPredicate, handlerFunction);
RoutingFunction<Void> result = RoutingFunctions.route(requestPredicate, handlerFunction);
assertNotNull(result);
Optional<HandlerFunction<Void>> resultHandlerFunction = result.route(request);
@@ -73,7 +73,7 @@ public class RouterTests {
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(false);
RoutingFunction<Void> result = Router.route(requestPredicate, handlerFunction);
RoutingFunction<Void> result = RoutingFunctions.route(requestPredicate, handlerFunction);
assertNotNull(result);
Optional<HandlerFunction<Void>> resultHandlerFunction = result.route(request);
@@ -89,7 +89,7 @@ public class RouterTests {
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(true);
RoutingFunction<Void> result = Router.subroute(requestPredicate, routingFunction);
RoutingFunction<Void> result = RoutingFunctions.subroute(requestPredicate, routingFunction);
assertNotNull(result);
Optional<HandlerFunction<Void>> resultHandlerFunction = result.route(request);
@@ -106,7 +106,7 @@ public class RouterTests {
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(false);
RoutingFunction<Void> result = Router.subroute(requestPredicate, routingFunction);
RoutingFunction<Void> result = RoutingFunctions.subroute(requestPredicate, routingFunction);
assertNotNull(result);
Optional<HandlerFunction<Void>> resultHandlerFunction = result.route(request);
@@ -128,7 +128,7 @@ public class RouterTests {
RequestPredicate requestPredicate = mock(RequestPredicate.class);
when(requestPredicate.test(request)).thenReturn(false);
Router.Configuration configuration = mock(Router.Configuration.class);
Configuration configuration = mock(Configuration.class);
when(configuration.messageReaders()).thenReturn(
() -> Collections.<HttpMessageReader<?>>emptyList().stream());
when(configuration.messageWriters()).thenReturn(
@@ -136,72 +136,13 @@ public class RouterTests {
when(configuration.viewResolvers()).thenReturn(
() -> Collections.<ViewResolver>emptyList().stream());
HttpHandler result = Router.toHttpHandler(routingFunction, configuration);
HttpHandler result = RoutingFunctions.toHttpHandler(routingFunction, configuration);
assertNotNull(result);
MockServerHttpRequest httpRequest = new MockServerHttpRequest(HttpMethod.GET, "http://localhost");
MockServerHttpRequest httpRequest =
new MockServerHttpRequest(HttpMethod.GET, "http://localhost");
MockServerHttpResponse serverHttpResponse = new MockServerHttpResponse();
result.handle(httpRequest, serverHttpResponse);
}
@Test
public void toConfiguration() throws Exception {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("messageWriter", DummyMessageWriter.class);
applicationContext.registerSingleton("messageReader", DummyMessageReader.class);
applicationContext.refresh();
Router.Configuration configuration = Router.toConfiguration(applicationContext);
assertTrue(configuration.messageReaders().get()
.allMatch(r -> r instanceof DummyMessageReader));
assertTrue(configuration.messageWriters().get()
.allMatch(r -> r instanceof DummyMessageWriter));
}
private static class DummyMessageWriter implements HttpMessageWriter<Object> {
@Override
public boolean canWrite(ResolvableType elementType, MediaType mediaType, Map<String, Object> hints) {
return false;
}
@Override
public List<MediaType> getWritableMediaTypes() {
return Collections.emptyList();
}
@Override
public Mono<Void> write(Publisher<?> inputStream, ResolvableType elementType,
MediaType mediaType,
ReactiveHttpOutputMessage outputMessage,
Map<String, Object> hints) {
return Mono.empty();
}
}
private static class DummyMessageReader implements HttpMessageReader<Object> {
@Override
public boolean canRead(ResolvableType elementType, MediaType mediaType, Map<String, Object> hints) {
return false;
}
@Override
public List<MediaType> getReadableMediaTypes() {
return Collections.emptyList();
}
@Override
public Flux<Object> read(ResolvableType elementType, ReactiveHttpInputMessage inputMessage,
Map<String, Object> hints) {
return Flux.empty();
}
@Override
public Mono<Object> readMono(ResolvableType elementType, ReactiveHttpInputMessage inputMessage,
Map<String, Object> hints) {
return Mono.empty();
}
}
}
}

View File

@@ -32,7 +32,7 @@ import org.springframework.web.client.reactive.WebClient;
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.get;
import static org.springframework.web.client.reactive.ResponseExtractors.bodyStream;
import static org.springframework.web.reactive.function.Router.route;
import static org.springframework.web.reactive.function.RoutingFunctions.route;
/**
* @author Arjen Poutsma