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

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

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

View File

@@ -18,11 +18,14 @@ package org.springframework.mock.http.client;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.util.Assert;
/**
* Mock implementation of {@link ClientHttpRequest}.
@@ -37,6 +40,7 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie
private URI uri;
@Nullable
private ClientHttpResponse clientHttpResponse;
private boolean executed = false;
@@ -46,6 +50,13 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie
* Default constructor.
*/
public MockClientHttpRequest() {
this.httpMethod = HttpMethod.GET;
try {
this.uri = new URI("/");
}
catch (URISyntaxException ex) {
throw new IllegalStateException(ex);
}
}
/**
@@ -106,6 +117,7 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie
* potentially different than the configured response.
*/
protected ClientHttpResponse executeInternal() throws IOException {
Assert.state(this.clientHttpResponse != null, "No ClientHttpResponse");
return this.clientHttpResponse;
}
@@ -113,12 +125,8 @@ public class MockClientHttpRequest extends MockHttpOutputMessage implements Clie
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (this.httpMethod != null) {
sb.append(this.httpMethod);
}
if (this.uri != null) {
sb.append(" ").append(this.uri);
}
sb.append(this.httpMethod);
sb.append(" ").append(this.uri);
if (!getHeaders().isEmpty()) {
sb.append(", headers: ").append(getHeaders());
}

View File

