@Nullable all the way: null-safety at field level

This commits extends nullability declarations to the field level, formalizing the interaction between methods and their underlying fields and therefore avoiding any nullability mismatch.

Issue: SPR-15720
This commit is contained in:
Juergen Hoeller
2017-06-30 01:53:45 +02:00
parent c4694c3f5c
commit cc74a2891a
936 changed files with 6090 additions and 2806 deletions

View File

@@ -37,6 +37,7 @@ import org.springframework.web.server.ServerWebExchange;
*/
public class BindingContext {
@Nullable
private final WebBindingInitializer initializer;
private final Model model = new BindingAwareConcurrentModel();

View File

@@ -36,12 +36,14 @@ public class HandlerResult {
private final Object handler;
@Nullable
private final Object returnValue;
private final ResolvableType returnType;
private final BindingContext bindingContext;
@Nullable
private Function<Throwable, Mono<HandlerResult>> exceptionHandler;
@@ -144,7 +146,7 @@ public class HandlerResult {
* @return the new result or the same error if there is no exception handler
*/
public Mono<HandlerResult> applyExceptionHandler(Throwable failure) {
return (hasExceptionHandler() ? this.exceptionHandler.apply(failure) : Mono.error(failure));
return (this.exceptionHandler != null ? this.exceptionHandler.apply(failure) : Mono.error(failure));
}
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.accept;
import java.util.ArrayList;
@@ -25,7 +26,7 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
/**
* Builder for a composite {@link RequestedContentTypeResolver} that delegates
@@ -113,9 +114,9 @@ public class RequestedContentTypeResolverBuilder {
private final Map<String, MediaType> mediaTypes = new HashMap<>();
@Nullable
private String parameterName;
/**
* Configure a mapping between a lookup key (extracted from a query
* parameter value) and a corresponding {@code MediaType}.

View File

@@ -23,6 +23,7 @@ import org.springframework.cache.Cache;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.CacheControl;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.resource.ResourceWebHandler;
@@ -40,8 +41,10 @@ public class ResourceHandlerRegistration {
private final List<Resource> locations = new ArrayList<>();
@Nullable
private CacheControl cacheControl;
@Nullable
private ResourceChainRegistration resourceChainRegistration;
@@ -52,7 +55,8 @@ public class ResourceHandlerRegistration {
* @param pathPatterns one or more resource URL path patterns
*/
public ResourceHandlerRegistration(ResourceLoader resourceLoader, String... pathPatterns) {
Assert.notEmpty(pathPatterns, "At least one path pattern is required for resource handling.");
Assert.notNull(resourceLoader, "ResourceLoader is required");
Assert.notEmpty(pathPatterns, "At least one path pattern is required for resource handling");
this.resourceLoader = resourceLoader;
this.pathPatterns = pathPatterns;
}
@@ -74,7 +78,7 @@ public class ResourceHandlerRegistration {
*/
public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) {
for (String location : resourceLocations) {
this.locations.add(resourceLoader.getResource(location));
this.locations.add(this.resourceLoader.getResource(location));
}
return this;
}
@@ -82,7 +86,6 @@ public class ResourceHandlerRegistration {
/**
* Specify the {@link CacheControl} which should be used
* by the resource handler.
*
* @param cacheControl the CacheControl configuration to use
* @return the same {@link ResourceHandlerRegistration} instance, for
* chained method invocation
@@ -95,11 +98,9 @@ public class ResourceHandlerRegistration {
/**
* Configure a chain of resource resolvers and transformers to use. This
* can be useful, for example, to apply a version strategy to resource URLs.
*
* <p>If this method is not invoked, by default only a simple
* {@code PathResourceResolver} is used in order to match URL paths to
* resources under the configured locations.
*
* @param cacheResources whether to cache the result of resource resolution;
* setting this to "true" is recommended for production (and "false" for
* development, especially when applying a version strategy)
@@ -114,11 +115,9 @@ public class ResourceHandlerRegistration {
/**
* Configure a chain of resource resolvers and transformers to use. This
* can be useful, for example, to apply a version strategy to resource URLs.
*
* <p>If this method is not invoked, by default only a simple
* {@code PathResourceResolver} is used in order to match URL paths to
* resources under the configured locations.
*
* @param cacheResources whether to cache the result of resource resolution;
* setting this to "true" is recommended for production (and "false" for
* development, especially when applying a version strategy

View File

@@ -23,9 +23,8 @@ import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ResourceLoader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.handler.AbstractUrlHandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.resource.ResourceWebHandler;
@@ -53,7 +52,7 @@ import org.springframework.web.server.WebHandler;
*/
public class ResourceHandlerRegistry {
private final ApplicationContext applicationContext;
private final ResourceLoader resourceLoader;
private final List<ResourceHandlerRegistration> registrations = new ArrayList<>();
@@ -61,13 +60,12 @@ public class ResourceHandlerRegistry {
/**
* Create a new resource handler registry for the given application context.
* @param applicationContext the Spring application context
* Create a new resource handler registry for the given resource loader
* (typically an application context).
* @param resourceLoader the resource loader to use
*/
public ResourceHandlerRegistry(ApplicationContext applicationContext) {
Assert.notNull(applicationContext, "ApplicationContext is required");
this.applicationContext = applicationContext;
public ResourceHandlerRegistry(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@@ -82,8 +80,7 @@ public class ResourceHandlerRegistry {
* configure the registered resource handler
*/
public ResourceHandlerRegistration addResourceHandler(String... patterns) {
ResourceHandlerRegistration registration =
new ResourceHandlerRegistration(this.applicationContext, patterns);
ResourceHandlerRegistration registration = new ResourceHandlerRegistration(this.resourceLoader, patterns);
this.registrations.add(registration);
return registration;
}
@@ -126,7 +123,7 @@ public class ResourceHandlerRegistry {
try {
handler.afterPropertiesSet();
}
catch (Exception ex) {
catch (Throwable ex) {
throw new BeanInitializationException("Failed to init ResourceHttpRequestHandler", ex);
}
urlMap.put(pathPattern, handler);

View File

@@ -24,7 +24,7 @@ import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.result.view.HttpMessageWriterView;
import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
@@ -46,17 +46,18 @@ import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewRes
*/
public class ViewResolverRegistry {
@Nullable
private final ApplicationContext applicationContext;
private final List<ViewResolver> viewResolvers = new ArrayList<>(4);
private final List<View> defaultViews = new ArrayList<>(4);
@Nullable
private Integer order;
public ViewResolverRegistry(ApplicationContext applicationContext) {
Assert.notNull(applicationContext, "ApplicationContext must not be null");
public ViewResolverRegistry(@Nullable ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@@ -67,7 +68,7 @@ public class ViewResolverRegistry {
* adding a {@link FreeMarkerConfigurer} bean.
*/
public UrlBasedViewResolverRegistration freeMarker() {
if (this.applicationContext != null && !hasBeanOfType(FreeMarkerConfigurer.class)) {
if (!checkBeanOfType(FreeMarkerConfigurer.class)) {
throw new BeanInitializationException("In addition to a FreeMarker view resolver " +
"there must also be a single FreeMarkerConfig bean in this web application context " +
"(or its parent): FreeMarkerConfigurer is the usual implementation. " +
@@ -82,11 +83,6 @@ public class ViewResolverRegistry {
return registration;
}
protected boolean hasBeanOfType(Class<?> beanType) {
return !ObjectUtils.isEmpty(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.applicationContext, beanType, false, false));
}
/**
* Register a {@link ViewResolver} bean instance. This may be useful to
* configure a 3rd party resolver implementation or as an alternative to
@@ -126,6 +122,13 @@ public class ViewResolverRegistry {
this.order = order;
}
private boolean checkBeanOfType(Class<?> beanType) {
return (this.applicationContext == null ||
!ObjectUtils.isEmpty(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.applicationContext, beanType, false, false)));
}
protected int getOrder() {
return (this.order != null ? this.order : Ordered.LOWEST_PRECEDENCE);
}

View File

@@ -29,6 +29,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.support.DefaultFormattingConversionService;
@@ -58,11 +60,11 @@ import org.springframework.web.reactive.result.method.annotation.ResponseBodyRes
import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler;
import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.i18n.LocaleContextResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import org.springframework.web.server.i18n.LocaleContextResolver;
/**
* The main class for Spring WebFlux configuration.
@@ -74,12 +76,16 @@ import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
*/
public class WebFluxConfigurationSupport implements ApplicationContextAware {
@Nullable
private Map<String, CorsConfiguration> corsConfigurations;
@Nullable
private PathMatchConfigurer pathMatchConfigurer;
@Nullable
private ViewResolverRegistry viewResolverRegistry;
@Nullable
private ApplicationContext applicationContext;
@@ -88,7 +94,8 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
this.applicationContext = applicationContext;
}
protected ApplicationContext getApplicationContext() {
@Nullable
public final ApplicationContext getApplicationContext() {
return this.applicationContext;
}
@@ -205,7 +212,11 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
*/
@Bean
public HandlerMapping resourceHandlerMapping() {
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext);
ResourceLoader resourceLoader = this.applicationContext;
if (resourceLoader == null) {
resourceLoader = new DefaultResourceLoader();
}
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(resourceLoader);
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
@@ -427,7 +438,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
*/
protected final ViewResolverRegistry getViewResolverRegistry() {
if (this.viewResolverRegistry == null) {
this.viewResolverRegistry = new ViewResolverRegistry(getApplicationContext());
this.viewResolverRegistry = new ViewResolverRegistry(this.applicationContext);
configureViewResolvers(this.viewResolverRegistry);
}
return this.viewResolverRegistry;

View File

@@ -32,6 +32,7 @@ import org.springframework.lang.Nullable;
@SuppressWarnings("serial")
public class UnsupportedMediaTypeException extends NestedRuntimeException {
@Nullable
private final MediaType contentType;
private final List<MediaType> supportedMediaTypes;

View File

@@ -63,8 +63,10 @@ class DefaultWebClient implements WebClient {
private final UriBuilderFactory uriBuilderFactory;
@Nullable
private final HttpHeaders defaultHeaders;
@Nullable
private final MultiValueMap<String, String> defaultCookies;
private final DefaultWebClientBuilder builder;
@@ -170,10 +172,13 @@ class DefaultWebClient implements WebClient {
private final URI uri;
@Nullable
private HttpHeaders headers;
@Nullable
private MultiValueMap<String, String> cookies;
@Nullable
private BodyInserter<?, ? super ClientHttpRequest> inserter;
DefaultRequestBodySpec(HttpMethod httpMethod, URI uri) {
@@ -284,11 +289,9 @@ class DefaultWebClient implements WebClient {
@Override
public Mono<ClientResponse> exchange() {
ClientRequest request = this.inserter != null ?
ClientRequest request = (this.inserter != null ?
initRequestBuilder().body(this.inserter).build() :
initRequestBuilder().build();
initRequestBuilder().build());
return exchangeFunction.exchange(request);
}

View File

@@ -42,24 +42,33 @@ import org.springframework.web.util.UriBuilderFactory;
*/
class DefaultWebClientBuilder implements WebClient.Builder {
@Nullable
private String baseUrl;
@Nullable
private Map<String, ?> defaultUriVariables;
@Nullable
private UriBuilderFactory uriBuilderFactory;
@Nullable
private HttpHeaders defaultHeaders;
@Nullable
private MultiValueMap<String, String> defaultCookies;
@Nullable
private List<ExchangeFilterFunction> filters;
@Nullable
private ClientHttpConnector connector;
private ExchangeStrategies exchangeStrategies = ExchangeStrategies.withDefaults();
@Nullable
private ExchangeFunction exchangeFunction;
public DefaultWebClientBuilder() {
}
@@ -85,6 +94,7 @@ class DefaultWebClientBuilder implements WebClient.Builder {
this.exchangeFunction = other.exchangeFunction;
}
@Override
public WebClient.Builder baseUrl(String baseUrl) {
this.baseUrl = baseUrl;
@@ -105,9 +115,9 @@ class DefaultWebClientBuilder implements WebClient.Builder {
@Override
public WebClient.Builder defaultHeader(String headerName, String... headerValues) {
initHeaders();
HttpHeaders headers = initHeaders();
for (String headerValue : headerValues) {
this.defaultHeaders.add(headerName, headerValue);
headers.add(headerName, headerValue);
}
return this;
}
@@ -115,37 +125,35 @@ class DefaultWebClientBuilder implements WebClient.Builder {
@Override
public WebClient.Builder defaultHeaders(Consumer<HttpHeaders> headersConsumer) {
Assert.notNull(headersConsumer, "'headersConsumer' must not be null");
initHeaders();
headersConsumer.accept(this.defaultHeaders);
headersConsumer.accept(initHeaders());
return this;
}
private void initHeaders() {
private HttpHeaders initHeaders() {
if (this.defaultHeaders == null) {
this.defaultHeaders = new HttpHeaders();
}
return this.defaultHeaders;
}
@Override
public WebClient.Builder defaultCookie(String cookieName, String... cookieValues) {
initCookies();
this.defaultCookies.addAll(cookieName, Arrays.asList(cookieValues));
initCookies().addAll(cookieName, Arrays.asList(cookieValues));
return this;
}
@Override
public WebClient.Builder defaultCookies(
Consumer<MultiValueMap<String, String>> cookiesConsumer) {
Assert.notNull(cookiesConsumer, "'cookiesConsumer' must not be null");
initCookies();
cookiesConsumer.accept(this.defaultCookies);
public WebClient.Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer) {
Assert.notNull(cookiesConsumer, "Cookies consumer must not be null");
cookiesConsumer.accept(initCookies());
return this;
}
private void initCookies() {
private MultiValueMap<String, String> initCookies() {
if (this.defaultCookies == null) {
this.defaultCookies = new LinkedMultiValueMap<>(4);
}
return this.defaultCookies;
}
@Override
@@ -156,29 +164,28 @@ class DefaultWebClientBuilder implements WebClient.Builder {
@Override
public WebClient.Builder filter(ExchangeFilterFunction filter) {
Assert.notNull(filter, "'filter' must not be null");
initFilters();
this.filters.add(filter);
Assert.notNull(filter, "ExchangeFilterFunction must not be null");
initFilters().add(filter);
return this;
}
@Override
public WebClient.Builder filters(Consumer<List<ExchangeFilterFunction>> filtersConsumer) {
Assert.notNull(filtersConsumer, "'filtersConsumer' must not be null");
initFilters();
filtersConsumer.accept(this.filters);
Assert.notNull(filtersConsumer, "Filters consumer must not be null");
filtersConsumer.accept(initFilters());
return this;
}
private void initFilters() {
private List<ExchangeFilterFunction> initFilters() {
if (this.filters == null) {
this.filters = new ArrayList<>();
}
return this.filters;
}
@Override
public WebClient.Builder exchangeStrategies(ExchangeStrategies strategies) {
Assert.notNull(strategies, "'strategies' must not be null");
Assert.notNull(strategies, "ExchangeStrategies must not be null");
this.exchangeStrategies = strategies;
return this;
}

View File

@@ -392,6 +392,7 @@ public interface ServerResponse {
Mono<ServerResponse> render(String name, Map<String, ?> model);
}
/**
* Defines the context used during the {@link #writeTo(ServerWebExchange, Context)}.
*/
@@ -399,13 +400,13 @@ public interface ServerResponse {
/**
* Return the {@link HttpMessageWriter}s to be used for response body conversion.
* @return the stream of message writers
* @return the list of message writers
*/
List<HttpMessageWriter<?>> messageWriters();
/**
* Return the {@link ViewResolver}s to be used for view name resolution.
* @return the stream of view resolvers
* @return the list of view resolvers
*/
List<ViewResolver> viewResolvers();
}

View File

@@ -24,6 +24,8 @@ import reactor.core.publisher.Mono;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
@@ -43,10 +45,13 @@ import org.springframework.web.server.ServerWebExchange;
*/
public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean {
@Nullable
private RouterFunction<?> routerFunction;
@Nullable
private ServerCodecConfigurer messageCodecConfigurer;
/**
* Create an empty {@code RouterFunctionMapping}.
* <p>If this constructor is used, this mapping will detect all {@link RouterFunction} instances
@@ -64,6 +69,7 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
this.routerFunction = routerFunction;
}
/**
* Configure HTTP message readers to de-serialize the request body with.
* <p>By default this is set to {@link ServerCodecConfigurer} with defaults.
@@ -114,6 +120,7 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
if (this.routerFunction != null) {
Assert.state(this.messageCodecConfigurer != null, "No ServerCodecConfigurer set");
ServerRequest request = ServerRequest.create(exchange, this.messageCodecConfigurer.getReaders());
exchange.getAttributes().put(RouterFunctions.REQUEST_ATTRIBUTE, request);
return this.routerFunction.route(request);
@@ -123,8 +130,10 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
}
}
private static class SortedRouterFunctionsContainer {
@Nullable
private List<RouterFunction<?>> routerFunctions;
@Autowired(required = false)

View File

@@ -25,6 +25,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Ordered;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.HandlerResultHandler;
@@ -38,12 +39,12 @@ import org.springframework.web.server.ServerWebExchange;
* @author Arjen Poutsma
* @since 5.0
*/
public class ServerResponseResultHandler implements HandlerResultHandler, InitializingBean,
Ordered {
public class ServerResponseResultHandler implements HandlerResultHandler, InitializingBean, Ordered {
@Nullable
private ServerCodecConfigurer messageCodecConfigurer;
private List<ViewResolver> viewResolvers;
private List<ViewResolver> viewResolvers = Collections.emptyList();
private int order = LOWEST_PRECEDENCE;
@@ -79,10 +80,7 @@ public class ServerResponseResultHandler implements HandlerResultHandler, Initia
@Override
public void afterPropertiesSet() throws Exception {
if (this.messageCodecConfigurer == null) {
throw new IllegalArgumentException("'messageCodecConfigurer' is required");
}
if (this.viewResolvers == null) {
this.viewResolvers = Collections.emptyList();
throw new IllegalArgumentException("Property 'messageCodecConfigurer' is required");
}
}
@@ -98,9 +96,9 @@ public class ServerResponseResultHandler implements HandlerResultHandler, Initia
return response.writeTo(exchange, new ServerResponse.Context() {
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return messageCodecConfigurer.getWriters();
return (messageCodecConfigurer != null ?
messageCodecConfigurer.getWriters() : Collections.emptyList());
}
@Override
public List<ViewResolver> viewResolvers() {
return viewResolvers;

View File

@@ -16,6 +16,7 @@
package org.springframework.web.reactive.handler;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
@@ -50,11 +51,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
private boolean lazyInitHandlers = false;
@Nullable
private PathPatternRegistry<Object> patternRegistry;
public PathPatternRegistry<Object> getPatternRegistry() {
return patternRegistry;
}
/**
* Set whether to lazily initialize handlers. Only applicable to
@@ -76,7 +75,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
* as value.
*/
public final Map<PathPattern, Object> getHandlerMap() {
return this.patternRegistry.getPatternsMap();
return (this.patternRegistry != null ? this.patternRegistry.getPatternsMap() : Collections.emptyMap());
}
@@ -103,11 +102,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
/**
* Look up a handler instance for the given URL lookup path.
*
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and various path pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the PathPattern class.
*
* @param lookupPath URL the handler is mapped to
* @param exchange the current exchange
* @return the associated handler instance, or {@code null} if not found
@@ -115,19 +112,17 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
*/
@Nullable
protected Object lookupHandler(String lookupPath, ServerWebExchange exchange) throws Exception {
Optional<PathMatchResult<Object>> matches = this.patternRegistry.findFirstMatch(lookupPath);
if (matches.isPresent()) {
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + lookupPath + "] are " + matches);
if (this.patternRegistry != null) {
Optional<PathMatchResult<Object>> matches = this.patternRegistry.findFirstMatch(lookupPath);
if (matches.isPresent()) {
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + lookupPath + "] are " + matches);
}
PathMatchResult<Object> bestMatch = matches.get();
String pathWithinMapping = bestMatch.getPattern().extractPathWithinPattern(lookupPath);
Object handler = bestMatch.getHandler();
return handleMatch(handler, bestMatch.getPattern(), pathWithinMapping, exchange);
}
PathMatchResult<Object> bestMatch = matches.get();
String pathWithinMapping = bestMatch.getPattern().extractPathWithinPattern(lookupPath);
Object handler = bestMatch.getHandler();
if (handler == null) {
throw new IllegalStateException(
"Could not find handler for best pattern match [" + bestMatch + "]");
}
return handleMatch(handler, bestMatch.getPattern(), pathWithinMapping, exchange);
}
// No handler found...

View File

@@ -16,7 +16,6 @@
package org.springframework.web.reactive.handler;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.util.pattern.PathPattern;
@@ -37,8 +36,9 @@ public class PathMatchResult<T> {
private final T handler;
public PathMatchResult(PathPattern pattern, @Nullable T handler) {
public PathMatchResult(PathPattern pattern, T handler) {
Assert.notNull(pattern, "PathPattern must not be null");
Assert.notNull(handler, "Handler must not be null");
this.pattern = pattern;
this.handler = handler;
}
@@ -54,7 +54,6 @@ public class PathMatchResult<T> {
/**
* Return the request handler associated with the {@link PathPattern}.
*/
@Nullable
public T getHandler() {
return this.handler;
}

View File

@@ -164,14 +164,13 @@ public class AppCacheManifestTransformer extends ResourceTransformerSupport {
private final Scanner scanner;
@Nullable
private LineInfo previous;
public LineGenerator(String content) {
this.scanner = new Scanner(content);
}
@Override
public void accept(SynchronousSink<LineInfo> sink) {
if (this.scanner.hasNext()) {
@@ -186,6 +185,7 @@ public class AppCacheManifestTransformer extends ResourceTransformerSupport {
}
}
private static class LineInfo {
private final String line;
@@ -194,8 +194,7 @@ public class AppCacheManifestTransformer extends ResourceTransformerSupport {
private final boolean link;
public LineInfo(String line, LineInfo previousLine) {
public LineInfo(String line, @Nullable LineInfo previousLine) {
this.line = line;
this.cacheSection = initCacheSectionFlag(line, previousLine);
this.link = iniLinkFlag(line, this.cacheSection);
@@ -222,7 +221,6 @@ public class AppCacheManifestTransformer extends ResourceTransformerSupport {
return (line.startsWith("//") || (index > 0 && !line.substring(0, index).contains("/")));
}
public String getLine() {
return this.line;
}
@@ -236,13 +234,14 @@ public class AppCacheManifestTransformer extends ResourceTransformerSupport {
}
}
private static class LineOutput {
private final String line;
@Nullable
private final Resource resource;
public LineOutput(String line, @Nullable Resource resource) {
this.line = line;
this.resource = resource;
@@ -258,6 +257,7 @@ public class AppCacheManifestTransformer extends ResourceTransformerSupport {
}
}
private static class LineAggregator {
private final StringWriter writer = new StringWriter();
@@ -266,7 +266,6 @@ public class AppCacheManifestTransformer extends ResourceTransformerSupport {
private final Resource resource;
public LineAggregator(Resource resource, String content) {
this.resource = resource;
this.baos = new ByteArrayOutputStream(content.length());

View File

@@ -205,9 +205,6 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
logger.trace("Invoking ResourceResolverChain for URL pattern \"" + result.getPattern() + "\"");
}
ResourceWebHandler handler = result.getHandler();
if (handler == null) {
throw new IllegalStateException("No handler for URL pattern \"" + result.getPattern() + "\"");
}
ResourceResolverChain chain = new DefaultResourceResolverChain(handler.getResourceResolvers());
return chain.resolveUrlPath(pathWithinMapping, handler.getLocations())
.map(resolvedPath -> {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -20,17 +20,19 @@ import java.io.IOException;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
/**
* An extension of {@link ByteArrayResource}
* that a {@link ResourceTransformer} can use to represent an original
* resource preserving all other information except the content.
* An extension of {@link ByteArrayResource} that a {@link ResourceTransformer}
* can use to represent an original resource preserving all other information
* except the content.
*
* @author Rossen Stoyanchev
* @since 5.0
*/
public class TransformedResource extends ByteArrayResource {
@Nullable
private final String filename;
private final long lastModified;
@@ -50,6 +52,7 @@ public class TransformedResource extends ByteArrayResource {
@Override
@Nullable
public String getFilename() {
return this.filename;
}

View File

@@ -48,6 +48,7 @@ import org.springframework.web.util.pattern.PathPatternParser;
*/
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
@Nullable
private final String name;
private final PatternsRequestCondition patternsCondition;
@@ -392,18 +393,25 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
private String[] paths;
@Nullable
private RequestMethod[] methods;
@Nullable
private String[] params;
@Nullable
private String[] headers;
@Nullable
private String[] consumes;
@Nullable
private String[] produces;
@Nullable
private String mappingName;
@Nullable
private RequestCondition<?> customCondition;
private BuilderConfiguration options = new BuilderConfiguration();
@@ -492,19 +500,21 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
*/
public static class BuilderConfiguration {
@Nullable
private PathPatternParser patternParser;
@Nullable
private RequestedContentTypeResolver contentTypeResolver;
public void setPatternParser(PathPatternParser patternParser) {
this.patternParser = patternParser;
}
@Nullable
public PathPatternParser getPatternParser() {
return this.patternParser;
}
public void setPatternParser(PathPatternParser patternParser) {
this.patternParser = patternParser;
}
/**
* Set the ContentNegotiationManager to use for the ProducesRequestCondition.
* <p>By default this is not set.

View File

@@ -23,6 +23,7 @@ import java.util.stream.Collectors;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.HandlerResult;
@@ -93,6 +94,7 @@ public class SyncInvocableHandlerMethod extends HandlerMethod {
* @param providedArgs optional list of argument values to match by type
* @return Mono with a {@link HandlerResult}.
*/
@Nullable
public HandlerResult invokeForHandlerResult(ServerWebExchange exchange,
BindingContext bindingContext, Object... providedArgs) {

View File

@@ -60,8 +60,10 @@ import org.springframework.web.server.ServerWebInputException;
*/
public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodArgumentResolverSupport {
@Nullable
private final ConfigurableBeanFactory configurableBeanFactory;
@Nullable
private final BeanExpressionContext expressionContext;
private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);
@@ -154,7 +156,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
*/
@Nullable
private Object resolveStringValue(String value) {
if (this.configurableBeanFactory == null) {
if (this.configurableBeanFactory == null || this.expressionContext == null) {
return value;
}
String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
@@ -291,6 +293,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
private final boolean required;
@Nullable
private final String defaultValue;
public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {

View File

@@ -52,7 +52,7 @@ import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
import static org.springframework.core.MethodIntrospector.selectMethods;
import static org.springframework.core.MethodIntrospector.*;
/**
* Package-private class to assist {@link RequestMappingHandlerAdapter} with
@@ -218,7 +218,6 @@ class ControllerMethodResolver {
* or in the controller of the given {@code @RequestMapping} method.
*/
public List<SyncInvocableHandlerMethod> getInitBinderMethods(HandlerMethod handlerMethod) {
List<SyncInvocableHandlerMethod> result = new ArrayList<>();
Class<?> handlerType = handlerMethod.getBeanType();
@@ -251,7 +250,6 @@ class ControllerMethodResolver {
* components or in the controller of the given {@code @RequestMapping} method.
*/
public List<InvocableHandlerMethod> getModelAttributeMethods(HandlerMethod handlerMethod) {
List<InvocableHandlerMethod> result = new ArrayList<>();
Class<?> handlerType = handlerMethod.getBeanType();
@@ -331,18 +329,18 @@ class ControllerMethodResolver {
private final List<HandlerMethodArgumentResolver> customResolvers;
@Nullable
private final List<HttpMessageReader<?>> messageReaders;
private final boolean modelAttributeSupported;
private final List<HandlerMethodArgumentResolver> result = new ArrayList<>();
private ArgumentResolverRegistrar(ArgumentResolverConfigurer resolvers,
@Nullable ServerCodecConfigurer codecs, boolean modelAttribute) {
this.customResolvers = resolvers.getCustomResolvers();
this.messageReaders = codecs != null ? codecs.getReaders() : null;
this.messageReaders = (codecs != null ? codecs.getReaders() : null);
this.modelAttributeSupported = modelAttribute;
}
@@ -379,7 +377,6 @@ class ControllerMethodResolver {
.collect(Collectors.toList());
}
public static Builder configurer(ArgumentResolverConfigurer configurer) {
return new Builder(configurer);
}
@@ -389,12 +386,10 @@ class ControllerMethodResolver {
private final ArgumentResolverConfigurer resolvers;
public Builder(ArgumentResolverConfigurer configurer) {
this.resolvers = configurer;
}
public ArgumentResolverRegistrar fullSupport(ServerCodecConfigurer codecs) {
return new ArgumentResolverRegistrar(this.resolvers, codecs, true);
}
@@ -407,7 +402,6 @@ class ControllerMethodResolver {
return new ArgumentResolverRegistrar(this.resolvers, null, false);
}
}
}
}

View File

@@ -26,6 +26,7 @@ import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebExchangeDataBinder;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
import org.springframework.web.server.ServerWebExchange;
@@ -71,10 +72,10 @@ class InitBinderBindingContext extends BindingContext {
private void invokeBinderMethod(WebExchangeDataBinder dataBinder,
ServerWebExchange exchange, SyncInvocableHandlerMethod binderMethod) {
Object returnValue = binderMethod.invokeForHandlerResult(exchange, this.binderMethodContext, dataBinder)
.getReturnValue();
HandlerResult result = binderMethod.invokeForHandlerResult(
exchange, this.binderMethodContext, dataBinder);
if (returnValue != null) {
if (result != null && result.getReturnValue() != null) {
throw new IllegalStateException(
"@InitBinder methods should return void: " + binderMethod);
}

View File

@@ -50,18 +50,25 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
private static final Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class);
@Nullable
private ServerCodecConfigurer messageCodecConfigurer;
@Nullable
private WebBindingInitializer webBindingInitializer;
@Nullable
private ArgumentResolverConfigurer argumentResolverConfigurer;
@Nullable
private ReactiveAdapterRegistry reactiveAdapterRegistry;
@Nullable
private ConfigurableApplicationContext applicationContext;
@Nullable
private ControllerMethodResolver methodResolver;
@Nullable
private ModelInitializer modelInitializer;
@@ -76,6 +83,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
/**
* Return the configurer for HTTP message readers.
*/
@Nullable
public ServerCodecConfigurer getMessageCodecConfigurer() {
return this.messageCodecConfigurer;
}
@@ -107,6 +115,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
/**
* Return the configured resolvers for controller method arguments.
*/
@Nullable
public ArgumentResolverConfigurer getArgumentResolverConfigurer() {
return this.argumentResolverConfigurer;
}
@@ -123,6 +132,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
/**
* Return the configured registry for adapting reactive types.
*/
@Nullable
public ReactiveAdapterRegistry getReactiveAdapterRegistry() {
return this.reactiveAdapterRegistry;
}
@@ -139,22 +149,17 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
}
}
public ConfigurableApplicationContext getApplicationContext() {
return this.applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.applicationContext, "ApplicationContext is required");
if (this.messageCodecConfigurer == null) {
this.messageCodecConfigurer = ServerCodecConfigurer.create();
}
if (this.argumentResolverConfigurer == null) {
this.argumentResolverConfigurer = new ArgumentResolverConfigurer();
}
if (this.reactiveAdapterRegistry == null) {
this.reactiveAdapterRegistry = new ReactiveAdapterRegistry();
}
@@ -173,9 +178,8 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
@Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
Assert.notNull(handler, "Expected handler");
HandlerMethod handlerMethod = (HandlerMethod) handler;
Assert.state(this.methodResolver != null && this.modelInitializer != null, "Not initialized");
BindingContext bindingContext = new InitBinderBindingContext(
getWebBindingInitializer(), this.methodResolver.getInitBinderMethods(handlerMethod));
@@ -197,6 +201,8 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
private Mono<HandlerResult> handleException(Throwable ex, HandlerMethod handlerMethod,
BindingContext bindingContext, ServerWebExchange exchange) {
Assert.state(this.methodResolver != null, "Not initialized");
InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(ex, handlerMethod);
if (invocable != null) {
try {

View File

@@ -50,6 +50,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
private RequestedContentTypeResolver contentTypeResolver = new RequestedContentTypeResolverBuilder().build();
@Nullable
private StringValueResolver embeddedValueResolver;
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();

View File

@@ -62,8 +62,10 @@ public abstract class AbstractView implements View, ApplicationContextAware {
private Charset defaultCharset = StandardCharsets.UTF_8;
@Nullable
private String requestContextAttribute;
@Nullable
private ApplicationContext applicationContext;

View File

@@ -24,6 +24,7 @@ import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.context.NoSuchMessageException;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
@@ -51,25 +52,34 @@ public class BindStatus {
private final boolean htmlEscape;
@Nullable
private final String expression;
@Nullable
private final Errors errors;
@Nullable
private BindingResult bindingResult;
@Nullable
private Object value;
@Nullable
private Class<?> valueType;
@Nullable
private Object actualValue;
@Nullable
private PropertyEditor editor;
@Nullable
private List<? extends ObjectError> objectErrors;
private String[] errorCodes;
private String[] errorCodes = new String[0];
@Nullable
private String[] errorMessages;
@@ -244,14 +254,13 @@ public class BindStatus {
* Return if this status represents a field or object error.
*/
public boolean isError() {
return (this.errorCodes != null && this.errorCodes.length > 0);
return (this.errorCodes.length > 0);
}
/**
* Return the error codes for the field or object, if any.
* Returns an empty array instead of null if none.
*/
@Nullable
public String[] getErrorCodes() {
return this.errorCodes;
}
@@ -260,7 +269,7 @@ public class BindStatus {
* Return the first error codes for the field or object, if any.
*/
public String getErrorCode() {
return (this.errorCodes.length > 0 ? this.errorCodes[0] : "");
return (!ObjectUtils.isEmpty(this.errorCodes) ? this.errorCodes[0] : "");
}
/**
@@ -268,16 +277,15 @@ public class BindStatus {
* if any. Returns an empty array instead of null if none.
*/
public String[] getErrorMessages() {
initErrorMessages();
return this.errorMessages;
return initErrorMessages();
}
/**
* Return the first error message for the field or object, if any.
*/
public String getErrorMessage() {
initErrorMessages();
return (this.errorMessages.length > 0 ? this.errorMessages[0] : "");
String[] errorMessages = initErrorMessages();
return (errorMessages.length > 0 ? errorMessages[0] : "");
}
/**
@@ -287,21 +295,26 @@ public class BindStatus {
* @return the error message string
*/
public String getErrorMessagesAsString(String delimiter) {
initErrorMessages();
return StringUtils.arrayToDelimitedString(this.errorMessages, delimiter);
return StringUtils.arrayToDelimitedString(initErrorMessages(), delimiter);
}
/**
* Extract the error messages from the ObjectError list.
*/
private void initErrorMessages() throws NoSuchMessageException {
private String[] initErrorMessages() throws NoSuchMessageException {
if (this.errorMessages == null) {
this.errorMessages = new String[this.objectErrors.size()];
for (int i = 0; i < this.objectErrors.size(); i++) {
ObjectError error = this.objectErrors.get(i);
this.errorMessages[i] = this.requestContext.getMessage(error, this.htmlEscape);
if (this.objectErrors != null) {
this.errorMessages = new String[this.objectErrors.size()];
for (int i = 0; i < this.objectErrors.size(); i++) {
ObjectError error = this.objectErrors.get(i);
this.errorMessages[i] = this.requestContext.getMessage(error, this.htmlEscape);
}
}
else {
this.errorMessages = new String[0];
}
}
return this.errorMessages;
}
/**
@@ -343,7 +356,7 @@ public class BindStatus {
StringBuilder sb = new StringBuilder("BindStatus: ");
sb.append("expression=[").append(this.expression).append("]; ");
sb.append("value=[").append(this.value).append("]");
if (isError()) {
if (!ObjectUtils.isEmpty(this.errorCodes)) {
sb.append("; errorCodes=").append(Arrays.asList(this.errorCodes));
}
return sb.toString();

View File

@@ -39,12 +39,13 @@ class DefaultRendering implements Rendering {
private final Map<String, Object> model;
@Nullable
private final HttpStatus status;
private final HttpHeaders headers;
DefaultRendering(Object view, @Nullable Model model, HttpStatus status, @Nullable HttpHeaders headers) {
DefaultRendering(Object view, @Nullable Model model, @Nullable HttpStatus status, @Nullable HttpHeaders headers) {
this.view = view;
this.model = (model != null ? model.asMap() : Collections.emptyMap());
this.status = status;
@@ -63,6 +64,7 @@ class DefaultRendering implements Rendering {
}
@Override
@Nullable
public HttpStatus status() {
return this.status;
}

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.view;
import java.util.Arrays;
@@ -20,6 +21,7 @@ import java.util.Map;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
@@ -34,10 +36,13 @@ class DefaultRenderingBuilder implements Rendering.RedirectBuilder {
private final Object view;
@Nullable
private Model model;
@Nullable
private HttpStatus status;
@Nullable
private HttpHeaders headers;
@@ -48,38 +53,35 @@ class DefaultRenderingBuilder implements Rendering.RedirectBuilder {
@Override
public DefaultRenderingBuilder modelAttribute(String name, Object value) {
initModel();
this.model.addAttribute(name, value);
initModel().addAttribute(name, value);
return this;
}
private void initModel() {
if (this.model == null) {
this.model = new ExtendedModelMap();
}
}
@Override
public DefaultRenderingBuilder modelAttribute(Object value) {
initModel();
this.model.addAttribute(value);
initModel().addAttribute(value);
return this;
}
@Override
public DefaultRenderingBuilder modelAttributes(Object... values) {
initModel();
this.model.addAllAttributes(Arrays.asList(values));
initModel().addAllAttributes(Arrays.asList(values));
return this;
}
@Override
public DefaultRenderingBuilder model(Map<String, ?> map) {
initModel();
this.model.addAllAttributes(map);
initModel().addAllAttributes(map);
return this;
}
private Model initModel() {
if (this.model == null) {
this.model = new ExtendedModelMap();
}
return this.model;
}
@Override
public DefaultRenderingBuilder status(HttpStatus status) {
this.status = status;
@@ -88,22 +90,21 @@ class DefaultRenderingBuilder implements Rendering.RedirectBuilder {
@Override
public DefaultRenderingBuilder header(String headerName, String... headerValues) {
initHeaders();
this.headers.put(headerName, Arrays.asList(headerValues));
initHeaders().put(headerName, Arrays.asList(headerValues));
return this;
}
@Override
public DefaultRenderingBuilder headers(HttpHeaders headers) {
initHeaders();
this.headers.putAll(headers);
initHeaders().putAll(headers);
return this;
}
private void initHeaders() {
private HttpHeaders initHeaders() {
if (this.headers == null) {
this.headers = new HttpHeaders();
}
return this.headers;
}
@Override
@@ -123,6 +124,7 @@ class DefaultRenderingBuilder implements Rendering.RedirectBuilder {
return (RedirectView) this.view;
}
@Override
public Rendering build() {
return new DefaultRendering(this.view, this.model, this.status, this.headers);

View File

@@ -65,10 +65,13 @@ public class RequestContext {
private TimeZone timeZone;
@Nullable
private Boolean defaultHtmlEscape;
@Nullable
private Map<String, Errors> errorsMap;
@Nullable
private RequestDataValueProcessor dataValueProcessor;
@@ -79,19 +82,21 @@ public class RequestContext {
public RequestContext(ServerWebExchange exchange, Map<String, Object> model, MessageSource messageSource,
@Nullable RequestDataValueProcessor dataValueProcessor) {
Assert.notNull(exchange, "'exchange' is required");
Assert.notNull(model, "'model' is required");
Assert.notNull(messageSource, "'messageSource' is required");
Assert.notNull(exchange, "ServerWebExchange is required");
Assert.notNull(model, "Model is required");
Assert.notNull(messageSource, "MessageSource is required");
this.exchange = exchange;
this.model = model;
this.messageSource = messageSource;
LocaleContext localeContext = exchange.getLocaleContext();
this.locale = localeContext.getLocale();
this.timeZone = (localeContext instanceof TimeZoneAwareLocaleContext ?
((TimeZoneAwareLocaleContext)localeContext).getTimeZone() : TimeZone.getDefault());
Locale locale = localeContext.getLocale();
this.locale = (locale != null ? locale : Locale.getDefault());
TimeZone timeZone = (localeContext instanceof TimeZoneAwareLocaleContext ?
((TimeZoneAwareLocaleContext) localeContext).getTimeZone() : null);
this.timeZone = (timeZone != null ? timeZone : TimeZone.getDefault());
this.defaultHtmlEscape = null; // TODO
this.defaultHtmlEscape = null; // TODO
this.dataValueProcessor = dataValueProcessor;
}
@@ -125,7 +130,6 @@ public class RequestContext {
/**
* Return the current TimeZone.
* TODO: currently this is the Timezone.getDefault()
*/
public TimeZone getTimeZone() {
return this.timeZone;
@@ -168,6 +172,7 @@ public class RequestContext {
* specified and an explicit value.
* @return whether default HTML escaping is enabled (null = no explicit default)
*/
@Nullable
public Boolean getDefaultHtmlEscape() {
return this.defaultHtmlEscape;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
@@ -26,7 +26,9 @@ import freemarker.template.TemplateException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.lang.Nullable;
import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory;
import org.springframework.util.Assert;
/**
* Configures FreeMarker for web usage via the "configLocation" and/or
@@ -62,6 +64,7 @@ import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory;
public class FreeMarkerConfigurer extends FreeMarkerConfigurationFactory
implements FreeMarkerConfig, InitializingBean, ResourceLoaderAware {
@Nullable
private Configuration configuration;
@@ -111,7 +114,8 @@ public class FreeMarkerConfigurer extends FreeMarkerConfigurationFactory
*/
@Override
public Configuration getConfiguration() {
Assert.state(this.configuration != null, "No Configuration available");
return this.configuration;
}
}
}

View File

@@ -37,7 +37,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
@@ -74,22 +73,28 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
private static final String DEFAULT_RESOURCE_LOADER_PATH = "classpath:";
@Nullable
private ScriptEngine engine;
@Nullable
private String engineName;
@Nullable
private Boolean sharedEngine;
@Nullable
private String[] scripts;
@Nullable
private String renderObject;
@Nullable
private String renderFunction;
@Nullable
private String[] resourceLoaderPaths;
private ResourceLoader resourceLoader;
@Nullable
private volatile ScriptEngineManager scriptEngineManager;
@@ -193,9 +198,6 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
String resourceLoaderPath = viewConfig.getResourceLoaderPath();
setResourceLoaderPath(resourceLoaderPath == null ? DEFAULT_RESOURCE_LOADER_PATH : resourceLoaderPath);
}
if (this.resourceLoader == null) {
this.resourceLoader = getApplicationContext();
}
if (this.sharedEngine == null && viewConfig.isSharedEngine() != null) {
this.sharedEngine = viewConfig.isSharedEngine();
}
@@ -214,24 +216,34 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
loadScripts(this.engine);
}
else {
setEngine(createEngineFromName());
setEngine(createEngineFromName(this.engineName));
}
if (this.renderFunction != null && this.engine != null) {
Assert.isInstanceOf(Invocable.class, this.engine, "ScriptEngine must implement Invocable when 'renderFunction' is specified.");
Assert.isInstanceOf(Invocable.class, this.engine,
"ScriptEngine must implement Invocable when 'renderFunction' is specified");
}
}
protected ScriptEngine getEngine() {
return Boolean.FALSE.equals(this.sharedEngine) ? createEngineFromName() : this.engine;
if (Boolean.FALSE.equals(this.sharedEngine)) {
Assert.state(this.engineName != null, "No engine name specified");
return createEngineFromName(this.engineName);
}
else {
Assert.state(this.engine != null, "No shared engine available");
return this.engine;
}
}
protected ScriptEngine createEngineFromName() {
if (this.scriptEngineManager == null) {
this.scriptEngineManager = new ScriptEngineManager(obtainApplicationContext().getClassLoader());
protected ScriptEngine createEngineFromName(String engineName) {
ScriptEngineManager scriptEngineManager = this.scriptEngineManager;
if (scriptEngineManager == null) {
scriptEngineManager = new ScriptEngineManager(obtainApplicationContext().getClassLoader());
this.scriptEngineManager = scriptEngineManager;
}
ScriptEngine engine = StandardScriptUtils.retrieveEngineByName(this.scriptEngineManager, this.engineName);
ScriptEngine engine = StandardScriptUtils.retrieveEngineByName(scriptEngineManager, engineName);
loadScripts(engine);
return engine;
}
@@ -255,10 +267,12 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
@Nullable
protected Resource getResource(String location) {
for (String path : this.resourceLoaderPaths) {
Resource resource = this.resourceLoader.getResource(path + location);
if (resource.exists()) {
return resource;
if (this.resourceLoaderPaths != null) {
for (String path : this.resourceLoaderPaths) {
Resource resource = obtainApplicationContext().getResource(path + location);
if (resource.exists()) {
return resource;
}
}
}
return null;
@@ -317,10 +331,10 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
}
else if (this.renderObject != null) {
Object thiz = engine.eval(this.renderObject);
html = ((Invocable)engine).invokeMethod(thiz, this.renderFunction, template, model, context);
html = ((Invocable) engine).invokeMethod(thiz, this.renderFunction, template, model, context);
}
else {
html = ((Invocable)engine).invokeFunction(this.renderFunction, template, model, context);
html = ((Invocable) engine).invokeFunction(this.renderFunction, template, model, context);
}
byte[] bytes = String.valueOf(html).getBytes(StandardCharsets.UTF_8);

View File

@@ -135,6 +135,7 @@ public final class CloseStatus {
private final int code;
@Nullable
private final String reason;

View File

@@ -41,6 +41,7 @@ public class HandshakeInfo {
private final HttpHeaders headers;
@Nullable
private final String protocol;

View File

@@ -30,6 +30,7 @@ import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.server.reactive.AbstractListenerReadPublisher;
import org.springframework.http.server.reactive.AbstractListenerWriteProcessor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.socket.CloseStatus;
import org.springframework.web.reactive.socket.HandshakeInfo;
import org.springframework.web.reactive.socket.WebSocketMessage;
@@ -58,10 +59,12 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
private static final int RECEIVE_BUFFER_SIZE = 8192;
@Nullable
private final MonoProcessor<Void> completionMono;
private final WebSocketReceivePublisher receivePublisher = new WebSocketReceivePublisher();
@Nullable
private volatile WebSocketSendProcessor sendProcessor;
private final AtomicBoolean sendCalled = new AtomicBoolean();
@@ -93,7 +96,9 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
protected WebSocketSendProcessor getSendProcessor() {
return this.sendProcessor;
WebSocketSendProcessor sendProcessor = this.sendProcessor;
Assert.state(sendProcessor != null, "No WebSocketSendProcessor available");
return sendProcessor;
}
@Override
@@ -106,10 +111,11 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
@Override
public Mono<Void> send(Publisher<WebSocketMessage> messages) {
if (this.sendCalled.compareAndSet(false, true)) {
this.sendProcessor = new WebSocketSendProcessor();
WebSocketSendProcessor sendProcessor = new WebSocketSendProcessor();
this.sendProcessor = sendProcessor;
return Mono.from(subscriber -> {
messages.subscribe(this.sendProcessor);
this.sendProcessor.subscribe(subscriber);
messages.subscribe(sendProcessor);
sendProcessor.subscribe(subscriber);
});
}
else {
@@ -157,18 +163,20 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
/** Handle an error callback from the WebSocketHandler adapter */
void handleError(Throwable ex) {
this.receivePublisher.onError(ex);
if (this.sendProcessor != null) {
this.sendProcessor.cancel();
this.sendProcessor.onError(ex);
WebSocketSendProcessor sendProcessor = this.sendProcessor;
if (sendProcessor != null) {
sendProcessor.cancel();
sendProcessor.onError(ex);
}
}
/** Handle a close callback from the WebSocketHandler adapter */
void handleClose(CloseStatus reason) {
this.receivePublisher.onAllDataRead();
if (this.sendProcessor != null) {
this.sendProcessor.cancel();
this.sendProcessor.onComplete();
WebSocketSendProcessor sendProcessor = this.sendProcessor;
if (sendProcessor != null) {
sendProcessor.cancel();
sendProcessor.onComplete();
}
}
@@ -205,6 +213,7 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
private final class WebSocketReceivePublisher extends AbstractListenerReadPublisher<WebSocketMessage> {
@Nullable
private volatile WebSocketMessage webSocketMessage;
@Override

View File

@@ -31,6 +31,7 @@ import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.OpCode;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.socket.CloseStatus;
import org.springframework.web.reactive.socket.WebSocketHandler;
@@ -56,6 +57,7 @@ public class JettyWebSocketHandlerAdapter {
private final Function<Session, JettyWebSocketSession> sessionFactory;
@Nullable
private JettyWebSocketSession delegateSession;

View File

@@ -45,6 +45,7 @@ import org.springframework.web.reactive.socket.WebSocketSession;
*/
public class JettyWebSocketSession extends AbstractListenerWebSocketSession<Session> {
@Nullable
private volatile SuspendToken suspendToken;

View File

@@ -26,6 +26,7 @@ import javax.websocket.PongMessage;
import javax.websocket.Session;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.socket.CloseStatus;
import org.springframework.web.reactive.socket.WebSocketHandler;
@@ -47,6 +48,7 @@ public class StandardWebSocketHandlerAdapter extends Endpoint {
private Function<Session, StandardWebSocketSession> sessionFactory;
@Nullable
private StandardWebSocketSession delegateSession;
@@ -62,8 +64,8 @@ public class StandardWebSocketHandlerAdapter extends Endpoint {
@Override
public void onOpen(Session session, EndpointConfig config) {
this.delegateSession = this.sessionFactory.apply(session);
Assert.state(this.delegateSession != null, "No delegate session");
session.addMessageHandler(String.class, message -> {
WebSocketMessage webSocketMessage = toMessage(message);

View File

@@ -35,6 +35,7 @@ import reactor.core.publisher.MonoProcessor;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.socket.HandshakeInfo;
import org.springframework.web.reactive.socket.WebSocketHandler;
@@ -184,6 +185,7 @@ public class UndertowWebSocketClient extends WebSocketClientSupport implements W
private final HttpHeaders responseHeaders = new HttpHeaders();
@Nullable
private final WebSocketClientNegotiation delegate;
public DefaultNegotiation(List<String> protocols, HttpHeaders requestHeaders,

View File

@@ -54,8 +54,10 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Life
new NamedThreadLocal<>("JettyWebSocketHandlerAdapter");
@Nullable
private WebSocketServerFactory factory;
@Nullable
private volatile ServletContext servletContext;
private volatile boolean running = false;
@@ -66,10 +68,11 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Life
@Override
public void start() {
synchronized (this.lifecycleMonitor) {
if (!isRunning() && this.servletContext != null) {
ServletContext servletContext = this.servletContext;
if (!isRunning() && servletContext != null) {
this.running = true;
try {
this.factory = new WebSocketServerFactory(this.servletContext);
this.factory = new WebSocketServerFactory(servletContext);
this.factory.setCreator((request, response) -> {
WebSocketHandlerContainer container = adapterHolder.get();
String protocol = container.getProtocol();
@@ -92,11 +95,13 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Life
synchronized (this.lifecycleMonitor) {
if (isRunning()) {
this.running = false;
try {
this.factory.stop();
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to stop WebSocketServerFactory", ex);
if (this.factory != null) {
try {
this.factory.stop();
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to stop WebSocketServerFactory", ex);
}
}
}
}
@@ -125,6 +130,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Life
startLazily(servletRequest);
Assert.state(this.factory != null, "No WebSocketServerFactory available");
boolean isUpgrade = this.factory.isUpgradeRequest(servletRequest, servletResponse);
Assert.isTrue(isUpgrade, "Not a WebSocket handshake");
@@ -175,6 +181,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Life
private final JettyWebSocketHandlerAdapter adapter;
@Nullable
private final String protocol;
public WebSocketHandlerContainer(JettyWebSocketHandlerAdapter adapter, @Nullable String protocol) {

View File

@@ -261,8 +261,6 @@ public class WebFluxConfigurationSupportTests {
assertEquals(Ordered.LOWEST_PRECEDENCE - 1, handlerMapping.getOrder());
assertNotNull(handlerMapping.getPatternRegistry());
SimpleUrlHandlerMapping urlHandlerMapping = (SimpleUrlHandlerMapping) handlerMapping;
WebHandler webHandler = (WebHandler) urlHandlerMapping.getUrlMap().get("/images/**");
assertNotNull(webHandler);