Consistent use of @Nullable across the codebase (even for internals)

Beyond just formally declaring the current behavior, this revision actually enforces non-null behavior in selected signatures now, not tolerating null values anymore when not explicitly documented. It also changes some utility methods with historic null-in/null-out tolerance towards enforced non-null return values, making them a proper citizen in non-null assignments.

Some issues are left as to-do: in particular a thorough revision of spring-test, and a few tests with unclear failures (ignored as "TODO: NULLABLE") to be sorted out in a follow-up commit.

Issue: SPR-15540
This commit is contained in:
Juergen Hoeller
2017-06-07 14:17:48 +02:00
parent ffc3f6d87d
commit f813712f5b
1493 changed files with 10670 additions and 9172 deletions

View File

@@ -63,7 +63,7 @@ public class HandlerResult {
* @param context the binding context used for request handling
*/
public HandlerResult(Object handler, @Nullable Object returnValue, MethodParameter returnType,
BindingContext context) {
@Nullable BindingContext context) {
Assert.notNull(handler, "'handler' is required");
Assert.notNull(returnType, "'returnType' is required");

View File

@@ -53,7 +53,7 @@ public abstract class AbstractMappingContentTypeResolver implements MappingConte
/**
* Create an instance with the given map of file extensions and media types.
*/
public AbstractMappingContentTypeResolver(Map<String, MediaType> mediaTypes) {
public AbstractMappingContentTypeResolver(@Nullable Map<String, MediaType> mediaTypes) {
if (mediaTypes != null) {
for (Map.Entry<String, MediaType> entry : mediaTypes.entrySet()) {
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
@@ -90,11 +90,8 @@ public abstract class AbstractMappingContentTypeResolver implements MappingConte
// RequestedContentTypeResolver implementation
@Override
public List<MediaType> resolveMediaTypes(ServerWebExchange exchange)
throws NotAcceptableStatusException {
String key = extractKey(exchange);
return resolveMediaTypes(key);
public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) throws NotAcceptableStatusException {
return resolveMediaTypes(extractKey(exchange));
}
/**
@@ -103,7 +100,7 @@ public abstract class AbstractMappingContentTypeResolver implements MappingConte
* @return a list of resolved media types or an empty list
* @throws NotAcceptableStatusException
*/
public List<MediaType> resolveMediaTypes(String key) throws NotAcceptableStatusException {
public List<MediaType> resolveMediaTypes(@Nullable String key) throws NotAcceptableStatusException {
if (StringUtils.hasText(key)) {
MediaType mediaType = getMediaType(key);
if (mediaType != null) {

View File

@@ -48,13 +48,6 @@ public class PathExtensionContentTypeResolver extends AbstractMappingContentType
private boolean ignoreUnknownExtensions = true;
/**
* Create an instance with the given map of file extensions and media types.
*/
public PathExtensionContentTypeResolver(Map<String, MediaType> mediaTypes) {
super(mediaTypes);
}
/**
* Create an instance without any mappings to start with. Mappings may be added
* later on if any extensions are resolved through the Java Activation framework.
@@ -63,6 +56,13 @@ public class PathExtensionContentTypeResolver extends AbstractMappingContentType
super(null);
}
/**
* Create an instance with the given map of file extensions and media types.
*/
public PathExtensionContentTypeResolver(@Nullable Map<String, MediaType> mediaTypes) {
super(mediaTypes);
}
/**
* Whether to only use the registered mappings to look up file extensions, or also refer to

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.
@@ -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.List;
@@ -33,13 +34,10 @@ public interface RequestedContentTypeResolver {
/**
* Resolve the given request to a list of requested media types. The returned
* list is ordered by specificity first and by quality parameter second.
*
* @param exchange the current exchange
* @return the requested media types or an empty list
*
* @throws NotAcceptableStatusException if the requested media types is invalid
*/
List<MediaType> resolveMediaTypes(ServerWebExchange exchange)
throws NotAcceptableStatusException;
List<MediaType> resolveMediaTypes(ServerWebExchange exchange) throws NotAcceptableStatusException;
}

View File

@@ -16,6 +16,7 @@
package org.springframework.web.reactive.config;
import org.springframework.lang.Nullable;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.support.HttpRequestPathHelper;
import org.springframework.web.util.pattern.ParsingPathMatcher;
@@ -90,26 +91,29 @@ public class PathMatchConfigurer {
return this;
}
@Nullable
protected Boolean isUseSuffixPatternMatch() {
return this.suffixPatternMatch;
}
@Nullable
protected Boolean isUseTrailingSlashMatch() {
return this.trailingSlashMatch;
}
@Nullable
protected Boolean isUseRegisteredSuffixPatternMatch() {
return this.registeredSuffixPatternMatch;
}
@Nullable
protected HttpRequestPathHelper getPathHelper() {
return this.pathHelper;
}
@Nullable
public PathMatcher getPathMatcher() {
if(this.pathMatcher != null
&& this.pathMatcher.getClass().isAssignableFrom(ParsingPathMatcher.class)
&& (this.trailingSlashMatch || this.suffixPatternMatch)) {
if (this.pathMatcher instanceof ParsingPathMatcher && (this.trailingSlashMatch || this.suffixPatternMatch)) {
throw new IllegalStateException("When using a ParsingPathMatcher, useTrailingSlashMatch" +
" and useSuffixPatternMatch should be set to 'false'.");
}

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.
@@ -21,6 +21,7 @@ import java.util.List;
import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.reactive.resource.CachingResourceResolver;
@@ -63,7 +64,7 @@ public class ResourceChainRegistration {
this(cacheResources, cacheResources ? new ConcurrentMapCache(DEFAULT_CACHE_NAME) : null);
}
public ResourceChainRegistration(boolean cacheResources, Cache cache) {
public ResourceChainRegistration(boolean cacheResources, @Nullable Cache cache) {
Assert.isTrue(!cacheResources || cache != null, "'cache' is required when cacheResources=true");
if (cacheResources) {
this.resolvers.add(new CachingResourceResolver(cache));

View File

@@ -77,7 +77,7 @@ public class ResourceHandlerRegistry {
* @param contentTypeResolver the content type resolver to use
*/
public ResourceHandlerRegistry(ApplicationContext applicationContext,
CompositeContentTypeResolver contentTypeResolver) {
@Nullable CompositeContentTypeResolver contentTypeResolver) {
Assert.notNull(applicationContext, "ApplicationContext is required");
this.applicationContext = applicationContext;

View File

@@ -75,7 +75,9 @@ public class ViewResolverRegistry {
}
FreeMarkerRegistration registration = new FreeMarkerRegistration();
UrlBasedViewResolver resolver = registration.getViewResolver();
resolver.setApplicationContext(this.applicationContext);
if (this.applicationContext != null) {
resolver.setApplicationContext(this.applicationContext);
}
this.viewResolvers.add(resolver);
return registration;
}

View File

@@ -38,6 +38,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.PathMatcher;
import org.springframework.validation.Errors;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
@@ -63,6 +64,7 @@ import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
import org.springframework.web.server.support.HttpRequestPathHelper;
/**
* The main class for Spring WebFlux configuration.
@@ -119,20 +121,26 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
if (configurer.isUseSuffixPatternMatch() != null) {
mapping.setUseSuffixPatternMatch(configurer.isUseSuffixPatternMatch());
Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
}
if (configurer.isUseRegisteredSuffixPatternMatch() != null) {
mapping.setUseRegisteredSuffixPatternMatch(configurer.isUseRegisteredSuffixPatternMatch());
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
if (configurer.isUseTrailingSlashMatch() != null) {
mapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
if (configurer.getPathMatcher() != null) {
mapping.setPathMatcher(configurer.getPathMatcher());
HttpRequestPathHelper pathHelper = configurer.getPathHelper();
if (pathHelper != null) {
mapping.setPathHelper(pathHelper);
}
if (configurer.getPathHelper() != null) {
mapping.setPathHelper(configurer.getPathHelper());
PathMatcher pathMatcher = configurer.getPathMatcher();
if (pathMatcher != null) {
mapping.setPathMatcher(pathMatcher);
}
return mapping;
@@ -313,7 +321,10 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(webFluxConversionService());
initializer.setValidator(webFluxValidator());
initializer.setMessageCodesResolver(getMessageCodesResolver());
MessageCodesResolver messageCodesResolver = getMessageCodesResolver();
if (messageCodesResolver != null) {
initializer.setMessageCodesResolver(messageCodesResolver);
}
return initializer;
}

View File

@@ -132,8 +132,7 @@ public abstract class BodyInserters {
serverRequest.get(), (ServerHttpResponse) outputMessage, context.hints());
}
else {
return messageWriter.write(inputStream, RESOURCE_TYPE, null,
outputMessage, context.hints());
return messageWriter.write(inputStream, RESOURCE_TYPE, null, outputMessage, context.hints());
}
};
}

View File

@@ -49,8 +49,8 @@ public class UnsupportedMediaTypeException extends NestedRuntimeException {
/**
* Constructor for when the Content-Type can be parsed but is not supported.
*/
public UnsupportedMediaTypeException(MediaType contentType, List<MediaType> supportedMediaTypes) {
super("Content type '" + contentType + "' not supported");
public UnsupportedMediaTypeException(@Nullable MediaType contentType, List<MediaType> supportedMediaTypes) {
super("Content type '" + (contentType != null ? contentType : "") + "' not supported");
this.contentType = contentType;
this.supportedMediaTypes = Collections.unmodifiableList(supportedMediaTypes);
}

View File

@@ -73,9 +73,7 @@ class DefaultClientRequestBuilder implements ClientRequest.Builder {
@Override
public ClientRequest.Builder headers(HttpHeaders headers) {
if (headers != null) {
this.headers.putAll(headers);
}
this.headers.putAll(headers);
return this;
}
@@ -87,15 +85,12 @@ class DefaultClientRequestBuilder implements ClientRequest.Builder {
@Override
public ClientRequest.Builder cookies(MultiValueMap<String, String> cookies) {
if (cookies != null) {
this.cookies.putAll(cookies);
}
this.cookies.putAll(cookies);
return this;
}
@Override
public <S, P extends Publisher<S>> ClientRequest.Builder body(P publisher,
Class<S> elementClass) {
public <S, P extends Publisher<S>> ClientRequest.Builder body(P publisher, Class<S> elementClass) {
Assert.notNull(publisher, "'publisher' must not be null");
Assert.notNull(elementClass, "'elementClass' must not be null");
@@ -105,7 +100,7 @@ class DefaultClientRequestBuilder implements ClientRequest.Builder {
@Override
public ClientRequest.Builder body(BodyInserter<?, ? super ClientHttpRequest> inserter) {
this.inserter = inserter != null ? inserter : BodyInserters.empty();
this.inserter = inserter;
return this;
}

View File

@@ -22,6 +22,7 @@ import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -67,8 +68,8 @@ class DefaultWebClient implements WebClient {
private final MultiValueMap<String, String> defaultCookies;
DefaultWebClient(ExchangeFunction exchangeFunction, UriBuilderFactory factory,
HttpHeaders defaultHeaders, MultiValueMap<String, String> defaultCookies) {
DefaultWebClient(ExchangeFunction exchangeFunction, @Nullable UriBuilderFactory factory,
@Nullable HttpHeaders defaultHeaders, @Nullable MultiValueMap<String, String> defaultCookies) {
this.exchangeFunction = exchangeFunction;
this.uriBuilderFactory = (factory != null ? factory : new DefaultUriBuilderFactory());
@@ -205,9 +206,7 @@ class DefaultWebClient implements WebClient {
@Override
public DefaultRequestBodySpec headers(HttpHeaders headers) {
if (headers != null) {
getHeaders().putAll(headers);
}
getHeaders().putAll(headers);
return this;
}
@@ -243,9 +242,7 @@ class DefaultWebClient implements WebClient {
@Override
public DefaultRequestBodySpec cookies(MultiValueMap<String, String> cookies) {
if (cookies != null) {
getCookies().putAll(cookies);
}
getCookies().putAll(cookies);
return this;
}
@@ -298,10 +295,9 @@ class DefaultWebClient implements WebClient {
return ClientRequest.method(this.httpMethod, this.uri).headers(initHeaders()).cookies(initCookies());
}
@Nullable
private HttpHeaders initHeaders() {
if (CollectionUtils.isEmpty(defaultHeaders) && CollectionUtils.isEmpty(this.headers)) {
return null;
return new HttpHeaders();
}
else if (CollectionUtils.isEmpty(defaultHeaders)) {
return this.headers;
@@ -321,10 +317,9 @@ class DefaultWebClient implements WebClient {
}
}
@Nullable
private MultiValueMap<String, String> initCookies() {
if (CollectionUtils.isEmpty(defaultCookies) && CollectionUtils.isEmpty(this.cookies)) {
return null;
return new LinkedMultiValueMap<>(0);
}
else if (CollectionUtils.isEmpty(defaultCookies)) {
return this.cookies;

View File

@@ -84,9 +84,7 @@ class DefaultEntityResponseBuilder<T> implements EntityResponse.Builder<T> {
@Override
public EntityResponse.Builder<T> headers(HttpHeaders headers) {
if (headers != null) {
this.headers.putAll(headers);
}
this.headers.putAll(headers);
return this;
}
@@ -115,16 +113,14 @@ class DefaultEntityResponseBuilder<T> implements EntityResponse.Builder<T> {
}
@Override
public EntityResponse.Builder<T> eTag(String eTag) {
if (eTag != null) {
if (!eTag.startsWith("\"") && !eTag.startsWith("W/\"")) {
eTag = "\"" + eTag;
}
if (!eTag.endsWith("\"")) {
eTag = eTag + "\"";
}
public EntityResponse.Builder<T> eTag(String etag) {
if (!etag.startsWith("\"") && !etag.startsWith("W/\"")) {
etag = "\"" + etag;
}
this.headers.setETag(eTag);
if (!etag.endsWith("\"")) {
etag = etag + "\"";
}
this.headers.setETag(etag);
return this;
}

View File

@@ -85,25 +85,19 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder {
@Override
public RenderingResponse.Builder modelAttributes(Object... attributes) {
if (attributes != null) {
modelAttributes(Arrays.asList(attributes));
}
modelAttributes(Arrays.asList(attributes));
return this;
}
@Override
public RenderingResponse.Builder modelAttributes(Collection<?> attributes) {
if (attributes != null) {
attributes.forEach(this::modelAttribute);
}
attributes.forEach(this::modelAttribute);
return this;
}
@Override
public RenderingResponse.Builder modelAttributes(Map<String, ?> attributes) {
if (attributes != null) {
this.model.putAll(attributes);
}
this.model.putAll(attributes);
return this;
}
@@ -117,9 +111,7 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder {
@Override
public RenderingResponse.Builder headers(HttpHeaders headers) {
if (headers != null) {
this.headers.putAll(headers);
}
this.headers.putAll(headers);
return this;
}

View File

@@ -75,9 +75,7 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
@Override
public ServerResponse.BodyBuilder headers(HttpHeaders headers) {
if (headers != null) {
this.headers.putAll(headers);
}
this.headers.putAll(headers);
return this;
}
@@ -106,16 +104,14 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
}
@Override
public ServerResponse.BodyBuilder eTag(String eTag) {
if (eTag != null) {
if (!eTag.startsWith("\"") && !eTag.startsWith("W/\"")) {
eTag = "\"" + eTag;
}
if (!eTag.endsWith("\"")) {
eTag = eTag + "\"";
}
public ServerResponse.BodyBuilder eTag(String etag) {
if (!etag.startsWith("\"") && !etag.startsWith("W/\"")) {
etag = "\"" + etag;
}
this.headers.setETag(eTag);
if (!etag.endsWith("\"")) {
etag = etag + "\"";
}
this.headers.setETag(etag);
return this;
}

View File

@@ -37,6 +37,7 @@ import reactor.core.publisher.Mono;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.server.WebSession;
@@ -293,7 +294,7 @@ public abstract class RequestPredicates {
}
private static void traceMatch(String prefix, Object desired, Object actual, boolean match) {
private static void traceMatch(String prefix, Object desired, @Nullable Object actual, boolean match) {
if (logger.isTraceEnabled()) {
String message = String.format("%s \"%s\" %s against value \"%s\"",
prefix, desired, match ? "matches" : "does not match", actual);

View File

@@ -54,23 +54,25 @@ class ResourceHandlerFunction implements HandlerFunction<ServerResponse> {
@Override
public Mono<ServerResponse> handle(ServerRequest request) {
switch (request.method()) {
case GET:
return EntityResponse.fromObject(this.resource).build()
.map(response -> response);
case HEAD:
Resource headResource = new HeadMethodResource(this.resource);
return EntityResponse.fromObject(headResource).build()
.map(response -> response);
case OPTIONS:
return ServerResponse.ok()
.allow(SUPPORTED_METHODS)
.body(BodyInserters.empty());
default:
return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED)
.allow(SUPPORTED_METHODS)
.body(BodyInserters.empty());
HttpMethod method = request.method();
if (method != null) {
switch (method) {
case GET:
return EntityResponse.fromObject(this.resource).build()
.map(response -> response);
case HEAD:
Resource headResource = new HeadMethodResource(this.resource);
return EntityResponse.fromObject(headResource).build()
.map(response -> response);
case OPTIONS:
return ServerResponse.ok()
.allow(SUPPORTED_METHODS)
.body(BodyInserters.empty());
}
}
return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED)
.allow(SUPPORTED_METHODS)
.body(BodyInserters.empty());
}

View File

@@ -36,6 +36,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
@@ -54,6 +55,7 @@ public interface ServerRequest {
/**
* Return the HTTP method.
*/
@Nullable
HttpMethod method();
/**
@@ -241,6 +243,7 @@ public interface ServerRequest {
* <p>If the header value does not contain a port, the returned
* {@linkplain InetSocketAddress#getPort() port} will be {@code 0}.
*/
@Nullable
InetSocketAddress host();
/**

View File

@@ -105,7 +105,7 @@ public class RouterFunctionMapping extends AbstractHandlerMapping implements Ini
private List<RouterFunction<?>> routerFunctions() {
SortedRouterFunctionsContainer container = new SortedRouterFunctionsContainer();
getApplicationContext().getAutowireCapableBeanFactory().autowireBean(container);
obtainApplicationContext().getAutowireCapableBeanFactory().autowireBean(container);
return CollectionUtils.isEmpty(container.routerFunctions) ? Collections.emptyList() :
container.routerFunctions;

View File

@@ -188,7 +188,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, exchange);
@@ -241,8 +241,8 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
resolvedHandler = getApplicationContext().getBean(handlerName);
if (obtainApplicationContext().isSingleton(handlerName)) {
resolvedHandler = obtainApplicationContext().getBean(handlerName);
}
}

View File

@@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -37,7 +38,7 @@ public abstract class AbstractResourceResolver implements ResourceResolver {
@Override
public Mono<Resource> resolveResource(ServerWebExchange exchange, String requestPath,
public Mono<Resource> resolveResource(@Nullable ServerWebExchange exchange, String requestPath,
List<? extends Resource> locations, ResourceResolverChain chain) {
if (logger.isTraceEnabled()) {
@@ -58,7 +59,7 @@ public abstract class AbstractResourceResolver implements ResourceResolver {
}
protected abstract Mono<Resource> resolveResourceInternal(ServerWebExchange exchange,
protected abstract Mono<Resource> resolveResourceInternal(@Nullable ServerWebExchange exchange,
String requestPath, List<? extends Resource> locations, ResourceResolverChain chain);
protected abstract Mono<String> resolveUrlPathInternal(String resourceUrlPath,

View File

@@ -201,7 +201,7 @@ public class AppCacheManifestTransformer extends ResourceTransformerSupport {
this.link = iniLinkFlag(line, this.cacheSection);
}
private static boolean initCacheSectionFlag(String line, LineInfo previousLine) {
private static boolean initCacheSectionFlag(String line, @Nullable LineInfo previousLine) {
if (MANIFEST_SECTION_HEADERS.contains(line.trim())) {
return line.trim().equals(CACHE_HEADER);
}
@@ -252,6 +252,7 @@ public class AppCacheManifestTransformer extends ResourceTransformerSupport {
return this.line;
}
@Nullable
public Resource getResource() {
return this.resource;
}

View File

@@ -23,6 +23,7 @@ import reactor.core.publisher.Mono;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
@@ -44,15 +45,19 @@ public class CachingResourceResolver extends AbstractResourceResolver {
private final Cache cache;
public CachingResourceResolver(CacheManager cacheManager, String cacheName) {
this(cacheManager.getCache(cacheName));
}
public CachingResourceResolver(Cache cache) {
Assert.notNull(cache, "Cache is required");
this.cache = cache;
}
public CachingResourceResolver(CacheManager cacheManager, String cacheName) {
Cache cache = cacheManager.getCache(cacheName);
if (cache == null) {
throw new IllegalArgumentException("Cache '" + cacheName + "' not found");
}
this.cache = cache;
}
/**
* Return the configured {@code Cache}.
@@ -63,8 +68,8 @@ public class CachingResourceResolver extends AbstractResourceResolver {
@Override
protected Mono<Resource> resolveResourceInternal(ServerWebExchange exchange, String requestPath,
List<? extends Resource> locations, ResourceResolverChain chain) {
protected Mono<Resource> resolveResourceInternal(@Nullable ServerWebExchange exchange,
String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
String key = computeKey(exchange, requestPath);
Resource cachedResource = this.cache.get(key, Resource.class);
@@ -85,7 +90,7 @@ public class CachingResourceResolver extends AbstractResourceResolver {
});
}
protected String computeKey(ServerWebExchange exchange, String requestPath) {
protected String computeKey(@Nullable ServerWebExchange exchange, String requestPath) {
StringBuilder key = new StringBuilder(RESOLVED_RESOURCE_CACHE_KEY_PREFIX);
key.append(requestPath);
if (exchange != null) {

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.
@@ -41,15 +41,19 @@ public class CachingResourceTransformer implements ResourceTransformer {
private final Cache cache;
public CachingResourceTransformer(CacheManager cacheManager, String cacheName) {
this(cacheManager.getCache(cacheName));
}
public CachingResourceTransformer(Cache cache) {
Assert.notNull(cache, "Cache is required");
this.cache = cache;
}
public CachingResourceTransformer(CacheManager cacheManager, String cacheName) {
Cache cache = cacheManager.getCache(cacheName);
if (cache == null) {
throw new IllegalArgumentException("Cache '" + cacheName + "' not found");
}
this.cache = cache;
}
/**
* Return the configured {@code Cache}.

View File

@@ -33,6 +33,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
@@ -293,7 +294,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}

View File

@@ -40,7 +40,7 @@ class DefaultResourceResolverChain implements ResourceResolverChain {
private int index = -1;
public DefaultResourceResolverChain(List<? extends ResourceResolver> resolvers) {
public DefaultResourceResolverChain(@Nullable List<? extends ResourceResolver> resolvers) {
if (resolvers != null) {
this.resolvers.addAll(resolvers);
}
@@ -53,7 +53,7 @@ class DefaultResourceResolverChain implements ResourceResolverChain {
ResourceResolver resolver = getNext();
if (resolver == null) {
return null;
return Mono.empty();
}
try {
@@ -68,7 +68,7 @@ class DefaultResourceResolverChain implements ResourceResolverChain {
public Mono<String> resolveUrlPath(String resourcePath, List<? extends Resource> locations) {
ResourceResolver resolver = getNext();
if (resolver == null) {
return null;
return Mono.empty();
}
try {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-201/ 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.
@@ -43,7 +43,7 @@ class DefaultResourceTransformerChain implements ResourceTransformerChain {
public DefaultResourceTransformerChain(ResourceResolverChain resolverChain,
List<ResourceTransformer> transformers) {
@Nullable List<ResourceTransformer> transformers) {
Assert.notNull(resolverChain, "ResourceResolverChain is required");
this.resolverChain = resolverChain;

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.
@@ -28,6 +28,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -43,8 +44,8 @@ import org.springframework.web.server.ServerWebExchange;
public class GzipResourceResolver extends AbstractResourceResolver {
@Override
protected Mono<Resource> resolveResourceInternal(ServerWebExchange exchange, String requestPath,
List<? extends Resource> locations, ResourceResolverChain chain) {
protected Mono<Resource> resolveResourceInternal(@Nullable ServerWebExchange exchange,
String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
return chain.resolveResource(exchange, requestPath, locations)
.map(resource -> {

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.
@@ -27,6 +27,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
@@ -64,6 +65,7 @@ public class PathResourceResolver extends AbstractResourceResolver {
this.allowedLocations = locations;
}
@Nullable
public Resource[] getAllowedLocations() {
return this.allowedLocations;
}
@@ -113,10 +115,11 @@ public class PathResourceResolver extends AbstractResourceResolver {
return Mono.just(resource);
}
else if (logger.isTraceEnabled()) {
Resource[] allowedLocations = getAllowedLocations();
logger.trace("Resource path=\"" + resourcePath + "\" was successfully resolved " +
"but resource=\"" + resource.getURL() + "\" is neither under the " +
"current location=\"" + location.getURL() + "\" nor under any of the " +
"allowed locations=" + Arrays.asList(getAllowedLocations()));
"allowed locations=" + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]"));
}
}
else if (logger.isTraceEnabled()) {

View File

@@ -21,6 +21,7 @@ import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -44,7 +45,7 @@ public interface ResourceResolver {
* @param chain the chain of remaining resolvers to delegate to
* @return the resolved resource or an empty {@code Mono} if unresolved
*/
Mono<Resource> resolveResource(ServerWebExchange exchange, String requestPath,
Mono<Resource> resolveResource(@Nullable ServerWebExchange exchange, String requestPath,
List<? extends Resource> locations, ResourceResolverChain chain);
/**

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.
@@ -39,7 +39,7 @@ public interface ResourceResolverChain {
* @param exchange the current exchange
* @param requestPath the portion of the request path to use
* @param locations the locations to search in when looking up resources
* @return the resolved resource or an empty {@code Mono} if unresolved
* @return the resolved resource; or an empty {@code Mono} if unresolved
*/
Mono<Resource> resolveResource(@Nullable ServerWebExchange exchange, String requestPath,
List<? extends Resource> locations);
@@ -51,7 +51,7 @@ public interface ResourceResolverChain {
* <p>This is useful when rendering URL links to clients.
* @param resourcePath the internal resource path
* @param locations the locations to search in when looking up resources
* @return the resolved public URL path or an empty {@code Mono} if unresolved
* @return the resolved public URL path; or an empty {@code Mono} if unresolved
*/
Mono<String> resolveUrlPath(String resourcePath, List<? extends Resource> locations);

View File

@@ -21,7 +21,7 @@ import java.util.Collections;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
@@ -50,8 +50,9 @@ public abstract class ResourceTransformerSupport implements ResourceTransformer
}
/**
* @return the configured {@code ResourceUrlProvider}.
* Return the configured {@code ResourceUrlProvider}.
*/
@Nullable
public ResourceUrlProvider getResourceUrlProvider() {
return this.resourceUrlProvider;
}

View File

@@ -33,6 +33,7 @@ import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.PathMatcher;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.server.ServerWebExchange;
@@ -101,7 +102,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
* from the Spring {@code ApplicationContext}. However if this property is
* used, the auto-detection is turned off.
*/
public void setHandlerMap(Map<String, ResourceWebHandler> handlerMap) {
public void setHandlerMap(@Nullable Map<String, ResourceWebHandler> handlerMap) {
if (handlerMap != null) {
this.handlerMap.clear();
this.handlerMap.putAll(handlerMap);

View File

@@ -85,8 +85,7 @@ import org.springframework.web.server.WebHandler;
* @author Brian Clozel
* @since 5.0
*/
public class ResourceWebHandler
implements WebHandler, InitializingBean, SmartInitializingSingleton {
public class ResourceWebHandler implements WebHandler, InitializingBean, SmartInitializingSingleton {
/** Set of supported HTTP methods */
private static final Set<HttpMethod> SUPPORTED_METHODS = EnumSet.of(HttpMethod.GET, HttpMethod.HEAD);
@@ -113,10 +112,11 @@ public class ResourceWebHandler
* Set the {@code List} of {@code Resource} paths to use as sources
* for serving static resources.
*/
public void setLocations(List<Resource> locations) {
Assert.notNull(locations, "Locations list must not be null");
public void setLocations(@Nullable List<Resource> locations) {
this.locations.clear();
this.locations.addAll(locations);
if (locations != null) {
this.locations.addAll(locations);
}
}
/**
@@ -132,7 +132,7 @@ public class ResourceWebHandler
* <p>By default {@link PathResourceResolver} is configured. If using this property,
* it is recommended to add {@link PathResourceResolver} as the last resolver.
*/
public void setResourceResolvers(List<ResourceResolver> resourceResolvers) {
public void setResourceResolvers(@Nullable List<ResourceResolver> resourceResolvers) {
this.resourceResolvers.clear();
if (resourceResolvers != null) {
this.resourceResolvers.addAll(resourceResolvers);
@@ -150,7 +150,7 @@ public class ResourceWebHandler
* Configure the list of {@link ResourceTransformer}s to use.
* <p>By default no transformers are configured for use.
*/
public void setResourceTransformers(List<ResourceTransformer> resourceTransformers) {
public void setResourceTransformers(@Nullable List<ResourceTransformer> resourceTransformers) {
this.resourceTransformers.clear();
if (resourceTransformers != null) {
this.resourceTransformers.addAll(resourceTransformers);
@@ -172,6 +172,11 @@ public class ResourceWebHandler
this.cacheControl = cacheControl;
}
/**
* Return the {@link org.springframework.http.CacheControl} instance to build
* the Cache-Control HTTP response header.
*/
@Nullable
public CacheControl getCacheControl() {
return this.cacheControl;
}
@@ -187,6 +192,7 @@ public class ResourceWebHandler
/**
* Return the configured resource message writer.
*/
@Nullable
public ResourceHttpMessageWriter getResourceHttpMessageWriter() {
return this.resourceHttpMessageWriter;
}
@@ -204,21 +210,18 @@ public class ResourceWebHandler
/**
* Return the configured {@link CompositeContentTypeResolver}.
*/
@Nullable
public CompositeContentTypeResolver getContentTypeResolver() {
return this.contentTypeResolver;
}
@Override
public void afterPropertiesSet() throws Exception {
if (logger.isWarnEnabled() && CollectionUtils.isEmpty(this.locations)) {
logger.warn("Locations list is empty. No resources will be served unless a " +
"custom ResourceResolver is configured as an alternative to PathResourceResolver.");
}
if (this.resourceResolvers.isEmpty()) {
this.resourceResolvers.add(new PathResourceResolver());
}
initAllowedLocations();
if (this.resourceHttpMessageWriter == null) {
if (getResourceHttpMessageWriter() == null) {
this.resourceHttpMessageWriter = new ResourceHttpMessageWriter();
}
}
@@ -230,6 +233,10 @@ public class ResourceWebHandler
*/
protected void initAllowedLocations() {
if (CollectionUtils.isEmpty(this.locations)) {
if (logger.isWarnEnabled()) {
logger.warn("Locations list is empty. No resources will be served unless a " +
"custom ResourceResolver is configured as an alternative to PathResourceResolver.");
}
return;
}
for (int i = getResourceResolvers().size() - 1; i >= 0; i--) {
@@ -275,7 +282,6 @@ public class ResourceWebHandler
*/
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
return getResource(exchange)
.switchIfEmpty(Mono.defer(() -> {
logger.trace("No matching resource found - returning 404");
@@ -292,7 +298,8 @@ public class ResourceWebHandler
// Supported methods and required session
HttpMethod httpMethod = exchange.getRequest().getMethod();
if (!SUPPORTED_METHODS.contains(httpMethod)) {
return Mono.error(new MethodNotAllowedException(httpMethod, SUPPORTED_METHODS));
return Mono.error(new MethodNotAllowedException(
exchange.getRequest().getMethodValue(), SUPPORTED_METHODS));
}
// Header phase
@@ -332,7 +339,9 @@ public class ResourceWebHandler
}
setHeaders(exchange, resource, mediaType);
return this.resourceHttpMessageWriter.write(Mono.just(resource),
ResourceHttpMessageWriter writer = getResourceHttpMessageWriter();
Assert.state(writer != null, "No ResourceHttpMessageWriter");
return writer.write(Mono.just(resource),
null, ResolvableType.forClass(Resource.class), mediaType,
exchange.getRequest(), exchange.getResponse(), Collections.emptyMap());
}
@@ -485,7 +494,7 @@ public class ResourceWebHandler
* @param resource the identified resource (never {@code null})
* @param mediaType the resource's media type (never {@code null})
*/
protected void setHeaders(ServerWebExchange exchange, Resource resource, MediaType mediaType)
protected void setHeaders(ServerWebExchange exchange, Resource resource, @Nullable MediaType mediaType)
throws IOException {
HttpHeaders headers = exchange.getResponse().getHeaders();

View File

@@ -156,16 +156,16 @@ public class VersionResourceResolver extends AbstractResourceResolver {
@Override
protected Mono<Resource> resolveResourceInternal(ServerWebExchange exchange, String requestPath,
List<? extends Resource> locations, ResourceResolverChain chain) {
protected Mono<Resource> resolveResourceInternal(@Nullable ServerWebExchange exchange,
String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
return chain.resolveResource(exchange, requestPath, locations)
.switchIfEmpty(Mono.defer(() ->
resolveVersionedResource(exchange, requestPath, locations, chain)));
}
private Mono<Resource> resolveVersionedResource(ServerWebExchange exchange, String requestPath,
List<? extends Resource> locations, ResourceResolverChain chain) {
private Mono<Resource> resolveVersionedResource(@Nullable ServerWebExchange exchange,
String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
VersionStrategy versionStrategy = getStrategyForPath(requestPath);
if (versionStrategy == null) {

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.
@@ -17,7 +17,6 @@
package org.springframework.web.reactive.resource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
/**
* An extension of {@link VersionPathStrategy} that adds a method
@@ -35,7 +34,6 @@ public interface VersionStrategy extends VersionPathStrategy {
* @param resource the resource to check
* @return the version (never {@code null})
*/
@Nullable
String getResourceVersion(Resource resource);
}

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.
@@ -72,8 +72,8 @@ public class WebJarsResourceResolver extends AbstractResourceResolver {
@Override
protected Mono<Resource> resolveResourceInternal(ServerWebExchange exchange, String requestPath,
List<? extends Resource> locations, ResourceResolverChain chain) {
protected Mono<Resource> resolveResourceInternal(@Nullable ServerWebExchange exchange,
String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
return chain.resolveResource(exchange, requestPath, locations)
.switchIfEmpty(Mono.defer(() -> {

View File

@@ -25,6 +25,7 @@ import java.util.Set;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
@@ -112,7 +113,7 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
}
return this;
}
return matchRequestMethod(exchange.getRequest().getMethod().name());
return matchRequestMethod(exchange.getRequest().getMethod());
}
/**
@@ -125,11 +126,10 @@ public final class RequestMethodsRequestCondition extends AbstractRequestConditi
return this;
}
HttpMethod expectedMethod = request.getHeaders().getAccessControlRequestMethod();
return matchRequestMethod(expectedMethod.name());
return matchRequestMethod(expectedMethod);
}
private RequestMethodsRequestCondition matchRequestMethod(String httpMethodValue) {
HttpMethod httpMethod = HttpMethod.resolve(httpMethodValue);
private RequestMethodsRequestCondition matchRequestMethod(@Nullable HttpMethod httpMethod) {
if (httpMethod != null) {
for (RequestMethod method : getMethods()) {
if (httpMethod.matches(method.name())) {

View File

@@ -158,13 +158,13 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = getApplicationContext().getBeanNamesForType(Object.class);
String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
@@ -186,19 +186,20 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
*/
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);
obtainApplicationContext().getType((String) handler) : handler.getClass());
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
if (handlerType != null) {
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));
if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
methods.forEach((key, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
methods.forEach((key, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
/**
@@ -225,7 +226,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
if (handler instanceof String) {
String beanName = (String) handler;
handlerMethod = new HandlerMethod(beanName,
getApplicationContext().getAutowireCapableBeanFactory(), method);
obtainApplicationContext().getAutowireCapableBeanFactory(), method);
}
else {
handlerMethod = new HandlerMethod(handler, method);
@@ -428,7 +429,6 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
* @param exchange the current exchange
* @return the comparator (never {@code null})
*/
@Nullable
protected abstract Comparator<T> getMappingComparator(ServerWebExchange exchange);
@@ -462,6 +462,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
* Return matches for the given URL path. Not thread-safe.
* @see #acquireReadLock()
*/
@Nullable
public List<T> getMappingsByUrl(String urlPath) {
return this.urlLookup.get(urlPath);
}
@@ -572,7 +573,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
private final List<String> directUrls;
public MappingRegistration(T mapping, HandlerMethod handlerMethod, List<String> directUrls) {
public MappingRegistration(T mapping, HandlerMethod handlerMethod, @Nullable List<String> directUrls) {
Assert.notNull(mapping, "Mapping must not be null");
Assert.notNull(handlerMethod, "HandlerMethod must not be null");
this.mapping = mapping;

View File

@@ -32,6 +32,7 @@ import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
@@ -194,7 +195,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
}
private IllegalStateException getArgumentError(String text, MethodParameter parameter, Throwable ex) {
private IllegalStateException getArgumentError(String text, MethodParameter parameter, @Nullable Throwable ex) {
return new IllegalStateException(getDetailedErrorMessage(text, parameter), ex);
}

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.
@@ -564,6 +564,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
* {@code registeredSuffixPatternMatch=true}, the extensions are obtained
* from the configured {@code contentTypeResolver}.
*/
@Nullable
public Set<String> getFileExtensions() {
RequestedContentTypeResolver resolver = getContentTypeResolver();
if (useRegisteredSuffixPatternMatch() && resolver != null) {
@@ -582,6 +583,7 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
this.contentTypeResolver = resolver;
}
@Nullable
public RequestedContentTypeResolver getContentTypeResolver() {
return this.contentTypeResolver;
}

View File

@@ -105,8 +105,9 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
BindingContext bindingContext, ServerWebExchange exchange) {
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(bodyType.resolve());
ResolvableType elementType = (adapter != null ? bodyType.getGeneric(0) : bodyType);
Class<?> resolvedType = bodyType.resolve();
ReactiveAdapter adapter = (resolvedType != null ? getAdapterRegistry().getAdapter(resolvedType) : null);
ResolvableType elementType = (adapter != null ? bodyType.getGeneric() : bodyType);
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
@@ -161,7 +162,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
}
private ServerWebInputException handleMissingBody(MethodParameter param) {
return new ServerWebInputException("Request body is missing: " + param.getMethod().toGenericString());
return new ServerWebInputException("Request body is missing: " + param.getExecutable().toGenericString());
}
/**

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.method.annotation;
import java.util.Collections;
@@ -30,6 +31,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.result.HandlerResultHandlerSupport;
@@ -85,8 +87,7 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa
@SuppressWarnings("unchecked")
protected Mono<Void> writeBody(Object body, MethodParameter bodyParameter, ServerWebExchange exchange) {
protected Mono<Void> writeBody(@Nullable Object body, MethodParameter bodyParameter, ServerWebExchange exchange) {
ResolvableType bodyType = ResolvableType.forMethodParameter(bodyParameter);
Class<?> bodyClass = bodyType.resolve();
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(bodyClass, body);

View File

@@ -152,6 +152,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
* Resolve the given annotation-specified value,
* potentially containing placeholders and expressions.
*/
@Nullable
private Object resolveStringValue(String value) {
if (this.configurableBeanFactory == null) {
return value;
@@ -177,7 +178,8 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
/**
* Apply type conversion if necessary.
*/
private Object applyConversion(Object value, NamedValueInfo namedValueInfo, MethodParameter parameter,
@Nullable
private Object applyConversion(@Nullable Object value, NamedValueInfo namedValueInfo, MethodParameter parameter,
BindingContext bindingContext, ServerWebExchange exchange) {
WebDataBinder binder = bindingContext.createDataBinder(exchange, namedValueInfo.name);
@@ -249,6 +251,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
* A {@code null} results in a {@code false} value for {@code boolean}s or
* an exception for other primitives.
*/
@Nullable
private Object handleNullValue(String name, @Nullable Object value, Class<?> paramType) {
if (value == null) {
if (Boolean.TYPE.equals(paramType)) {
@@ -274,7 +277,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
*/
@SuppressWarnings("UnusedParameters")
protected void handleResolvedValue(
Object arg, String name, MethodParameter parameter, Model model, ServerWebExchange exchange) {
@Nullable Object arg, String name, MethodParameter parameter, Model model, ServerWebExchange exchange) {
}
@@ -290,7 +293,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
private final String defaultValue;
public NamedValueInfo(String name, boolean required, String defaultValue) {
public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
this.name = name;
this.required = required;
this.defaultValue = defaultValue;

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.method.annotation;
import java.lang.reflect.Method;
@@ -52,7 +53,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
@@ -163,7 +164,7 @@ class ControllerMethodResolver {
registrar.addIfModelAttribute(() -> new ModelAttributeMethodArgumentResolver(reactiveRegistry, true));
}
private void initControllerAdviceCaches(ApplicationContext applicationContext) {
private void initControllerAdviceCaches(@Nullable ApplicationContext applicationContext) {
if (applicationContext == null) {
return;
}
@@ -176,25 +177,27 @@ class ControllerMethodResolver {
for (ControllerAdviceBean bean : beans) {
Class<?> beanType = bean.getBeanType();
Set<Method> attrMethods = selectMethods(beanType, ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(bean, attrMethods);
if (logger.isInfoEnabled()) {
logger.info("Detected @ModelAttribute methods in " + bean);
if (beanType != null) {
Set<Method> attrMethods = selectMethods(beanType, ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(bean, attrMethods);
if (logger.isInfoEnabled()) {
logger.info("Detected @ModelAttribute methods in " + bean);
}
}
}
Set<Method> binderMethods = selectMethods(beanType, BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(bean, binderMethods);
if (logger.isInfoEnabled()) {
logger.info("Detected @InitBinder methods in " + bean);
Set<Method> binderMethods = selectMethods(beanType, BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(bean, binderMethods);
if (logger.isInfoEnabled()) {
logger.info("Detected @InitBinder methods in " + bean);
}
}
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(bean, resolver);
if (logger.isInfoEnabled()) {
logger.info("Detected @ExceptionHandler methods in " + bean);
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(bean, resolver);
if (logger.isInfoEnabled()) {
logger.info("Detected @ExceptionHandler methods in " + bean);
}
}
}
}

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.
@@ -23,6 +23,7 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.HttpCookie;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
@@ -57,8 +58,9 @@ public class CookieValueMethodArgumentResolver extends AbstractNamedValueSyncArg
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
CookieValue annotation = parameter.getParameterAnnotation(CookieValue.class);
return new CookieValueNamedValueInfo(annotation);
CookieValue ann = parameter.getParameterAnnotation(CookieValue.class);
Assert.state(ann != null, "No CookieValue annotation");
return new CookieValueNamedValueInfo(ann);
}
@Override
@@ -69,7 +71,7 @@ public class CookieValueMethodArgumentResolver extends AbstractNamedValueSyncArg
return Optional.ofNullable(cookie);
}
else if (cookie != null) {
return Optional.ofNullable(cookie.getValue());
return Optional.of(cookie.getValue());
}
else {
return Optional.empty();

View File

@@ -22,7 +22,6 @@ import org.springframework.core.Conventions;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
@@ -80,18 +79,16 @@ public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverS
"Errors argument must be immediately after a model attribute argument");
int index = parameter.getParameterIndex() - 1;
MethodParameter attributeParam = new MethodParameter(parameter.getMethod(), index);
ResolvableType type = ResolvableType.forMethodParameter(attributeParam);
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type.resolve());
MethodParameter attributeParam = MethodParameter.forExecutable(parameter.getExecutable(), index);
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(attributeParam.getParameterType());
Assert.isNull(adapter, "Errors/BindingResult cannot be used with an async model attribute. " +
"Either declare the model attribute without the async wrapper type " +
"or handle WebExchangeBindException through the async type.");
ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);
if (annot != null && StringUtils.hasText(annot.value())) {
return annot.value();
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null && StringUtils.hasText(ann.value())) {
return ann.value();
}
return Conventions.getVariableNameForParameter(attributeParam);
}

View File

@@ -23,6 +23,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
/**
@@ -50,13 +51,14 @@ public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueSyn
@Override
public boolean supportsParameter(MethodParameter param) {
return checkAnnotatedParamNoReactiveWrapper(param, Value.class, (annot, type) -> true);
return checkAnnotatedParamNoReactiveWrapper(param, Value.class, (ann, type) -> true);
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
Value annotation = parameter.getParameterAnnotation(Value.class);
return new ExpressionValueNamedValueInfo(annotation);
Value ann = parameter.getParameterAnnotation(Value.class);
Assert.state(ann != null, "No Value annotation");
return new ExpressionValueNamedValueInfo(ann);
}
@Override

View File

@@ -20,6 +20,8 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebExchangeDataBinder;
@@ -41,7 +43,7 @@ class InitBinderBindingContext extends BindingContext {
private final BindingContext binderMethodContext;
InitBinderBindingContext(WebBindingInitializer initializer,
InitBinderBindingContext(@Nullable WebBindingInitializer initializer,
List<SyncInvocableHandlerMethod> binderMethods) {
super(initializer);
@@ -56,8 +58,9 @@ class InitBinderBindingContext extends BindingContext {
this.binderMethods.stream()
.filter(binderMethod -> {
InitBinder annotation = binderMethod.getMethodAnnotation(InitBinder.class);
Collection<String> names = Arrays.asList(annotation.value());
InitBinder ann = binderMethod.getMethodAnnotation(InitBinder.class);
Assert.state(ann != null, "No InitBinder annotation");
Collection<String> names = Arrays.asList(ann.value());
return (names.size() == 0 || names.contains(dataBinder.getObjectName()));
})
.forEach(method -> invokeBinderMethod(dataBinder, exchange, method));

View File

@@ -35,6 +35,7 @@ import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -106,8 +107,9 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
MethodParameter parameter, BindingContext context, ServerWebExchange exchange) {
ResolvableType type = ResolvableType.forMethodParameter(parameter);
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type.resolve());
ResolvableType valueType = (adapter != null ? type.getGeneric(0) : type);
Class<?> resolvedType = type.resolve();
ReactiveAdapter adapter = (resolvedType != null ? getAdapterRegistry().getAdapter(resolvedType) : null);
ResolvableType valueType = (adapter != null ? type.getGeneric() : type);
Assert.state(adapter == null || !adapter.isMultiValue(),
() -> getClass().getSimpleName() + " doesn't support multi-value reactive type wrapper: " +
@@ -165,7 +167,9 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
}
if (attribute == null) {
return createAttribute(attributeName, attributeType.getRawClass(), context, exchange);
Class<?> attributeClass = attributeType.getRawClass();
Assert.state(attributeClass != null, "No attribute class");
return createAttribute(attributeName,attributeClass , context, exchange);
}
ReactiveAdapter adapterFrom = getAdapterRegistry().getAdapter(null, attribute);
@@ -178,6 +182,7 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
}
}
@Nullable
private Object findAndRemoveReactiveAttribute(Model model, String attributeName) {
return model.asMap().entrySet().stream()
.filter(entry -> {
@@ -236,7 +241,7 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
private boolean hasErrorsArgument(MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();
return (paramTypes.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
}

View File

@@ -100,7 +100,7 @@ class ModelInitializer {
private String getAttributeName(MethodParameter param) {
return Optional
.ofNullable(AnnotatedElementUtils.findMergedAnnotation(param.getMethod(), ModelAttribute.class))
.ofNullable(AnnotatedElementUtils.findMergedAnnotation(param.getAnnotatedElement(), ModelAttribute.class))
.filter(ann -> StringUtils.hasText(ann.value()))
.map(ModelAttribute::value)
.orElse(Conventions.getVariableNameForParameter(param));

View File

@@ -25,6 +25,7 @@ import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.Nullable;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ValueConstants;
@@ -75,8 +76,9 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueSyncAr
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
PathVariable annotation = parameter.getParameterAnnotation(PathVariable.class);
return new PathVariableNamedValueInfo(annotation);
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
Assert.state(ann != null, "No PathVariable annotation");
return new PathVariableNamedValueInfo(ann);
}
@Override
@@ -95,7 +97,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueSyncAr
@Override
@SuppressWarnings("unchecked")
protected void handleResolvedValue(
Object arg, String name, MethodParameter parameter, Model model, ServerWebExchange exchange) {
@Nullable Object arg, String name, MethodParameter parameter, Model model, ServerWebExchange exchange) {
// TODO: View.PATH_VARIABLES ?
}

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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.method.annotation;
import java.util.Optional;
@@ -21,6 +22,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.server.ServerWebExchange;
@@ -55,8 +57,9 @@ public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueSy
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestAttribute annot = parameter.getParameterAnnotation(RequestAttribute.class);
return new NamedValueInfo(annot.name(), annot.required(), ValueConstants.DEFAULT_NONE);
RequestAttribute ann = parameter.getParameterAnnotation(RequestAttribute.class);
Assert.state(ann != null, "No RequestAttribute annotation");
return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
}
@Override

View File

@@ -23,9 +23,9 @@ import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
@@ -61,8 +61,9 @@ public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentRe
public Mono<Object> resolveArgument(
MethodParameter param, BindingContext bindingContext, ServerWebExchange exchange) {
RequestBody annotation = param.getParameterAnnotation(RequestBody.class);
return readBody(param, annotation.required(), bindingContext, exchange);
RequestBody ann = param.getParameterAnnotation(RequestBody.class);
Assert.state(ann != null, "No RequestBody annotation");
return readBody(param, ann.required(), bindingContext, exchange);
}
}

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.
@@ -25,6 +25,7 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.convert.ConversionService;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
@@ -69,8 +70,9 @@ public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueSyncA
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
RequestHeader annotation = parameter.getParameterAnnotation(RequestHeader.class);
return new RequestHeaderNamedValueInfo(annotation);
RequestHeader ann = parameter.getParameterAnnotation(RequestHeader.class);
Assert.state(ann != null, "No RequestHeader annotation");
return new RequestHeaderNamedValueInfo(ann);
}
@Override

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.
@@ -153,6 +153,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
/**
* Return the file extensions to use for suffix pattern matching.
*/
@Nullable
public Set<String> getFileExtensions() {
return this.config.getFileExtensions();
}
@@ -246,19 +247,20 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* result of merging annotation attributes within an annotation hierarchy.
*/
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, RequestCondition<?> customCondition) {
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
return RequestMappingInfo
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name())
.customCondition(customCondition)
.options(this.config)
.build();
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}
/**
@@ -301,7 +303,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
return config.applyPermitDefaultValues();
}
private void updateCorsConfig(CorsConfiguration config, CrossOrigin annotation) {
private void updateCorsConfig(CorsConfiguration config, @Nullable CrossOrigin annotation) {
if (annotation == null) {
return;
}
@@ -336,7 +338,13 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
}
private String resolveCorsAnnotationValue(String value) {
return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value);
if (this.embeddedValueResolver != null) {
String resolved = this.embeddedValueResolver.resolveStringValue(value);
return (resolved != null ? resolved : "");
}
else {
return value;
}
}
}

View File

@@ -31,6 +31,7 @@ import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.reactive.BindingContext;
@@ -71,7 +72,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageReaderArgu
ServerWebExchange exchange) {
RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
boolean isRequired = requestPart == null || requestPart.required();
boolean isRequired = (requestPart == null || requestPart.required());
String name = getPartName(parameter, requestPart);
Flux<Part> partFlux = getPartValues(name, exchange);
@@ -99,7 +100,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageReaderArgu
});
}
private String getPartName(MethodParameter methodParam, RequestPart requestPart) {
private String getPartName(MethodParameter methodParam, @Nullable RequestPart requestPart) {
String partName = (requestPart != null ? requestPart.name() : "");
if (partName.isEmpty()) {
partName = methodParam.getParameterName();

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.method.annotation;
import java.time.Instant;
@@ -30,6 +31,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.reactive.HandlerResultHandler;
@@ -83,11 +85,12 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand
}
ReactiveAdapter adapter = getAdapter(result);
return adapter != null && !adapter.isNoValue() &&
isSupportedType(result.getReturnType().getGeneric(0).resolve(Object.class));
isSupportedType(result.getReturnType().getGeneric().resolve(Object.class));
}
private boolean isSupportedType(Class<?> clazz) {
return (HttpEntity.class.isAssignableFrom(clazz) && !RequestEntity.class.isAssignableFrom(clazz));
private boolean isSupportedType(@Nullable Class<?> clazz) {
return (clazz != null && HttpEntity.class.isAssignableFrom(clazz) &&
!RequestEntity.class.isAssignableFrom(clazz));
}

View File

@@ -77,7 +77,7 @@ public class ServerWebExchangeArgumentResolver extends HandlerMethodArgumentReso
return Optional.of(exchange.getResponse());
}
else if (HttpMethod.class == paramType) {
return Optional.of(exchange.getRequest().getMethod());
return Optional.ofNullable(exchange.getRequest().getMethod());
}
else {
// should never happen...

View File

@@ -23,6 +23,7 @@ import reactor.core.publisher.Mono;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.server.ServerWebExchange;
@@ -49,8 +50,9 @@ public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueAr
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
SessionAttribute annot = parameter.getParameterAnnotation(SessionAttribute.class);
return new NamedValueInfo(annot.name(), annot.required(), ValueConstants.DEFAULT_NONE);
SessionAttribute ann = parameter.getParameterAnnotation(SessionAttribute.class);
Assert.state(ann != null, "No SessionAttribute annotation");
return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
}
@Override

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.
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.view;
import java.util.Locale;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
/**
* Abstract base class for URL-based views. Provides a consistent way of
@@ -56,6 +58,7 @@ public abstract class AbstractUrlBasedView extends AbstractView implements Initi
/**
* Return the URL of the resource that this view wraps.
*/
@Nullable
public String getUrl() {
return this.url;
}

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.
@@ -18,17 +18,21 @@ package org.springframework.web.reactive.result.view;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -77,7 +81,7 @@ public abstract class AbstractView implements View, ApplicationContextAware {
* Set the supported media types for this view.
* Default is "text/html;charset=UTF-8".
*/
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
public void setSupportedMediaTypes(@Nullable List<MediaType> supportedMediaTypes) {
Assert.notEmpty(supportedMediaTypes, "MediaType List must not be empty");
this.mediaTypes.clear();
if (supportedMediaTypes != null) {
@@ -115,7 +119,7 @@ public abstract class AbstractView implements View, ApplicationContextAware {
* Set the name of the RequestContext attribute for this view.
* Default is none.
*/
public void setRequestContextAttribute(String requestContextAttribute) {
public void setRequestContextAttribute(@Nullable String requestContextAttribute) {
this.requestContextAttribute = requestContextAttribute;
}
@@ -132,10 +136,24 @@ public abstract class AbstractView implements View, ApplicationContextAware {
this.applicationContext = applicationContext;
}
@Nullable
public ApplicationContext getApplicationContext() {
return this.applicationContext;
}
/**
* Obtain the ApplicationContext for actual use.
* @return the ApplicationContext (never {@code null})
* @throws IllegalStateException in case of no ApplicationContext set
* @since 5.0
*/
protected final ApplicationContext obtainApplicationContext() {
ApplicationContext applicationContext = getApplicationContext();
Assert.state(applicationContext != null, "No ApplicationContext");
return applicationContext;
}
/**
* Prepare the model to render.
* @param model Map with name Strings as keys and corresponding model
@@ -171,7 +189,7 @@ public abstract class AbstractView implements View, ApplicationContextAware {
* <p>The default implementation creates a combined output Map that includes
* model as well as static attributes with the former taking precedence.
*/
protected Mono<Map<String, Object>> getModelAttributes(Map<String, ?> model, ServerWebExchange exchange) {
protected Mono<Map<String, Object>> getModelAttributes(@Nullable Map<String, ?> model, ServerWebExchange exchange) {
int size = (model != null ? model.size() : 0);
Map<String, Object> attributes = new LinkedHashMap<>(size);
@@ -241,7 +259,7 @@ public abstract class AbstractView implements View, ApplicationContextAware {
* @see #setRequestContextAttribute
*/
protected RequestContext createRequestContext(ServerWebExchange exchange, Map<String, Object> model) {
return new RequestContext(exchange, model, getApplicationContext(), getRequestDataValueProcessor());
return new RequestContext(exchange, model, obtainApplicationContext(), getRequestDataValueProcessor());
}
/**
@@ -269,7 +287,7 @@ public abstract class AbstractView implements View, ApplicationContextAware {
*@param exchange current exchange @return {@code Mono} to represent when and if rendering succeeds
*/
protected abstract Mono<Void> renderInternal(Map<String, Object> renderAttributes,
MediaType contentType, ServerWebExchange exchange);
@Nullable MediaType contentType, ServerWebExchange exchange);
@Override

View File

@@ -21,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.Model;
/**
@@ -43,7 +44,7 @@ class DefaultRendering implements Rendering {
private final HttpHeaders headers;
DefaultRendering(Object view, Model model, HttpStatus status, HttpHeaders headers) {
DefaultRendering(Object view, @Nullable Model model, HttpStatus status, @Nullable HttpHeaders headers) {
this.view = view;
this.model = (model != null ? model.asMap() : Collections.emptyMap());
this.status = status;

View File

@@ -35,7 +35,6 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
/**
* {@code View} that writes model attribute(s) with an {@link HttpMessageWriter}.
*
@@ -97,7 +96,7 @@ public class HttpMessageWriterView implements View {
* otherwise raise an {@link IllegalStateException}.
* </ul>
*/
public void setModelKeys(Set<String> modelKeys) {
public void setModelKeys(@Nullable Set<String> modelKeys) {
this.modelKeys.clear();
if (modelKeys != null) {
this.modelKeys.addAll(modelKeys);
@@ -122,7 +121,10 @@ public class HttpMessageWriterView implements View {
}
@Nullable
private Object getObjectToRender(Map<String, ?> model) {
private Object getObjectToRender(@Nullable Map<String, ?> model) {
if (model == null) {
return null;
}
Map<String, ?> result = model.entrySet().stream()
.filter(this::isMatch)
@@ -155,7 +157,7 @@ public class HttpMessageWriterView implements View {
}
@SuppressWarnings("unchecked")
private <T> Mono<Void> write(T value, MediaType contentType, ServerWebExchange exchange) {
private <T> Mono<Void> write(T value, @Nullable MediaType contentType, ServerWebExchange exchange) {
Publisher<T> input = Mono.justOrEmpty(value);
ResolvableType elementType = ResolvableType.forClass(value.getClass());
return ((HttpMessageWriter<T>) this.writer).write(

View File

@@ -31,6 +31,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -120,6 +121,7 @@ public class RedirectView extends AbstractUrlBasedView {
/**
* Get the redirect status code to use.
*/
@Nullable
public HttpStatus getStatusCode() {
return this.statusCode;
}
@@ -197,12 +199,14 @@ public class RedirectView extends AbstractUrlBasedView {
* RequestDataValueProcessor}.
*/
protected final String createTargetUrl(Map<String, Object> model, ServerWebExchange exchange) {
String url = getUrl();
Assert.state(url != null, "'url' not set");
StringBuilder targetUrl = new StringBuilder();
if (isContextRelative() && getUrl().startsWith("/")) {
if (isContextRelative() && url.startsWith("/")) {
targetUrl.append(exchange.getRequest().getContextPath());
}
targetUrl.append(getUrl());
targetUrl.append(url);
if (StringUtils.hasText(targetUrl)) {
Map<String, String> uriVars = getCurrentUriVariables(exchange);
@@ -296,7 +300,10 @@ public class RedirectView extends AbstractUrlBasedView {
ServerHttpResponse response = exchange.getResponse();
String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeUrl(targetUrl));
response.getHeaders().setLocation(URI.create(encodedURL));
response.setStatusCode(getStatusCode());
HttpStatus status = getStatusCode();
if (status != null) {
response.setStatusCode(status);
}
return Mono.empty();
}

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.HashMap;
@@ -73,7 +74,7 @@ public class RequestContext {
}
public RequestContext(ServerWebExchange exchange, Map<String, Object> model, MessageSource messageSource,
RequestDataValueProcessor dataValueProcessor) {
@Nullable RequestDataValueProcessor dataValueProcessor) {
Assert.notNull(exchange, "'exchange' is required");
Assert.notNull(model, "'model' is required");
@@ -368,7 +369,11 @@ public class RequestContext {
Errors errors = this.errorsMap.get(name);
if (errors == null) {
errors = getModelObject(BindingResult.MODEL_KEY_PREFIX + name);
if (errors == null) {
return null;
}
}
if (errors instanceof BindException) {
errors = ((BindException) errors).getBindingResult();
}

View File

@@ -24,6 +24,7 @@ import reactor.core.publisher.Mono;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;
/**
@@ -87,8 +88,8 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
* which by default is AbstractUrlBasedView.
*/
public void setViewClass(Class<?> viewClass) {
if (viewClass == null || !requiredViewClass().isAssignableFrom(viewClass)) {
String name = (viewClass != null ? viewClass.getName() : null);
if (!requiredViewClass().isAssignableFrom(viewClass)) {
String name = viewClass.getName();
throw new IllegalArgumentException("Given view class [" + name + "] " +
"is not of type [" + requiredViewClass().getName() + "]");
}
@@ -98,6 +99,7 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
/**
* Return the view class to be used to create views.
*/
@Nullable
protected Class<?> getViewClass() {
return this.viewClass;
}
@@ -114,7 +116,7 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
/**
* Set the prefix that gets prepended to view names when building a URL.
*/
public void setPrefix(String prefix) {
public void setPrefix(@Nullable String prefix) {
this.prefix = (prefix != null ? prefix : "");
}
@@ -128,7 +130,7 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
/**
* Set the suffix that gets appended to view names when building a URL.
*/
public void setSuffix(String suffix) {
public void setSuffix(@Nullable String suffix) {
this.suffix = (suffix != null ? suffix : "");
}
@@ -153,6 +155,7 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
* Return the view names (or name patterns) that can be handled by this
* {@link ViewResolver}.
*/
@Nullable
protected String[] getViewNames() {
return this.viewNames;
}
@@ -243,11 +246,15 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
* @return the View instance
*/
protected AbstractUrlBasedView createUrlBasedView(String viewName) {
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
Class<?> viewClass = getViewClass();
Assert.state(viewClass != null, "No view class");
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
view.setSupportedMediaTypes(getSupportedMediaTypes());
view.setRequestContextAttribute(getRequestContextAttribute());
view.setDefaultCharset(getDefaultCharset());
view.setUrl(getPrefix() + viewName + getSuffix());
return view;
}

View File

@@ -38,6 +38,7 @@ import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
@@ -129,7 +130,7 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
* Set the default views to consider always when resolving view names and
* trying to satisfy the best matching content type.
*/
public void setDefaultViews(List<View> defaultViews) {
public void setDefaultViews(@Nullable List<View> defaultViews) {
this.defaultViews.clear();
if (defaultViews != null) {
this.defaultViews.addAll(defaultViews);
@@ -158,10 +159,11 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
type = result.getReturnType().getGeneric().resolve(Object.class);
}
return (CharSequence.class.isAssignableFrom(type) || Rendering.class.isAssignableFrom(type) ||
Model.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type) ||
void.class.equals(type) || View.class.isAssignableFrom(type) ||
!BeanUtils.isSimpleProperty(type));
return (type != null &&
(CharSequence.class.isAssignableFrom(type) || Rendering.class.isAssignableFrom(type) ||
Model.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type) ||
void.class.equals(type) || View.class.isAssignableFrom(type) ||
!BeanUtils.isSimpleProperty(type)));
}
private boolean hasModelAnnotation(MethodParameter parameter) {
@@ -299,10 +301,10 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
});
}
private boolean isBindingCandidate(String name, Object value) {
return !name.startsWith(BindingResult.MODEL_KEY_PREFIX) && value != null &&
private boolean isBindingCandidate(String name, @Nullable Object value) {
return (!name.startsWith(BindingResult.MODEL_KEY_PREFIX) && value != null &&
!value.getClass().isArray() && !(value instanceof Collection) &&
!(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass());
!(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass()));
}
private Mono<? extends Void> render(List<View> views, Map<String, Object> model,

View File

@@ -25,6 +25,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -56,7 +57,7 @@ public abstract class ViewResolverSupport implements ApplicationContextAware, Or
* Set the supported media types for this view.
* Default is "text/html;charset=UTF-8".
*/
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
public void setSupportedMediaTypes(@Nullable List<MediaType> supportedMediaTypes) {
Assert.notEmpty(supportedMediaTypes, "MediaType List must not be empty");
this.mediaTypes.clear();
if (supportedMediaTypes != null) {

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.
@@ -39,9 +39,12 @@ import reactor.core.publisher.Mono;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
import org.springframework.web.server.ServerWebExchange;
@@ -83,10 +86,23 @@ public class FreeMarkerView extends AbstractUrlBasedView {
/**
* Return the FreeMarker configuration used by this view.
*/
@Nullable
protected Configuration getConfiguration() {
return this.configuration;
}
/**
* Obtain the FreeMarker configuration for actual use.
* @return the FreeMarker configuration (never {@code null})
* @throws IllegalStateException in case of no Configuration object set
* @since 5.0
*/
protected Configuration obtainConfiguration() {
Configuration configuration = getConfiguration();
Assert.state(configuration != null, "No Configuration set");
return configuration;
}
/**
* Set the encoding of the FreeMarker template file.
* <p>By default {@link FreeMarkerConfigurer} sets the default encoding in
@@ -101,6 +117,7 @@ public class FreeMarkerView extends AbstractUrlBasedView {
/**
* Return the encoding for the FreeMarker template.
*/
@Nullable
protected String getEncoding() {
return this.encoding;
}
@@ -124,7 +141,7 @@ public class FreeMarkerView extends AbstractUrlBasedView {
protected FreeMarkerConfig autodetectConfiguration() throws BeansException {
try {
return BeanFactoryUtils.beanOfTypeIncludingAncestors(
getApplicationContext(), FreeMarkerConfig.class, true, false);
obtainApplicationContext(), FreeMarkerConfig.class, true, false);
}
catch (NoSuchBeanDefinitionException ex) {
throw new ApplicationContextException(
@@ -164,8 +181,8 @@ public class FreeMarkerView extends AbstractUrlBasedView {
}
@Override
protected Mono<Void> renderInternal(Map<String, Object> renderAttributes, MediaType contentType,
ServerWebExchange exchange) {
protected Mono<Void> renderInternal(Map<String, Object> renderAttributes,
@Nullable MediaType contentType, ServerWebExchange exchange) {
// Expose all standard FreeMarker hash models.
SimpleHash freeMarkerModel = getTemplateModel(renderAttributes, exchange);
@@ -192,7 +209,7 @@ public class FreeMarkerView extends AbstractUrlBasedView {
return exchange.getResponse().writeWith(Flux.just(dataBuffer));
}
private Charset getCharset(MediaType mediaType) {
private Charset getCharset(@Nullable MediaType mediaType) {
return Optional.ofNullable(mediaType).map(MimeType::getCharset).orElse(getDefaultCharset());
}
@@ -215,7 +232,7 @@ public class FreeMarkerView extends AbstractUrlBasedView {
* @see freemarker.template.Configuration#getObjectWrapper()
*/
protected ObjectWrapper getObjectWrapper() {
ObjectWrapper ow = getConfiguration().getObjectWrapper();
ObjectWrapper ow = obtainConfiguration().getObjectWrapper();
Version version = Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS;
return (ow != null ? ow : new DefaultObjectWrapperBuilder(version).build());
}
@@ -230,8 +247,8 @@ public class FreeMarkerView extends AbstractUrlBasedView {
*/
protected Template getTemplate(Locale locale) throws IOException {
return (getEncoding() != null ?
getConfiguration().getTemplate(getUrl(), locale, getEncoding()) :
getConfiguration().getTemplate(getUrl(), locale));
obtainConfiguration().getTemplate(getUrl(), locale, getEncoding()) :
obtainConfiguration().getTemplate(getUrl(), locale));
}
}

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.
@@ -19,6 +19,8 @@ package org.springframework.web.reactive.result.view.script;
import java.nio.charset.Charset;
import javax.script.ScriptEngine;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented by objects that configure and manage a
* JSR-223 {@link ScriptEngine} for automatic lookup in a web environment.
@@ -32,42 +34,50 @@ public interface ScriptTemplateConfig {
/**
* Return the {@link ScriptEngine} to use by the views.
*/
@Nullable
ScriptEngine getEngine();
/**
* Return the engine name that will be used to instantiate the {@link ScriptEngine}.
*/
@Nullable
String getEngineName();
/**
* Return whether to use a shared engine for all threads or whether to create
* thread-local engine instances for each thread.
*/
@Nullable
Boolean isSharedEngine();
/**
* Return the scripts to be loaded by the script engine (library or user provided).
*/
@Nullable
String[] getScripts();
/**
* Return the object where the render function belongs (optional).
*/
@Nullable
String getRenderObject();
/**
* Return the render function name (mandatory).
*/
@Nullable
String getRenderFunction();
/**
* Return the charset used to read script and template files.
*/
@Nullable
Charset getCharset();
/**
* Return the resource loader path(s) via a Spring resource location.
*/
@Nullable
String getResourceLoaderPath();
}

View File

@@ -234,7 +234,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
protected ScriptEngine createEngineFromName() {
if (this.scriptEngineManager == null) {
this.scriptEngineManager = new ScriptEngineManager(getApplicationContext().getClassLoader());
this.scriptEngineManager = new ScriptEngineManager(obtainApplicationContext().getClassLoader());
}
ScriptEngine engine = StandardScriptUtils.retrieveEngineByName(this.scriptEngineManager, this.engineName);
@@ -273,7 +273,7 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
protected ScriptTemplateConfig autodetectViewConfig() throws BeansException {
try {
return BeanFactoryUtils.beanOfTypeIncludingAncestors(
getApplicationContext(), ScriptTemplateConfig.class, true, false);
obtainApplicationContext(), ScriptTemplateConfig.class, true, false);
}
catch (NoSuchBeanDefinitionException ex) {
throw new ApplicationContextException("Expected a single ScriptTemplateConfig bean in the current " +
@@ -284,18 +284,25 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
@Override
public boolean checkResourceExists(Locale locale) throws Exception {
return (getResource(getUrl()) != null);
String url = getUrl();
Assert.state(url != null, "'url' not set");
return (getResource(url) != null);
}
@Override
protected Mono<Void> renderInternal(Map<String, Object> model, MediaType contentType, ServerWebExchange exchange) {
protected Mono<Void> renderInternal(
Map<String, Object> model, @Nullable MediaType contentType, ServerWebExchange exchange) {
return Mono.defer(() -> {
ServerHttpResponse response = exchange.getResponse();
try {
ScriptEngine engine = getEngine();
Invocable invocable = (Invocable) engine;
String url = getUrl();
Assert.state(url != null, "'url' not set");
String template = getTemplate(url);
Function<String, String> templateLoader = path -> {
try {
return getTemplate(path);
@@ -304,7 +311,9 @@ public class ScriptTemplateView extends AbstractUrlBasedView {
throw new IllegalStateException(ex);
}
};
RenderingContext context = new RenderingContext(this.getApplicationContext(), this.locale, templateLoader, url);
RenderingContext context = new RenderingContext(
obtainApplicationContext(), this.locale, templateLoader, url);
Object html;
if (this.renderObject != null) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 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.
@@ -151,7 +151,7 @@ public final class CloseStatus {
* @param code the status code
* @param reason the reason
*/
public CloseStatus(int code, String reason) {
public CloseStatus(int code, @Nullable String reason) {
Assert.isTrue((code >= 1000 && code < 5000), "Invalid status code");
this.code = code;
this.reason = reason;

View File

@@ -51,7 +51,7 @@ public class HandshakeInfo {
* @param principal the principal for the session
* @param protocol the negotiated sub-protocol (may be {@code null})
*/
public HandshakeInfo(URI uri, HttpHeaders headers, Mono<Principal> principal, String protocol) {
public HandshakeInfo(URI uri, HttpHeaders headers, Mono<Principal> principal, @Nullable String protocol) {
Assert.notNull(uri, "URI is required");
Assert.notNull(headers, "HttpHeaders are required");
Assert.notNull(principal, "Principal is required");

View File

@@ -29,6 +29,7 @@ import reactor.core.publisher.MonoProcessor;
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.web.reactive.socket.CloseStatus;
import org.springframework.web.reactive.socket.HandshakeInfo;
import org.springframework.web.reactive.socket.WebSocketMessage;
@@ -84,7 +85,7 @@ public abstract class AbstractListenerWebSocketSession<T> extends AbstractWebSoc
* the session completion (success or error) (for client-side use).
*/
public AbstractListenerWebSocketSession(T delegate, String id, HandshakeInfo handshakeInfo,
DataBufferFactory bufferFactory, MonoProcessor<Void> completionMono) {
DataBufferFactory bufferFactory, @Nullable MonoProcessor<Void> completionMono) {
super(delegate, id, handshakeInfo, bufferFactory);
this.completionMono = completionMono;

View File

@@ -81,21 +81,17 @@ public abstract class AbstractWebSocketSession<T> implements WebSocketSession {
return this.handshakeInfo;
}
@Override
public Flux<WebSocketMessage> receive() {
return null;
}
@Override
public Mono<Void> send(Publisher<WebSocketMessage> messages) {
return null;
}
@Override
public DataBufferFactory bufferFactory() {
return this.bufferFactory;
}
@Override
public abstract Flux<WebSocketMessage> receive();
@Override
public abstract Mono<Void> send(Publisher<WebSocketMessage> messages);
// WebSocketMessage factory methods

View File

@@ -27,6 +27,7 @@ import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.socket.CloseStatus;
@@ -52,7 +53,7 @@ public class JettyWebSocketSession extends AbstractListenerWebSocketSession<Sess
}
public JettyWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory,
MonoProcessor<Void> completionMono) {
@Nullable MonoProcessor<Void> completionMono) {
super(session, ObjectUtils.getIdentityHexString(session), info, factory, completionMono);
}

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.
@@ -29,6 +29,7 @@ import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.socket.CloseStatus;
import org.springframework.web.reactive.socket.HandshakeInfo;
import org.springframework.web.reactive.socket.WebSocketMessage;
@@ -44,13 +45,12 @@ import org.springframework.web.reactive.socket.WebSocketSession;
*/
public class StandardWebSocketSession extends AbstractListenerWebSocketSession<Session> {
public StandardWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory) {
this(session, info, factory, null);
}
public StandardWebSocketSession(Session session, HandshakeInfo info, DataBufferFactory factory,
MonoProcessor<Void> completionMono) {
@Nullable MonoProcessor<Void> completionMono) {
super(session, session.getId(), info, factory, completionMono);
}

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.
@@ -28,6 +28,7 @@ import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.socket.CloseStatus;
import org.springframework.web.reactive.socket.HandshakeInfo;
@@ -44,13 +45,12 @@ import org.springframework.web.reactive.socket.WebSocketSession;
*/
public class UndertowWebSocketSession extends AbstractListenerWebSocketSession<WebSocketChannel> {
public UndertowWebSocketSession(WebSocketChannel channel, HandshakeInfo info, DataBufferFactory factory) {
this(channel, info, factory, null);
}
public UndertowWebSocketSession(WebSocketChannel channel, HandshakeInfo info,
DataBufferFactory factory, MonoProcessor<Void> completionMono) {
DataBufferFactory factory, @Nullable MonoProcessor<Void> completionMono) {
super(channel, ObjectUtils.getIdentityHexString(channel), info, factory, completionMono);
}

View File

@@ -171,7 +171,6 @@ public class HandshakeWebSocketService implements WebSocketService, Lifecycle {
@Override
public Mono<Void> handleRequest(ServerWebExchange exchange, WebSocketHandler handler) {
ServerHttpRequest request = exchange.getRequest();
HttpMethod method = request.getMethod();
HttpHeaders headers = request.getHeaders();
@@ -181,7 +180,8 @@ public class HandshakeWebSocketService implements WebSocketService, Lifecycle {
}
if (HttpMethod.GET != method) {
return Mono.error(new MethodNotAllowedException(method, Collections.singleton(HttpMethod.GET)));
return Mono.error(new MethodNotAllowedException(
request.getMethodValue(), Collections.singleton(HttpMethod.GET)));
}
if (!"WebSocket".equalsIgnoreCase(headers.getUpgrade())) {

View File

@@ -152,7 +152,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Life
return ((ServletServerHttpResponse) response).getServletResponse();
}
private HandshakeInfo getHandshakeInfo(ServerWebExchange exchange, String protocol) {
private HandshakeInfo getHandshakeInfo(ServerWebExchange exchange, @Nullable String protocol) {
ServerHttpRequest request = exchange.getRequest();
Mono<Principal> principal = exchange.getPrincipal();
return new HandshakeInfo(request.getURI(), request.getHeaders(), principal, protocol);
@@ -177,7 +177,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Life
private final String protocol;
public WebSocketHandlerContainer(JettyWebSocketHandlerAdapter adapter, String protocol) {
public WebSocketHandlerContainer(JettyWebSocketHandlerAdapter adapter, @Nullable String protocol) {
this.adapter = adapter;
this.protocol = protocol;
}
@@ -186,6 +186,7 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Life
return this.adapter;
}
@Nullable
public String getProtocol() {
return this.protocol;
}

View File

@@ -48,7 +48,7 @@ public class ReactorNettyRequestUpgradeStrategy implements RequestUpgradeStrateg
(in, out) -> handler.handle(new ReactorNettyWebSocketSession(in, out, info, bufferFactory)));
}
private HandshakeInfo getHandshakeInfo(ServerWebExchange exchange, String protocol) {
private HandshakeInfo getHandshakeInfo(ServerWebExchange exchange, @Nullable String protocol) {
ServerHttpRequest request = exchange.getRequest();
Mono<Principal> principal = exchange.getPrincipal();
return new HandshakeInfo(request.getURI(), request.getHeaders(), principal, protocol);

View File

@@ -92,7 +92,7 @@ public class TomcatRequestUpgradeStrategy implements RequestUpgradeStrategy {
return ((ServletServerHttpResponse) response).getServletResponse();
}
private HandshakeInfo getHandshakeInfo(ServerWebExchange exchange, String protocol) {
private HandshakeInfo getHandshakeInfo(ServerWebExchange exchange, @Nullable String protocol) {
ServerHttpRequest request = exchange.getRequest();
Mono<Principal> principal = exchange.getPrincipal();
return new HandshakeInfo(request.getURI(), request.getHeaders(), principal, protocol);

View File

@@ -42,8 +42,8 @@ import org.springframework.core.codec.CharSequenceEncoder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.codec.EncoderHttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
@@ -52,16 +52,15 @@ import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.springframework.core.io.buffer.support.DataBufferTestUtils.dumpString;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.web.method.ResolvableMethod.on;
import static org.springframework.web.reactive.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
import static org.junit.Assert.*;
import static org.springframework.core.io.buffer.support.DataBufferTestUtils.*;
import static org.springframework.http.MediaType.*;
import static org.springframework.web.method.ResolvableMethod.*;
import static org.springframework.web.reactive.HandlerMapping.*;
/**
* Unit tests for {@link AbstractMessageWriterResultHandler}.
*
* @author Rossen Stoyanchev
*/
public class MessageWriterResultHandlerTests {