@@ -84,6 +84,7 @@ import org.springframework.util.ReflectionUtils;
public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder {
/** An instance of this class bound to JNDI */
@Nullable
private static volatile SimpleNamingContextBuilder activated;
private static boolean initialized = false;
@@ -111,17 +112,18 @@ public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder
* to control JNDI bindings
*/
public static SimpleNamingContextBuilder emptyActivatedContextBuilder() throws NamingException {
if (activated != null) {
SimpleNamingContextBuilder builder = activated;
if (builder != null) {
// Clear already activated context builder.
activated.clear();
builder.clear();
}
else {
// Create and activate new context builder.
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder = new SimpleNamingContextBuilder();
// The activate() call will cause an assignment to the activated variable.
builder.activate();
}
return activated;
return builder;
}

View File

@@ -44,10 +44,12 @@ public class MockAsyncContext implements AsyncContext {
private final HttpServletRequest request;
@Nullable
private final HttpServletResponse response;
private final List<AsyncListener> listeners = new ArrayList<>();
@Nullable
private String dispatchedPath;
private long timeout = 10 * 1000L; // 10 seconds is Tomcat's default
@@ -72,6 +74,7 @@ public class MockAsyncContext implements AsyncContext {
}
@Override
@Nullable
public ServletResponse getResponse() {
return this.response;
}
@@ -99,6 +102,7 @@ public class MockAsyncContext implements AsyncContext {
}
}
@Nullable
public String getDispatchedPath() {
return this.dispatchedPath;
}

View File

@@ -29,6 +29,7 @@ import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -43,19 +44,21 @@ import org.springframework.util.ObjectUtils;
* @author Juergen Hoeller
* @author Rob Winch
* @author Rossen Stoyanchev
*
* @since 2.0.3
* @see MockFilterConfig
* @see PassThroughFilterChain
*/
public class MockFilterChain implements FilterChain {
@Nullable
private ServletRequest request;
@Nullable
private ServletResponse response;
private final List<Filter> filters;
@Nullable
private Iterator<Filter> iterator;
@@ -70,7 +73,6 @@ public class MockFilterChain implements FilterChain {
/**
* Create a FilterChain with a Servlet.
*
* @param servlet the Servlet to invoke
* @since 3.2
*/
@@ -80,7 +82,6 @@ public class MockFilterChain implements FilterChain {
/**
* Create a {@code FilterChain} with Filter's and a Servlet.
*
* @param servlet the {@link Servlet} to invoke in this {@link FilterChain}
* @param filters the {@link Filter}'s to invoke in this {@link FilterChain}
* @since 3.2
@@ -96,9 +97,11 @@ public class MockFilterChain implements FilterChain {
return Arrays.asList(allFilters);
}
/**
* Return the request that {@link #doFilter} has been called with.
*/
@Nullable
public ServletRequest getRequest() {
return this.request;
}
@@ -106,6 +109,7 @@ public class MockFilterChain implements FilterChain {
/**
* Return the response that {@link #doFilter} has been called with.
*/
@Nullable
public ServletResponse getResponse() {
return this.response;
}

View File

@@ -169,10 +169,13 @@ public class MockHttpServletRequest implements HttpServletRequest {
private final Map<String, Object> attributes = new LinkedHashMap<>();
@Nullable
private String characterEncoding;
@Nullable
private byte[] content;
@Nullable
private String contentType;
private final Map<String, String[]> parameters = new LinkedHashMap<>(16);
@@ -206,6 +209,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
private boolean asyncSupported = false;
@Nullable
private MockAsyncContext asyncContext;
private DispatcherType dispatcherType = DispatcherType.REQUEST;
@@ -215,32 +219,42 @@ public class MockHttpServletRequest implements HttpServletRequest {
// HttpServletRequest properties
// ---------------------------------------------------------------------
@Nullable
private String authType;
@Nullable
private Cookie[] cookies;
private final Map<String, HeaderValueHolder> headers = new LinkedCaseInsensitiveMap<>();
@Nullable
private String method;
@Nullable
private String pathInfo;
private String contextPath = "";
@Nullable
private String queryString;
@Nullable
private String remoteUser;
private final Set<String> userRoles = new HashSet<>();
@Nullable
private Principal userPrincipal;
@Nullable
private String requestedSessionId;
@Nullable
private String requestURI;
private String servletPath = "";
@Nullable
private HttpSession session;
private boolean requestedSessionIdValid = true;
@@ -376,7 +390,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
@Override
public void setCharacterEncoding(String characterEncoding) {
public void setCharacterEncoding(@Nullable String characterEncoding) {
this.characterEncoding = characterEncoding;
updateContentTypeHeader();
}
@@ -401,13 +415,13 @@ public class MockHttpServletRequest implements HttpServletRequest {
* @see #getContentAsByteArray()
* @see #getContentAsString()
*/
public void setContent(byte[] content) {
public void setContent(@Nullable byte[] content) {
this.content = content;
}
/**
* Get the content of the request body as a byte array.
* @return the content as a byte array, potentially {@code null}
* @return the content as a byte array (potentially {@code null})
* @since 5.0
* @see #setContent(byte[])
* @see #getContentAsString()
@@ -932,6 +946,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
@Override
@Nullable
public String getAuthType() {
return this.authType;
}
@@ -1092,6 +1107,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
@Override
@Nullable
public String getMethod() {
return this.method;
}
@@ -1101,6 +1117,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
@Override
@Nullable
public String getPathInfo() {
return this.pathInfo;
}
@@ -1125,6 +1142,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
@Override
@Nullable
public String getQueryString() {
return this.queryString;
}
@@ -1134,6 +1152,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
@Override
@Nullable
public String getRemoteUser() {
return this.remoteUser;
}
@@ -1153,6 +1172,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
@Override
@Nullable
public Principal getUserPrincipal() {
return this.userPrincipal;
}
@@ -1162,6 +1182,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
@Override
@Nullable
public String getRequestedSessionId() {
return this.requestedSessionId;
}
@@ -1171,6 +1192,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
@Override
@Nullable
public String getRequestURI() {
return this.requestURI;
}

View File

@@ -72,6 +72,7 @@ public class MockHttpServletResponse implements HttpServletResponse {
private boolean writerAccessAllowed = true;
@Nullable
private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
private boolean charset = false;
@@ -80,10 +81,12 @@ public class MockHttpServletResponse implements HttpServletResponse {
private final ServletOutputStream outputStream = new ResponseServletOutputStream(this.content);
@Nullable
private PrintWriter writer;
private long contentLength = 0;
@Nullable
private String contentType;
private int bufferSize = 4096;
@@ -103,8 +106,10 @@ public class MockHttpServletResponse implements HttpServletResponse {
private int status = HttpServletResponse.SC_OK;
@Nullable
private String errorMessage;
@Nullable
private String forwardedUrl;
private final List<String> includedUrls = new ArrayList<>();
@@ -295,7 +300,7 @@ public class MockHttpServletResponse implements HttpServletResponse {
this.characterEncoding = null;
this.contentLength = 0;
this.contentType = null;
this.locale = null;
this.locale = Locale.getDefault();
this.cookies.clear();
this.headers.clear();
this.status = HttpServletResponse.SC_OK;
@@ -303,11 +308,9 @@ public class MockHttpServletResponse implements HttpServletResponse {
}
@Override
public void setLocale(@Nullable Locale locale) {
public void setLocale(Locale locale) {
this.locale = locale;
if (locale != null) {
doAddHeaderValue(HttpHeaders.ACCEPT_LANGUAGE, locale.toLanguageTag(), true);
}
doAddHeaderValue(HttpHeaders.ACCEPT_LANGUAGE, locale.toLanguageTag(), true);
}
@Override
@@ -585,7 +588,7 @@ public class MockHttpServletResponse implements HttpServletResponse {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT_LANGUAGE, value.toString());
List<Locale> locales = headers.getAcceptLanguageAsLocales();
setLocale(locales.isEmpty() ? null : locales.get(0));
this.locale = (!locales.isEmpty() ? locales.get(0) : Locale.getDefault());
return true;
}
else {
@@ -629,6 +632,7 @@ public class MockHttpServletResponse implements HttpServletResponse {
return this.status;
}
@Nullable
public String getErrorMessage() {
return this.errorMessage;
}

View File

@@ -35,6 +35,7 @@ public class MockJspWriter extends JspWriter {
private final HttpServletResponse response;
@Nullable
private PrintWriter targetWriter;

View File

@@ -44,6 +44,7 @@ public class MockMultipartFile implements MultipartFile {
private String originalFilename;
@Nullable
private String contentType;
private final byte[] content;
@@ -111,6 +112,7 @@ public class MockMultipartFile implements MultipartFile {
}
@Override
@Nullable
public String getContentType() {
return this.contentType;
}

View File

@@ -62,6 +62,7 @@ public class MockPageContext extends PageContext {
private final Map<String, Object> attributes = new LinkedHashMap<>();
@Nullable
private JspWriter out;

View File

@@ -39,6 +39,7 @@ public class MockPart implements Part {
private final String name;
@Nullable
private final String filename;
private final byte[] content;
@@ -79,6 +80,7 @@ public class MockPart implements Part {
}
@Override
@Nullable
public String getSubmittedFileName() {
return this.filename;
}

View File

@@ -29,7 +29,6 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.RequestDispatcher;
@@ -131,14 +130,17 @@ public class MockServletContext implements ServletContext {
private final Set<String> declaredRoles = new LinkedHashSet<>();
@Nullable
private Set<SessionTrackingMode> sessionTrackingModes;
private final SessionCookieConfig sessionCookieConfig = new MockSessionCookieConfig();
private int sessionTimeout;
@Nullable
private String requestCharacterEncoding;
@Nullable
private String responseCharacterEncoding;
private final Map<String, MediaType> mimeTypes = new LinkedHashMap<>();
@@ -585,6 +587,7 @@ public class MockServletContext implements ServletContext {
}
// @Override - but only against Servlet 4.0
@Nullable
public String getRequestCharacterEncoding() {
return this.requestCharacterEncoding;
}
@@ -595,6 +598,7 @@ public class MockServletContext implements ServletContext {
}
// @Override - but only against Servlet 4.0
@Nullable
public String getResponseCharacterEncoding() {
return this.responseCharacterEncoding;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 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.
@@ -24,6 +24,7 @@ import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -40,10 +41,13 @@ import org.springframework.util.Assert;
*/
public class PassThroughFilterChain implements FilterChain {
@Nullable
private Filter filter;
@Nullable
private FilterChain nextFilterChain;
@Nullable
private Servlet servlet;
@@ -79,6 +83,7 @@ public class PassThroughFilterChain implements FilterChain {
this.filter.doFilter(request, response, this.nextFilterChain);
}
else {
Assert.state(this.servlet != null, "Neither a Filter not a Servlet set");
this.servlet.service(request, response);
}
}

View File

@@ -59,6 +59,7 @@ public class MockServerRequest implements ServerRequest {
private final MockHeaders headers;
@Nullable
private final Object body;
private final Map<String, Object> attributes;
@@ -67,14 +68,16 @@ public class MockServerRequest implements ServerRequest {
private final Map<String, String> pathVariables;
@Nullable
private final WebSession session;
@Nullable
private Principal principal;
private MockServerRequest(HttpMethod method, URI uri, MockHeaders headers, @Nullable Object body,
Map<String, Object> attributes, MultiValueMap<String, String> queryParams,
Map<String, String> pathVariables, WebSession session, Principal principal) {
Map<String, String> pathVariables, @Nullable WebSession session, @Nullable Principal principal) {
this.method = method;
this.uri = uri;
@@ -209,6 +212,7 @@ public class MockServerRequest implements ServerRequest {
private MockHeaders headers = new MockHeaders(new HttpHeaders());
@Nullable
private Object body;
private Map<String, Object> attributes = new ConcurrentHashMap<>();
@@ -217,8 +221,10 @@ public class MockServerRequest implements ServerRequest {
private Map<String, String> pathVariables = new LinkedHashMap<>();
@Nullable
private WebSession session;
@Nullable
private Principal principal;
@Override

View File

@@ -51,9 +51,9 @@ public class ContextConfigurationAttributes {
private final Class<?> declaringClass;
private Class<?>[] classes;
private Class<?>[] classes = new Class<?>[0];
private String[] locations;
private String[] locations = new String[0];
private final boolean inheritLocations;
@@ -61,6 +61,7 @@ public class ContextConfigurationAttributes {
private final boolean inheritInitializers;
@Nullable
private final String name;
private final Class<? extends ContextLoader> contextLoaderClass;
@@ -204,7 +205,7 @@ public class ContextConfigurationAttributes {
* @see #setClasses(Class[])
*/
public Class<?>[] getClasses() {
return (this.classes != null ? this.classes : new Class<?>[0]);
return this.classes;
}
/**
@@ -215,7 +216,7 @@ public class ContextConfigurationAttributes {
* @see #hasLocations()
*/
public boolean hasClasses() {
return !ObjectUtils.isEmpty(getClasses());
return (getClasses().length > 0);
}
/**
@@ -239,7 +240,7 @@ public class ContextConfigurationAttributes {
* @see #setLocations
*/
public String[] getLocations() {
return (this.locations != null ? this.locations : new String[0]);
return this.locations;
}
/**
@@ -250,7 +251,7 @@ public class ContextConfigurationAttributes {
* @see #hasClasses()
*/
public boolean hasLocations() {
return !ObjectUtils.isEmpty(getLocations());
return (getLocations().length > 0);
}
/**

View File

@@ -96,8 +96,10 @@ public class MergedContextConfiguration implements Serializable {
private final ContextLoader contextLoader;
@Nullable
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
@Nullable
private final MergedContextConfiguration parent;

View File

@@ -104,10 +104,11 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
Assert.state(ClassUtils.isPresent("org.junit.internal.Throwables", SpringJUnit4ClassRunner.class.getClassLoader()),
"SpringJUnit4ClassRunner requires JUnit 4.12 or higher.");
withRulesMethod = ReflectionUtils.findMethod(SpringJUnit4ClassRunner.class, "withRules",
Method method = ReflectionUtils.findMethod(SpringJUnit4ClassRunner.class, "withRules",
FrameworkMethod.class, Object.class, Statement.class);
Assert.state(withRulesMethod != null, "SpringJUnit4ClassRunner requires JUnit 4.12 or higher");
ReflectionUtils.makeAccessible(withRulesMethod);
Assert.state(method != null, "SpringJUnit4ClassRunner requires JUnit 4.12 or higher");
ReflectionUtils.makeAccessible(method);
withRulesMethod = method;
}

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.
@@ -46,6 +46,7 @@ public class ProfileValueChecker extends Statement {
private final Class<?> testClass;
@Nullable
private final Method testMethod;

View File

@@ -50,10 +50,13 @@ public class DefaultTestContext implements TestContext {
private final Class<?> testClass;
@Nullable
private volatile Object testInstance;
@Nullable
private volatile Method testMethod;
@Nullable
private volatile Throwable testException;
@@ -133,15 +136,18 @@ public class DefaultTestContext implements TestContext {
}
public final Object getTestInstance() {
Assert.state(this.testInstance != null, "No test instance");
return this.testInstance;
Object testInstance = this.testInstance;
Assert.state(testInstance != null, "No test instance");
return testInstance;
}
public final Method getTestMethod() {
Assert.state(this.testMethod != null, "No test method");
return this.testMethod;
Method testMethod = this.testMethod;
Assert.state(testMethod != null, "No test method");
return testMethod;
}
@Nullable
public final Throwable getTestException() {
return this.testException;
}

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,13 @@ import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestResult;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -34,14 +41,6 @@ import org.springframework.test.context.support.DirtiesContextBeforeModesTestExe
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.web.ServletTestExecutionListener;
import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestResult;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
/**
* Abstract base test class which integrates the <em>Spring TestContext Framework</em>
* with explicit {@link ApplicationContext} testing support in a <strong>TestNG</strong>
@@ -96,10 +95,12 @@ public abstract class AbstractTestNGSpringContextTests implements IHookable, App
* The {@link ApplicationContext} that was injected into this test instance
* via {@link #setApplicationContext(ApplicationContext)}.
*/
@Nullable
protected ApplicationContext applicationContext;
private final TestContextManager testContextManager;
@Nullable
private Throwable testException;

View File

@@ -188,6 +188,7 @@ public abstract class AbstractTransactionalTestNGSpringContextTests extends Abst
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
DataSource ds = this.jdbcTemplate.getDataSource();
Assert.state(ds != null, "No DataSource set");
Assert.state(this.applicationContext != null, "No ApplicationContext available");
Resource resource = this.applicationContext.getResource(sqlResourcePath);
new ResourceDatabasePopulator(continueOnError, false, this.sqlScriptEncoding, resource).execute(ds);
}

View File

@@ -50,6 +50,7 @@ class TransactionContext {
private boolean flaggedForRollback;
@Nullable
private TransactionStatus transactionStatus;
private volatile int transactionsStarted = 0;

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.
@@ -307,7 +307,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
* @see #getTransactionManager(TestContext)
*/
@Nullable
protected PlatformTransactionManager getTransactionManager(TestContext testContext, String qualifier) {
protected PlatformTransactionManager getTransactionManager(TestContext testContext, @Nullable String qualifier) {
// Look up by type and qualifier from @Transactional
if (StringUtils.hasText(qualifier)) {
try {

View File

@@ -287,6 +287,7 @@ public abstract class MetaAnnotationUtils {
private final Class<?> declaringClass;
@Nullable
private final Annotation composedAnnotation;
private final T annotation;
@@ -306,8 +307,10 @@ public abstract class MetaAnnotationUtils {
this.declaringClass = declaringClass;
this.composedAnnotation = composedAnnotation;
this.annotation = annotation;
this.annotationAttributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
rootDeclaringClass, annotation.annotationType().getName(), false, false);
Assert.state(attributes != null, "No annotation attributes");
this.annotationAttributes = attributes;
}
public Class<?> getRootDeclaringClass() {
@@ -345,6 +348,7 @@ public abstract class MetaAnnotationUtils {
return this.annotationAttributes;
}
@Nullable
public Annotation getComposedAnnotation() {
return this.composedAnnotation;
}

View File

@@ -23,6 +23,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.support.RestGatewaySupport;
@@ -210,8 +211,10 @@ public class MockRestServiceServer {
private static class DefaultBuilder implements MockRestServiceServerBuilder {
@Nullable
private final RestTemplate restTemplate;
@Nullable
private final org.springframework.web.client.AsyncRestTemplate asyncRestTemplate;
private boolean ignoreExpectOrder;

View File

@@ -21,6 +21,7 @@ import java.util.Iterator;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -37,6 +38,7 @@ import org.springframework.util.Assert;
*/
public class SimpleRequestExpectationManager extends AbstractRequestExpectationManager {
@Nullable
private Iterator<RequestExpectation> expectationIterator;
private final RequestExpectationGroup repeatExpectations = new RequestExpectationGroup();
@@ -52,7 +54,7 @@ public class SimpleRequestExpectationManager extends AbstractRequestExpectationM
public ClientHttpResponse validateRequestInternal(ClientHttpRequest request) throws IOException {
RequestExpectation expectation = this.repeatExpectations.findExpectation(request);
if (expectation == null) {
if (!this.expectationIterator.hasNext()) {
if (this.expectationIterator == null || !this.expectationIterator.hasNext()) {
throw createUnexpectedRequestError(request);
}
expectation = this.expectationIterator.next();

View File

@@ -44,6 +44,7 @@ public class DefaultResponseCreator implements ResponseCreator {
private byte[] content = new byte[0];
@Nullable
private Resource contentResource;
private final HttpHeaders headers = new HttpHeaders();

View File

@@ -25,6 +25,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.Validator;
@@ -138,23 +139,30 @@ class DefaultControllerSpec extends AbstractMockServerSpec<WebTestClient.Control
private class TestWebFluxConfigurer implements WebFluxConfigurer {
@Nullable
private Consumer<RequestedContentTypeResolverBuilder> contentTypeResolverConsumer;
@Nullable
private Consumer<CorsRegistry> corsRegistryConsumer;
@Nullable
private Consumer<ArgumentResolverConfigurer> argumentResolverConsumer;
@Nullable
private Consumer<PathMatchConfigurer> pathMatchConsumer;
@Nullable
private Consumer<ServerCodecConfigurer> messageCodecsConsumer;
@Nullable
private Consumer<FormatterRegistry> formattersConsumer;
@Nullable
private Validator validator;
@Nullable
private Consumer<ViewResolverRegistry> viewResolversConsumer;
@Override
public void configureContentTypeResolver(RequestedContentTypeResolverBuilder builder) {
if (this.contentTypeResolverConsumer != null) {
@@ -198,6 +206,7 @@ class DefaultControllerSpec extends AbstractMockServerSpec<WebTestClient.Control
}
@Override
@Nullable
public Validator getValidator() {
return this.validator;
}

View File

@@ -51,11 +51,9 @@ import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriBuilder;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.springframework.test.util.AssertionErrors.assertEquals;
import static org.springframework.test.util.AssertionErrors.assertTrue;
import static org.springframework.web.reactive.function.BodyExtractors.toFlux;
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
import static java.nio.charset.StandardCharsets.*;
import static org.springframework.test.util.AssertionErrors.*;
import static org.springframework.web.reactive.function.BodyExtractors.*;
/**
* Default implementation of {@link WebTestClient}.
@@ -78,6 +76,7 @@ class DefaultWebTestClient implements WebTestClient {
DefaultWebTestClient(WebClient.Builder clientBuilder, ClientHttpConnector connector,
@Nullable Duration timeout, WebTestClient.Builder webTestClientBuilder) {
Assert.notNull(clientBuilder, "WebClient.Builder is required");
this.wiretapConnector = new WiretapConnector(connector);
this.webClient = clientBuilder.clientConnector(this.wiretapConnector).build();
@@ -173,6 +172,7 @@ class DefaultWebTestClient implements WebTestClient {
private final WebClient.RequestBodySpec bodySpec;
@Nullable
private final String uriTemplate;
private final String requestId;
@@ -270,6 +270,7 @@ class DefaultWebTestClient implements WebTestClient {
private DefaultResponseSpec toResponseSpec(Mono<ClientResponse> mono) {
ClientResponse clientResponse = mono.block(getTimeout());
Assert.state(clientResponse != null, "No ClientResponse");
ExchangeResult exchangeResult = wiretapConnector.claimRequest(this.requestId);
return new DefaultResponseSpec(exchangeResult, clientResponse, this.uriTemplate, getTimeout());
}
@@ -283,7 +284,7 @@ class DefaultWebTestClient implements WebTestClient {
private final Duration timeout;
UndecodedExchangeResult(ExchangeResult result, ClientResponse response,
String uriTemplate, Duration timeout) {
@Nullable String uriTemplate, Duration timeout) {
super(result, uriTemplate);
this.response = response;
@@ -318,7 +319,9 @@ class DefaultWebTestClient implements WebTestClient {
private final UndecodedExchangeResult result;
DefaultResponseSpec(ExchangeResult result, ClientResponse response, String uriTemplate, Duration timeout) {
DefaultResponseSpec(
ExchangeResult result, ClientResponse response, @Nullable String uriTemplate, Duration timeout) {
this.result = new UndecodedExchangeResult(result, response, uriTemplate, timeout);
}

View File

@@ -42,10 +42,13 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
private final WebClient.Builder webClientBuilder;
@Nullable
private final WebHttpHandlerBuilder httpHandlerBuilder;
@Nullable
private final ClientHttpConnector connector;
@Nullable
private Duration responseTimeout;
@@ -58,11 +61,10 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
}
DefaultWebTestClientBuilder(@Nullable WebClient.Builder webClientBuilder,
@Nullable WebHttpHandlerBuilder httpHandlerBuilder,
@Nullable ClientHttpConnector connector,
@Nullable WebHttpHandlerBuilder httpHandlerBuilder, @Nullable ClientHttpConnector connector,
@Nullable Duration responseTimeout) {
Assert.isTrue(httpHandlerBuilder != null || connector !=null,
Assert.isTrue(httpHandlerBuilder != null || connector != null,
"Either WebHttpHandlerBuilder or ClientHttpConnector must be provided");
this.webClientBuilder = (webClientBuilder != null ? webClientBuilder : WebClient.builder());
@@ -139,11 +141,14 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
return this;
}
@Override
public WebTestClient build() {
ClientHttpConnector connectorToUse = (this.connector != null ? this.connector :
new HttpHandlerConnector(this.httpHandlerBuilder.build()));
ClientHttpConnector connectorToUse = this.connector;
if (connectorToUse == null) {
Assert.state(this.httpHandlerBuilder != null, "No WebHttpHandlerBuilder available");
connectorToUse = new HttpHandlerConnector(this.httpHandlerBuilder.build());
}
DefaultWebTestClientBuilder webTestClientBuilder = new DefaultWebTestClientBuilder(
this.webClientBuilder.clone(), this.httpHandlerBuilder,

View File

@@ -29,6 +29,7 @@ import org.springframework.lang.Nullable;
*/
public class EntityExchangeResult<T> extends ExchangeResult {
@Nullable
private final T body;

View File

@@ -34,6 +34,7 @@ import org.springframework.http.ResponseCookie;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
/**
* Container for request and response details for exchanges performed through
@@ -61,6 +62,7 @@ public class ExchangeResult {
private final WiretapClientHttpResponse response;
@Nullable
private final String uriTemplate;
@@ -78,7 +80,7 @@ public class ExchangeResult {
* Constructor to copy the from the yet undecoded ExchangeResult with extra
* information to expose such as the original URI template used, if any.
*/
ExchangeResult(ExchangeResult other, String uriTemplate) {
ExchangeResult(ExchangeResult other, @Nullable String uriTemplate) {
this.request = other.request;
this.response = other.response;
this.uriTemplate = uriTemplate;
@@ -127,6 +129,7 @@ public class ExchangeResult {
* Return the raw request body content written as a {@code byte[]}.
* @throws IllegalStateException if the request body is not fully written yet.
*/
@Nullable
public byte[] getRequestBodyContent() {
MonoProcessor<byte[]> body = this.request.getRecordedContent();
Assert.isTrue(body.isTerminated(), "Request body incomplete.");
@@ -159,9 +162,10 @@ public class ExchangeResult {
* Return the raw request body content written as a {@code byte[]}.
* @throws IllegalStateException if the response is not fully read yet.
*/
@Nullable
public byte[] getResponseBodyContent() {
MonoProcessor<byte[]> body = this.response.getRecordedContent();
Assert.state(body.isTerminated(), "Response body incomplete.");
Assert.state(body.isTerminated(), "Response body incomplete");
return body.block(Duration.ZERO);
}
@@ -208,27 +212,23 @@ public class ExchangeResult {
private String formatBody(@Nullable MediaType contentType, MonoProcessor<byte[]> body) {
if (body.isSuccess()) {
byte[] bytes = body.block(Duration.ZERO);
if (bytes.length == 0) {
if (ObjectUtils.isEmpty(bytes)) {
return "No content";
}
if (contentType == null) {
return "Unknown content type (" + bytes.length + " bytes)";
}
Charset charset = contentType.getCharset();
if (charset != null) {
return new String(bytes, charset);
}
if (PRINTABLE_MEDIA_TYPES.stream().anyMatch(contentType::isCompatibleWith)) {
return new String(bytes, StandardCharsets.UTF_8);
}
return "Unknown charset (" + bytes.length + " bytes)";
}
else if (body.isError()) {
return "I/O failure: " + body.getError().getMessage();
return "I/O failure: " + body.getError();
}
else {
return "Content not available yet";

View File

@@ -90,7 +90,7 @@ public class FluxExchangeResult<T> extends ExchangeResult {
public byte[] getResponseBodyContent() {
return this.body.ignoreElements()
.timeout(this.timeout, Mono.error(TIMEOUT_ERROR))
.then(Mono.defer(() -> Mono.just(super.getResponseBodyContent())))
.then(Mono.defer(() -> Mono.justOrEmpty(super.getResponseBodyContent())))
.block();
}

View File

@@ -42,12 +42,16 @@ class DefaultMvcResult implements MvcResult {
private final MockHttpServletResponse mockResponse;
@Nullable
private Object handler;
@Nullable
private HandlerInterceptor[] interceptors;
@Nullable
private ModelAndView modelAndView;
@Nullable
private Exception resolvedException;
private final AtomicReference<Object> asyncResult = new AtomicReference<>(RESULT_NONE);
@@ -72,11 +76,12 @@ class DefaultMvcResult implements MvcResult {
return this.mockResponse;
}
public void setHandler(@Nullable Object handler) {
public void setHandler(Object handler) {
this.handler = handler;
}
@Override
@Nullable
public Object getHandler() {
return this.handler;
}
@@ -86,6 +91,7 @@ class DefaultMvcResult implements MvcResult {
}
@Override
@Nullable
public HandlerInterceptor[] getInterceptors() {
return this.interceptors;
}
@@ -95,6 +101,7 @@ class DefaultMvcResult implements MvcResult {
}
@Override
@Nullable
public Exception getResolvedException() {
return this.resolvedException;
}
@@ -104,6 +111,7 @@ class DefaultMvcResult implements MvcResult {
}
@Override
@Nullable
public ModelAndView getModelAndView() {
return this.modelAndView;
}

View File

@@ -23,6 +23,7 @@ import javax.servlet.Filter;
import javax.servlet.ServletContext;
import org.springframework.beans.Mergeable;
import org.springframework.lang.Nullable;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -68,6 +69,7 @@ public final class MockMvc {
private final ServletContext servletContext;
@Nullable
private RequestBuilder defaultRequestBuilder;
private List<ResultMatcher> defaultResultMatchers = new ArrayList<>();

View File

@@ -75,12 +75,16 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable {
private final WebRequest webRequest;
@Nullable
private String contextPath;
@Nullable
private RequestBuilder parentBuilder;
@Nullable
private SmartRequestBuilder parentPostProcessor;
@Nullable
private RequestPostProcessor forwardPostProcessor;

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,6 +18,7 @@ package org.springframework.test.web.servlet.htmlunit;
import com.gargoylesoftware.htmlunit.WebClient;
import org.springframework.lang.Nullable;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.util.Assert;
@@ -42,6 +43,7 @@ import org.springframework.web.context.WebApplicationContext;
*/
public class MockMvcWebClientBuilder extends MockMvcWebConnectionBuilderSupport<MockMvcWebClientBuilder> {
@Nullable
private WebClient webClient;

View File

@@ -87,11 +87,11 @@ public final class MockMvcWebConnection implements WebConnection {
* to {@link javax.servlet.http.HttpServletRequest#getContextPath()}
* which states that it can be an empty string and otherwise must start
* with a "/" character and not end with a "/" character.
* @param mockMvc the {@code MockMvc} instance to use; never {@code null}
* @param webClient the {@link WebClient} to use. never {@code null}
* @param mockMvc the {@code MockMvc} instance to use (never {@code null})
* @param webClient the {@link WebClient} to use (never {@code null})
* @param contextPath the contextPath to use
*/
public MockMvcWebConnection(MockMvc mockMvc, WebClient webClient, @Nullable String contextPath) {
public MockMvcWebConnection(MockMvc mockMvc, WebClient webClient, String contextPath) {
Assert.notNull(mockMvc, "MockMvc must not be null");
Assert.notNull(webClient, "WebClient must not be null");
validateContextPath(contextPath);

View File

@@ -22,7 +22,6 @@ import java.util.List;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConnection;
import org.springframework.lang.Nullable;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection.DelegateWebConnection;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@@ -93,7 +92,7 @@ public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebCon
* @return this builder for further customization
*/
@SuppressWarnings("unchecked")
public T contextPath(@Nullable String contextPath) {
public T contextPath(String contextPath) {
this.contextPath = contextPath;
return (T) this;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.WebClient;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.lang.Nullable;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.htmlunit.MockMvcWebConnectionBuilderSupport;
import org.springframework.test.web.servlet.htmlunit.WebRequestMatcher;
@@ -48,6 +49,7 @@ import org.springframework.web.context.WebApplicationContext;
*/
public class MockMvcHtmlUnitDriverBuilder extends MockMvcWebConnectionBuilderSupport<MockMvcHtmlUnitDriverBuilder> {
@Nullable
private HtmlUnitDriver driver;
private boolean javascriptEnabled = true;

View File

@@ -49,6 +49,7 @@ import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
@@ -87,18 +88,25 @@ public class MockHttpServletRequestBuilder
private String servletPath = "";
@Nullable
private String pathInfo = "";
@Nullable
private Boolean secure;
@Nullable
private Principal principal;
@Nullable
private MockHttpSession session;
@Nullable
private String characterEncoding;
@Nullable
private byte[] content;
@Nullable
private String contentType;
private final MultiValueMap<String, Object> headers = new LinkedMultiValueMap<>();
@@ -205,7 +213,7 @@ public class MockHttpServletRequestBuilder
* <p>If specified, the pathInfo will be used as-is.
* @see javax.servlet.http.HttpServletRequest#getPathInfo()
*/
public MockHttpServletRequestBuilder pathInfo(String pathInfo) {
public MockHttpServletRequestBuilder pathInfo(@Nullable String pathInfo) {
if (StringUtils.hasText(pathInfo)) {
Assert.isTrue(pathInfo.startsWith("/"), "Path info must start with a '/'");
}
@@ -704,7 +712,7 @@ public class MockHttpServletRequestBuilder
HttpInputMessage message = new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(content);
return (content != null ? new ByteArrayInputStream(content) : StreamUtils.emptyInput());
}
@Override
public HttpHeaders getHeaders() {

View File

@@ -21,6 +21,7 @@ import java.util.List;
import javax.servlet.Filter;
import javax.servlet.ServletContext;
import org.springframework.lang.Nullable;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.test.web.servlet.DispatcherServletCustomizer;
import org.springframework.test.web.servlet.MockMvc;
@@ -53,6 +54,7 @@ public abstract class AbstractMockMvcBuilder<B extends AbstractMockMvcBuilder<B>
private List<Filter> filters = new ArrayList<>();
@Nullable
private RequestBuilder defaultRequestBuilder;
private final List<ResultMatcher> globalResultMatchers = new ArrayList<>();

View File

@@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.setup;
import javax.servlet.http.HttpSession;
import org.springframework.lang.Nullable;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.web.context.WebApplicationContext;
@@ -42,6 +44,7 @@ import org.springframework.web.context.WebApplicationContext;
*/
public class SharedHttpSessionConfigurer implements MockMvcConfigurer {
@Nullable
private HttpSession session;

View File

@@ -29,6 +29,7 @@ import javax.servlet.ServletContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
@@ -90,6 +91,7 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
private final List<Object> controllers;
@Nullable
private List<Object> controllerAdvice;
private List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
@@ -100,26 +102,34 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
private final List<MappedInterceptor> mappedInterceptors = new ArrayList<>();
private Validator validator = null;
@Nullable
private Validator validator;
@Nullable
private ContentNegotiationManager contentNegotiationManager;
private FormattingConversionService conversionService = null;
@Nullable
private FormattingConversionService conversionService;
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;
@Nullable
private Long asyncRequestTimeout;
@Nullable
private List<ViewResolver> viewResolvers;
private LocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
private FlashMapManager flashMapManager = null;
@Nullable
private FlashMapManager flashMapManager;
private boolean useSuffixPatternMatch = true;
private boolean useTrailingSlashPatternMatch = true;
@Nullable
private Boolean removeSemicolonContent;
private Map<String, String> placeholderValues = new HashMap<>();
@@ -447,7 +457,7 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
@Override
public FormattingConversionService mvcConversionService() {
return (conversionService != null) ? conversionService : super.mvcConversionService();
return (conversionService != null ? conversionService : super.mvcConversionService());
}
@Override
@@ -478,7 +488,10 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
}
for (HandlerExceptionResolver resolver : handlerExceptionResolvers) {
if (resolver instanceof ApplicationContextAware) {
((ApplicationContextAware) resolver).setApplicationContext(getApplicationContext());
ApplicationContext applicationContext = getApplicationContext();
if (applicationContext != null) {
((ApplicationContextAware) resolver).setApplicationContext(applicationContext);
}
}
if (resolver instanceof InitializingBean) {
try {