From 65e7b6a486768330a8f5649de2808e37e569fec8 Mon Sep 17 00:00:00 2001 From: Nic Munroe Date: Wed, 20 Feb 2019 12:52:25 -0800 Subject: [PATCH] Add nullability annotations to APIs commonly used by services This significantly improves kotlin support. --- build.gradle | 6 +- riposte-archaius/build.gradle | 5 +- .../riposte/archaius/ArchaiusServerTest.java | 13 +- .../java/com/nike/riposte/util/AwsUtil.java | 24 ++- ...ifyAsyncHttpClientHelperComponentTest.java | 11 +- riposte-auth/build.gradle | 5 +- .../BasicAuthSecurityValidator.java | 16 +- .../PolymorphicSecurityValidator.java | 16 +- .../BasicAuthSecurityValidatorTest.java | 20 ++- .../java/com/nike/riposte/server/Server.java | 19 +- .../handler/ExceptionHandlingHandler.java | 36 ++-- .../NonblockingEndpointExecutionHandler.java | 24 ++- .../RequestContentValidationHandler.java | 8 +- .../handler/RequestInfoSetterHandler.java | 1 + .../server/handler/ResponseFilterHandler.java | 4 +- .../handler/SecurityValidationHandler.java | 2 +- .../server/http/ProxyRouterEndpoint.java | 76 +++++--- .../riposte/server/http/ResponseSender.java | 23 ++- .../riposte/server/http/StandardEndpoint.java | 8 +- .../http/impl/SimpleProxyRouterEndpoint.java | 64 +++++-- .../HttpChannelInitializerTest.java | 3 +- ...erAsynchronousProcessingComponentTest.java | 11 +- ...AutoPayloadDecompressionComponentTest.java | 31 ++-- ...equestAndResponseFiltersComponentTest.java | 37 ++-- .../VerifyCornerCasesComponentTest.java | 13 +- ...erifyDecoderFailedResultIsHandledTest.java | 38 ++-- ...ingConfigBehaviorAndTagsComponentTest.java | 24 +-- ...rResponsePayloadHandlingComponentTest.java | 68 ++++--- ...scellaneousFunctionalityComponentTest.java | 52 ++++-- ...ifyMultipartRequestsWorkComponentTest.java | 11 +- .../VerifyPayloadHandlingComponentTest.java | 84 ++++++--- ...dpointExecutionWorkChainComponentTest.java | 60 ++++--- ...questOrResponseByDefaultComponentTest.java | 30 ++-- ...yProxyRequestCornerCasesComponentTest.java | 50 +++--- ...oxyRouterTracingBehaviorComponentTest.java | 24 ++- ...uestAndResponseFilteringComponentTest.java | 56 ++++-- ...ifyRequestSizeValidationComponentTest.java | 32 +++- ...deHandlingRfcCorrectnessComponentTest.java | 26 ++- .../componenttest/VerifySSLComponentTest.java | 11 +- ...artHttpContentCompressorComponentTest.java | 11 +- ...xyConnectionPoolingWorksComponentTest.java | 39 ++-- ...nblockingEndpointExecutionHandlerTest.java | 19 ++ .../server/http/ProxyRouterEndpointTest.java | 98 +++++++++- .../server/http/StandardEndpointTest.java | 89 +++++++--- .../impl/SimpleProxyRouterEndpointTest.java | 139 +++++++++++++++ .../ErrorContractSerializerHelperTest.java | 8 +- riposte-guice-typesafe-config/build.gradle | 5 +- riposte-guice/build.gradle | 5 +- .../build.gradle | 5 +- ...SignalFxAwareCodahaleMetricsCollector.java | 124 +++++++++---- ...alFxAwareCodahaleMetricsCollectorTest.java | 65 +++++++ riposte-metrics-codahale/build.gradle | 5 +- .../codahale/CodahaleMetricsCollector.java | 63 ++++--- .../codahale/CodahaleMetricsListener.java | 4 +- .../riposte/metrics/MetricsComponentTest.java | 18 +- .../CodahaleMetricsCollectorTest.java | 13 ++ .../codahale/CodahaleMetricsListenerTest.java | 13 +- ...EndpointMetricsHandlerDefaultImplTest.java | 13 +- .../build.gradle | 5 +- .../eureka/EurekaServerHook.java | 5 +- riposte-servlet-api-adapter/build.gradle | 5 +- riposte-spi/build.gradle | 2 +- .../RequestInfoForLoggingRiposteAdapter.java | 37 ++-- .../riposte/RiposteApiExceptionHandler.java | 23 ++- .../RiposteUnhandledExceptionHandler.java | 27 ++- .../BackstopperRiposteConfigHelper.java | 20 ++- ...rRiposteFrameworkErrorHandlerListener.java | 31 ++-- .../model/riposte/ErrorResponseBodyImpl.java | 32 +++- .../model/riposte/ErrorResponseInfoImpl.java | 23 ++- .../BackstopperRiposteValidatorAdapter.java | 7 +- .../riposte/metrics/MetricsCollector.java | 62 +++---- .../nike/riposte/metrics/MetricsListener.java | 5 +- .../nike/riposte/server/config/AppInfo.java | 25 +-- .../riposte/server/config/ServerConfig.java | 57 +++--- .../server/config/impl/AppInfoImpl.java | 64 +++++-- .../error/handler/ErrorResponseBody.java | 9 +- .../handler/ErrorResponseBodySerializer.java | 4 +- .../error/handler/ErrorResponseInfo.java | 14 +- .../error/handler/RiposteErrorHandler.java | 5 +- .../handler/RiposteUnhandledErrorHandler.java | 6 +- .../impl/DelegatedErrorResponseBody.java | 23 ++- .../validation/RequestSecurityValidator.java | 13 +- .../error/validation/RequestValidator.java | 14 +- .../server/hooks/PipelineCreateHook.java | 5 +- .../server/hooks/PostServerStartupHook.java | 5 +- .../server/hooks/PreServerStartupHook.java | 5 +- .../server/hooks/ServerShutdownHook.java | 5 +- .../nike/riposte/server/http/Endpoint.java | 30 ++-- .../server/http/NonblockingEndpoint.java | 15 +- .../nike/riposte/server/http/RequestInfo.java | 88 +++++---- .../riposte/server/http/ResponseInfo.java | 75 ++++---- .../http/filter/RequestAndResponseFilter.java | 31 +++- ...ortCircuitingRequestAndResponseFilter.java | 35 +++- .../impl/AllowAllTheThingsCORSFilter.java | 21 ++- .../server/http/impl/BaseResponseInfo.java | 55 +++--- .../http/impl/BaseResponseInfoBuilder.java | 44 +++-- .../server/http/impl/ChunkedResponseInfo.java | 39 ++-- .../server/http/impl/FullResponseInfo.java | 48 ++--- .../server/http/impl/RequestInfoImpl.java | 168 +++++++++++------- .../riposte/server/logging/AccessLogger.java | 163 ++++++++++------- .../java/com/nike/riposte/util/HttpUtils.java | 137 +++++++++----- .../com/nike/riposte/util/MainClassUtils.java | 53 ++++-- .../java/com/nike/riposte/util/Matcher.java | 42 +++-- .../com/nike/riposte/util/MatcherUtil.java | 4 +- .../com/nike/riposte/util/MultiMatcher.java | 42 +++-- .../com/nike/riposte/util/SingleMatcher.java | 47 +++-- .../server/logging/AccessLoggerSpec.groovy | 59 +++--- ...questInfoForLoggingRiposteAdapterTest.java | 38 +++- .../riposte/ErrorResponseBodyImplTest.java | 50 ++++++ .../riposte/ErrorResponseInfoImplTest.java | 25 ++- .../server/config/ServerConfigTest.java | 3 +- .../server/config/impl/AppInfoImplTest.java | 27 ++- .../MissingRequiredContentExceptionTest.java | 4 +- ...ultipleMatchingEndpointsExceptionTest.java | 5 +- .../impl/DelegatedErrorResponseBodyTest.java | 12 ++ .../riposte/server/http/EndpointTest.java | 10 +- .../server/http/NonblockingEndpointTest.java | 11 +- .../riposte/server/http/RequestInfoTest.java | 81 ++++----- .../riposte/server/http/ResponseInfoTest.java | 45 ++--- .../filter/RequestAndResponseFilterTest.java | 22 ++- ...ircuitingRequestAndResponseFilterTest.java | 26 +-- .../http/impl/BaseResponseInfoTest.java | 5 +- .../server/http/impl/RequestInfoImplTest.java | 18 +- riposte-typesafe-config/build.gradle | 5 +- .../TypesafeConfigServerTest.java | 13 +- samples/sample-1-helloworld/build.gradle | 5 +- .../src/main/java/com/nike/Main.java | 7 +- .../nike/helloworld/HelloWorldEndpoint.java | 10 +- .../sample-2-kotlin-todoservice/build.gradle | 9 +- .../endpoints/TodoItemsEndpoint.kt | 4 +- .../com/nike/todoservice/TodoEndpointTest.kt | 2 +- 131 files changed, 2719 insertions(+), 1230 deletions(-) create mode 100644 riposte-core/src/test/java/com/nike/riposte/server/http/impl/SimpleProxyRouterEndpointTest.java diff --git a/build.gradle b/build.gradle index 624940e1..000c29bf 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ ext { wingtipsVersion = '0.18.1' backstopperVersion = '0.11.4' fastbreakVersion = '0.10.0' - spockVersion = '0.7-groovy-2.0' + spockVersion = '1.2-groovy-2.5' cgLibVersion = '3.1' objenesisVersion = '2.1' javassistVersion = '3.18.2-GA' @@ -98,9 +98,9 @@ ext { slf4jTestVersion = '1.1.0' guiceVersion = '4.0' - jetbrainsAnnotationsVersion = '16.0.3' + jetbrainsAnnotationsVersion = '17.0.0' - groovyVersion = '2.4.6' + groovyVersion = '2.5.6' restAssuredVersion = '3.0.2' diff --git a/riposte-archaius/build.gradle b/riposte-archaius/build.gradle index 28a90db8..bf96dea3 100644 --- a/riposte-archaius/build.gradle +++ b/riposte-archaius/build.gradle @@ -5,8 +5,11 @@ dependencies { project(":riposte-core"), "com.netflix.archaius:archaius-core:$archaiusVersion" ) - + compileOnly( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", + ) testCompile ( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", "junit:junit:$junitVersion", "org.mockito:mockito-core:$mockitoVersion", "io.rest-assured:rest-assured:$restAssuredVersion", diff --git a/riposte-archaius/src/test/java/com/nike/riposte/archaius/ArchaiusServerTest.java b/riposte-archaius/src/test/java/com/nike/riposte/archaius/ArchaiusServerTest.java index 677b7972..ae25310d 100644 --- a/riposte-archaius/src/test/java/com/nike/riposte/archaius/ArchaiusServerTest.java +++ b/riposte-archaius/src/test/java/com/nike/riposte/archaius/ArchaiusServerTest.java @@ -10,6 +10,7 @@ import com.netflix.config.ConfigurationManager; +import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -52,7 +53,7 @@ private ArchaiusServer generateArchaiusServer(int port) { protected ServerConfig getServerConfig() { return new ServerConfig() { @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return Collections.singleton(new SomeEndpoint()); } @@ -136,14 +137,16 @@ private static class SomeEndpoint extends StandardEndpoint { static final String MATCHING_PATH = "/archaiusValue"; @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { String value = ConfigurationManager.getConfigInstance().getString("archaiusServer.foo"); return CompletableFuture.completedFuture( ResponseInfo.newBuilder(value).build() diff --git a/riposte-async-http-client/src/main/java/com/nike/riposte/util/AwsUtil.java b/riposte-async-http-client/src/main/java/com/nike/riposte/util/AwsUtil.java index cbb2ad20..ffd19ac2 100644 --- a/riposte-async-http-client/src/main/java/com/nike/riposte/util/AwsUtil.java +++ b/riposte-async-http-client/src/main/java/com/nike/riposte/util/AwsUtil.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.ning.http.client.Response; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,7 +83,9 @@ protected AwsUtil() { /* do nothing */ } * completes successfully). If an error occurs retrieving the region from AWS then the error will be logged and * {@link AppInfo#UNKNOWN_VALUE} returned as the value. */ - public static CompletableFuture getAwsRegion(AsyncHttpClientHelper asyncHttpClientHelper) { + public static @NotNull CompletableFuture<@NotNull String> getAwsRegion( + @NotNull AsyncHttpClientHelper asyncHttpClientHelper + ) { return asyncHttpClientHelper.executeAsyncHttpRequest( asyncHttpClientHelper.getRequestBuilder(AMAZON_METADATA_DOCUMENT_URL, HttpMethod.GET), response -> { @@ -127,7 +130,9 @@ public static CompletableFuture getAwsRegion(AsyncHttpClientHelper async * completes successfully). If an error occurs retrieving the instance ID from AWS then the error will be logged and * {@link AppInfo#UNKNOWN_VALUE} returned as the value. */ - public static CompletableFuture getAwsInstanceId(AsyncHttpClientHelper asyncHttpClientHelper) { + public static @NotNull CompletableFuture<@NotNull String> getAwsInstanceId( + @NotNull AsyncHttpClientHelper asyncHttpClientHelper + ) { return asyncHttpClientHelper.executeAsyncHttpRequest( asyncHttpClientHelper.getRequestBuilder(AMAZON_METADATA_INSTANCE_ID_URL, HttpMethod.GET), Response::getResponseBody @@ -157,7 +162,9 @@ public static CompletableFuture getAwsInstanceId(AsyncHttpClientHelper a * See {@link #getAppInfoFutureWithAwsInfo(String, String, AsyncHttpClientHelper)} for more details on how the * {@link AppInfo} returned by the {@link CompletableFuture} will be structured. */ - public static CompletableFuture getAppInfoFutureWithAwsInfo(AsyncHttpClientHelper asyncHttpClientHelper) { + public static @NotNull CompletableFuture<@NotNull AppInfo> getAppInfoFutureWithAwsInfo( + @NotNull AsyncHttpClientHelper asyncHttpClientHelper + ) { String appId = AppInfoImpl.detectAppId(); if (appId == null) throw new IllegalStateException( @@ -187,8 +194,11 @@ public static CompletableFuture getAppInfoFutureWithAwsInfo(AsyncHttpCl * services will be used to determine {@link AppInfo#dataCenter()} and {@link AppInfo#instanceId()}. If those AWS * metadata calls fail for any reason then {@link AppInfo#UNKNOWN_VALUE} will be used instead. */ - public static CompletableFuture getAppInfoFutureWithAwsInfo(String appId, String environment, - AsyncHttpClientHelper asyncHttpClientHelper) { + public static @NotNull CompletableFuture<@NotNull AppInfo> getAppInfoFutureWithAwsInfo( + @NotNull String appId, + @NotNull String environment, + @NotNull AsyncHttpClientHelper asyncHttpClientHelper + ) { if ("local".equalsIgnoreCase(environment) || "compiletimetest".equalsIgnoreCase(environment)) { AppInfo localAppInfo = AppInfoImpl.createLocalInstance(appId); @@ -202,8 +212,8 @@ public static CompletableFuture getAppInfoFutureWithAwsInfo(String appI } // Not local, so assume AWS. - CompletableFuture dataCenterFuture = getAwsRegion(asyncHttpClientHelper); - CompletableFuture instanceIdFuture = getAwsInstanceId(asyncHttpClientHelper); + CompletableFuture<@NotNull String> dataCenterFuture = getAwsRegion(asyncHttpClientHelper); + CompletableFuture<@NotNull String> instanceIdFuture = getAwsInstanceId(asyncHttpClientHelper); return CompletableFuture.allOf(dataCenterFuture, instanceIdFuture).thenApply((aVoid) -> { diff --git a/riposte-async-http-client/src/test/java/com/nike/riposte/componenttest/VerifyAsyncHttpClientHelperComponentTest.java b/riposte-async-http-client/src/test/java/com/nike/riposte/componenttest/VerifyAsyncHttpClientHelperComponentTest.java index f38cd700..fb2667bc 100644 --- a/riposte-async-http-client/src/test/java/com/nike/riposte/componenttest/VerifyAsyncHttpClientHelperComponentTest.java +++ b/riposte-async-http-client/src/test/java/com/nike/riposte/componenttest/VerifyAsyncHttpClientHelperComponentTest.java @@ -23,6 +23,7 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -285,13 +286,15 @@ private static class TestEndpoint extends StandardEndpoint { new ApiErrorBase("MISSING_EXPECTED_HEADER", 42, "Missing expected header", 400); @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } @Override - public CompletableFuture> execute( - RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx ) { if (!EXPECTED_REQUEST_PAYLOAD.equals(request.getContent())) throw new ApiException(MISSING_EXPECTED_REQ_PAYLOAD); @@ -329,7 +332,7 @@ private AppServerConfigForTesting(Collection> endpoints, int port) { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } diff --git a/riposte-auth/build.gradle b/riposte-auth/build.gradle index 1add5fa4..c0d41ace 100644 --- a/riposte-auth/build.gradle +++ b/riposte-auth/build.gradle @@ -5,8 +5,11 @@ dependencies { project(":riposte-spi"), project(":riposte-core") ) - + compileOnly( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", + ) testCompile ( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", "org.assertj:assertj-core:$assertJVersion", "junit:junit:$junitVersion", "org.mockito:mockito-core:$mockitoVersion", diff --git a/riposte-auth/src/main/java/com/nike/riposte/server/error/validation/BasicAuthSecurityValidator.java b/riposte-auth/src/main/java/com/nike/riposte/server/error/validation/BasicAuthSecurityValidator.java index e80b9950..80386921 100644 --- a/riposte-auth/src/main/java/com/nike/riposte/server/error/validation/BasicAuthSecurityValidator.java +++ b/riposte-auth/src/main/java/com/nike/riposte/server/error/validation/BasicAuthSecurityValidator.java @@ -4,8 +4,11 @@ import com.nike.riposte.server.http.Endpoint; import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; + import java.util.Base64; import java.util.Collection; +import java.util.Collections; /** * A {@link RequestSecurityValidator} for validating that the incoming request for any of the given collection @@ -14,7 +17,7 @@ @SuppressWarnings("WeakerAccess") public class BasicAuthSecurityValidator implements RequestSecurityValidator { - protected final Collection> basicAuthValidatedEndpoints; + protected final @NotNull Collection> basicAuthValidatedEndpoints; protected final String expectedUsername; protected final String expectedPassword; @@ -28,13 +31,18 @@ public class BasicAuthSecurityValidator implements RequestSecurityValidator { public BasicAuthSecurityValidator(Collection> basicAuthValidatedEndpoints, String expectedUsername, String expectedPassword) { - this.basicAuthValidatedEndpoints = basicAuthValidatedEndpoints; + this.basicAuthValidatedEndpoints = (basicAuthValidatedEndpoints == null) + ? Collections.emptySet() + : basicAuthValidatedEndpoints; this.expectedUsername = expectedUsername; this.expectedPassword = expectedPassword; } @Override - public void validateSecureRequestForEndpoint(RequestInfo requestInfo, Endpoint endpoint) { + public void validateSecureRequestForEndpoint( + @NotNull RequestInfo requestInfo, + @NotNull Endpoint endpoint + ) { String authorizationHeader = requestInfo.getHeaders().get("Authorization"); if (authorizationHeader == null) { @@ -74,7 +82,7 @@ public void validateSecureRequestForEndpoint(RequestInfo requestInfo, Endpoin } @Override - public Collection> endpointsToValidate() { + public @NotNull Collection> endpointsToValidate() { return basicAuthValidatedEndpoints; } diff --git a/riposte-auth/src/main/java/com/nike/riposte/server/error/validation/PolymorphicSecurityValidator.java b/riposte-auth/src/main/java/com/nike/riposte/server/error/validation/PolymorphicSecurityValidator.java index 25c6d764..2a3ac1c3 100644 --- a/riposte-auth/src/main/java/com/nike/riposte/server/error/validation/PolymorphicSecurityValidator.java +++ b/riposte-auth/src/main/java/com/nike/riposte/server/error/validation/PolymorphicSecurityValidator.java @@ -5,6 +5,8 @@ import com.nike.riposte.server.http.Endpoint; import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -18,7 +20,7 @@ @SuppressWarnings("WeakerAccess") public class PolymorphicSecurityValidator implements RequestSecurityValidator { - protected Map, List> validationMap; + protected @NotNull Map, List> validationMap; protected final boolean isFastEnoughToRunOnNettyWorkerThread; /** @@ -34,8 +36,9 @@ public PolymorphicSecurityValidator(List validators) { isFastEnoughToRunOnNettyWorkerThread = !containsSlowValidator; } - protected Map, List> buildValidationMap( - List validators) { + protected @NotNull Map, List> buildValidationMap( + List validators + ) { Map, List> map = new HashMap<>(); if (validators == null) { return map; @@ -59,7 +62,10 @@ protected Map, List> buildValidationMap( * validators. */ @Override - public void validateSecureRequestForEndpoint(RequestInfo requestInfo, Endpoint endpoint) { + public void validateSecureRequestForEndpoint( + @NotNull RequestInfo requestInfo, + @NotNull Endpoint endpoint + ) { List validators = validationMap.get(endpoint); if (validators == null || validators.isEmpty()) { // if there are no validators for the endpoint, we don't need to validate @@ -83,7 +89,7 @@ public void validateSecureRequestForEndpoint(RequestInfo requestInfo, Endpoin } @Override - public Collection> endpointsToValidate() { + public @NotNull Collection> endpointsToValidate() { return validationMap.keySet(); } diff --git a/riposte-auth/src/test/java/com/nike/riposte/server/error/validation/BasicAuthSecurityValidatorTest.java b/riposte-auth/src/test/java/com/nike/riposte/server/error/validation/BasicAuthSecurityValidatorTest.java index cf0bc389..c5f5e788 100644 --- a/riposte-auth/src/test/java/com/nike/riposte/server/error/validation/BasicAuthSecurityValidatorTest.java +++ b/riposte-auth/src/test/java/com/nike/riposte/server/error/validation/BasicAuthSecurityValidatorTest.java @@ -5,6 +5,7 @@ import com.nike.riposte.server.http.RequestInfo; import org.apache.commons.codec.binary.Base64; +import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Test; @@ -14,7 +15,6 @@ import io.netty.handler.codec.http.HttpHeaders; import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -39,6 +39,18 @@ public void setup() { PASSWORD); } + @Test + public void constructor_uses_empty_collection_if_passed_null_endpoints_collection() { + // when + BasicAuthSecurityValidator instance = new BasicAuthSecurityValidator(null, USERNAME, PASSWORD); + + // then + Assertions.assertThat(instance.basicAuthValidatedEndpoints) + .isNotNull() + .isEmpty(); + Assertions.assertThat(instance.endpointsToValidate()).isSameAs(instance.basicAuthValidatedEndpoints); + } + @Test public void endpointsToValidateTest() { assertThat(underTest.endpointsToValidate().size(), is(3)); @@ -47,12 +59,6 @@ public void endpointsToValidateTest() { assertThat(underTest.endpointsToValidate().contains(mockEndpoint3), is(true)); } - @Test - public void endpointsToValidateNullTest() { - underTest = new BasicAuthSecurityValidator(null, USERNAME, PASSWORD); - assertThat(underTest.endpointsToValidate(), nullValue()); - } - @Test public void endpointsToValidateEmptyTest() { underTest = new BasicAuthSecurityValidator(Collections.emptyList(), USERNAME, PASSWORD); diff --git a/riposte-core/src/main/java/com/nike/riposte/server/Server.java b/riposte-core/src/main/java/com/nike/riposte/server/Server.java index b4237041..447ea50a 100644 --- a/riposte-core/src/main/java/com/nike/riposte/server/Server.java +++ b/riposte-core/src/main/java/com/nike/riposte/server/Server.java @@ -47,7 +47,7 @@ public class Server { private final Logger logger = LoggerFactory.getLogger(this.getClass()); - private final ServerConfig serverConfig; + private final @NotNull ServerConfig serverConfig; private final List eventLoopGroups = new ArrayList<>(); private final List channels = new ArrayList<>(); @@ -57,7 +57,7 @@ public class Server { @SuppressWarnings("WeakerAccess") public static final String SERVER_BOSS_CHANNEL_DEBUG_LOGGER_NAME = "ServerBossChannelDebugLogger"; - public Server(ServerConfig serverConfig) { + public Server(@NotNull ServerConfig serverConfig) { this.serverConfig = serverConfig; } @@ -155,8 +155,9 @@ public void startup() throws CertificateException, IOException, InterruptedExcep .childHandler(channelInitializer); // execute pre startup hooks - if (serverConfig.preServerStartupHooks() != null) { - for (PreServerStartupHook hook : serverConfig.preServerStartupHooks()) { + List<@NotNull PreServerStartupHook> preServerStartupHooks = serverConfig.preServerStartupHooks(); + if (preServerStartupHooks != null) { + for (PreServerStartupHook hook : preServerStartupHooks) { hook.executePreServerStartupHook(b); } } @@ -170,8 +171,9 @@ public void startup() throws CertificateException, IOException, InterruptedExcep .channel(); // execute post startup hooks - if (serverConfig.postServerStartupHooks() != null) { - for (PostServerStartupHook hook : serverConfig.postServerStartupHooks()) { + List<@NotNull PostServerStartupHook> postServerStartupHooks = serverConfig.postServerStartupHooks(); + if (postServerStartupHooks != null) { + for (PostServerStartupHook hook : postServerStartupHooks) { hook.executePostServerStartupHook(serverConfig, ch); } } @@ -224,8 +226,9 @@ public synchronized void shutdown() throws InterruptedException { List channelCloseFutures = new ArrayList<>(); for (Channel ch : channels) { // execute shutdown hooks - if (serverConfig.serverShutdownHooks() != null) { - for (ServerShutdownHook hook : serverConfig.serverShutdownHooks()) { + List<@NotNull ServerShutdownHook> serverShutdownHooks = serverConfig.serverShutdownHooks(); + if (serverShutdownHooks != null) { + for (ServerShutdownHook hook : serverShutdownHooks) { hook.executeServerShutdownHook(serverConfig, ch); } } diff --git a/riposte-core/src/main/java/com/nike/riposte/server/handler/ExceptionHandlingHandler.java b/riposte-core/src/main/java/com/nike/riposte/server/handler/ExceptionHandlingHandler.java index 7d57a331..9cdfe445 100644 --- a/riposte-core/src/main/java/com/nike/riposte/server/handler/ExceptionHandlingHandler.java +++ b/riposte-core/src/main/java/com/nike/riposte/server/handler/ExceptionHandlingHandler.java @@ -85,10 +85,13 @@ public ExceptionHandlingHandler( } @Override - public PipelineContinuationBehavior doExceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + public PipelineContinuationBehavior doExceptionCaught(ChannelHandlerContext ctx, @NotNull Throwable cause) { // We expect to end up here when handlers previously in the pipeline throw an error, so do the normal // processError call. HttpProcessingState state = getStateAndCreateIfNeeded(ctx, cause); + // Ensure that a RequestInfo is set on the state, no matter what. + getRequestInfo(state, null); + if (state.isResponseSendingStarted()) { String infoMessage = "A response has already been started. Ignoring this exception since it's secondary. NOTE: This often " @@ -132,6 +135,9 @@ public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Obj // We expect to be here for normal message processing, but only as a pass-through. If the state indicates that // the request was not handled then that's a pipeline misconfiguration and we need to throw an error. HttpProcessingState state = getStateAndCreateIfNeeded(ctx, null); + // Ensure that a RequestInfo is set on the state, no matter what. + getRequestInfo(state, msg); + if (!state.isRequestHandled()) { runnableWithTracingAndMdc(() -> { String errorMsg = "In ExceptionHandlingHandler's channelRead method, but the request has not yet been " @@ -180,7 +186,7 @@ protected boolean argsAreEligibleForLinkingAndUnlinkingDistributedTracingInfo( return (methodToExecute == DO_EXCEPTION_CAUGHT); } - protected HttpProcessingState getStateAndCreateIfNeeded(ChannelHandlerContext ctx, Throwable cause) { + protected @NotNull HttpProcessingState getStateAndCreateIfNeeded(ChannelHandlerContext ctx, Throwable cause) { HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get(); if (state == null) { // The error must have occurred before RequestStateCleanerHandler could even execute. Create a new state and @@ -201,8 +207,10 @@ protected HttpProcessingState getStateAndCreateIfNeeded(ChannelHandlerContext ct * will try to get it from the given state. If that fails, it will try to create a new one based on the given msg * (which only works if the msg is a {@link HttpRequest}). If that also fails then a new dummy instance for an * unknown request will be created via {@link RequestInfoImpl#dummyInstanceForUnknownRequests()} and returned. + * This will never return null, and the given {@link HttpProcessingState#getRequestInfo()} will always be non-null + * by the time this method returns. */ - RequestInfo getRequestInfo(HttpProcessingState state, Object msg) { + @NotNull RequestInfo getRequestInfo(@NotNull HttpProcessingState state, Object msg) { // Try to get the RequestInfo from the state variable first. RequestInfo requestInfo = state.getRequestInfo(); @@ -239,9 +247,11 @@ RequestInfo getRequestInfo(HttpProcessingState state, Object msg) { * happen" variety you can (and should) directly call {@link #processUnhandledError(HttpProcessingState, Object, * Throwable)} instead. */ - protected ResponseInfo processError(HttpProcessingState state, - Object msg, - Throwable cause) { + protected @NotNull ResponseInfo processError( + @NotNull HttpProcessingState state, + Object msg, + @NotNull Throwable cause + ) { RequestInfo requestInfo = getRequestInfo(state, msg); try { @@ -270,9 +280,11 @@ protected ResponseInfo processError(HttpProcessingState state * Object, Throwable)} instead in order to get an error response that is better tailored to the given error rather * than this one which guarantees a somewhat unhelpful generic error response. */ - ResponseInfo processUnhandledError(HttpProcessingState state, - Object msg, - Throwable cause) { + @NotNull ResponseInfo processUnhandledError( + @NotNull HttpProcessingState state, + Object msg, + @NotNull Throwable cause + ) { RequestInfo requestInfo = getRequestInfo(state, msg); // Run the error through the riposteUnhandledErrorHandler @@ -283,8 +295,10 @@ ResponseInfo processUnhandledError(HttpProcessingState state, return responseInfo; } - protected void setupResponseInfoBasedOnErrorResponseInfo(ResponseInfo responseInfo, - ErrorResponseInfo errorInfo) { + protected void setupResponseInfoBasedOnErrorResponseInfo( + @NotNull ResponseInfo responseInfo, + @NotNull ErrorResponseInfo errorInfo + ) { responseInfo.setContentForFullResponse(errorInfo.getErrorResponseBody()); responseInfo.setHttpStatusCode(errorInfo.getErrorHttpStatusCode()); Map> extraHeaders = errorInfo.getExtraHeadersToAddToResponse(); diff --git a/riposte-core/src/main/java/com/nike/riposte/server/handler/NonblockingEndpointExecutionHandler.java b/riposte-core/src/main/java/com/nike/riposte/server/handler/NonblockingEndpointExecutionHandler.java index 3eb5169e..9e50f376 100644 --- a/riposte-core/src/main/java/com/nike/riposte/server/handler/NonblockingEndpointExecutionHandler.java +++ b/riposte-core/src/main/java/com/nike/riposte/server/handler/NonblockingEndpointExecutionHandler.java @@ -51,7 +51,7 @@ public class NonblockingEndpointExecutionHandler extends BaseInboundHandlerWithTracingAndMdcSupport { private final Logger logger = LoggerFactory.getLogger(this.getClass()); - private final Executor longRunningTaskExecutor; + private final @NotNull Executor longRunningTaskExecutor; private final long defaultCompletableFutureTimeoutMillis; private final @NotNull ServerSpanNamingAndTaggingStrategy spanTaggingStrategy; @@ -135,9 +135,10 @@ public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Obj // Also schedule a timeout check with our Netty event loop to make sure we kill the // CompletableFuture if it goes on too long. - long timeoutValueToUse = (nonblockingEndpoint.completableFutureTimeoutOverrideMillis() == null) + Long endpointTimeoutOverride = nonblockingEndpoint.completableFutureTimeoutOverrideMillis(); + long timeoutValueToUse = (endpointTimeoutOverride == null) ? defaultCompletableFutureTimeoutMillis - : nonblockingEndpoint.completableFutureTimeoutOverrideMillis(); + : endpointTimeoutOverride; ScheduledFuture responseTimeoutScheduledFuture = ctx.channel().eventLoop().schedule(() -> { if (!responseFuture.isDone()) { runnableWithTracingAndMdc( @@ -219,10 +220,10 @@ public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Obj * {@link ServerSpanNamingAndTaggingStrategy#shouldAddEndpointStartAnnotation()}). */ protected Function>> doExecuteEndpointFunction( - RequestInfo requestInfo, - NonblockingEndpoint nonblockingEndpoint, - Span endpointExecutionSpan, - ChannelHandlerContext ctx + @NotNull RequestInfo requestInfo, + @NotNull NonblockingEndpoint nonblockingEndpoint, + @Nullable Span endpointExecutionSpan, + @NotNull ChannelHandlerContext ctx ) { return functionWithTracingAndMdc( aVoid -> { @@ -242,9 +243,16 @@ protected Function>> doExecuteEndpointFu // Kick off the endpoint execution. //noinspection unchecked - return (CompletableFuture>) nonblockingEndpoint.execute( + CompletableFuture> executionResult = nonblockingEndpoint.execute( requestInfo, longRunningTaskExecutor, ctx ); + + //noinspection ConstantConditions + if (executionResult == null) { + throw new NullPointerException("NonblockingEndpoint.execute() cannot return null."); + } + + return executionResult; }, ctx ); diff --git a/riposte-core/src/main/java/com/nike/riposte/server/handler/RequestContentValidationHandler.java b/riposte-core/src/main/java/com/nike/riposte/server/handler/RequestContentValidationHandler.java index 20dafde2..66d79b76 100644 --- a/riposte-core/src/main/java/com/nike/riposte/server/handler/RequestContentValidationHandler.java +++ b/riposte-core/src/main/java/com/nike/riposte/server/handler/RequestContentValidationHandler.java @@ -9,6 +9,8 @@ import com.nike.riposte.server.http.HttpProcessingState; import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; + import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -100,7 +102,11 @@ protected boolean argsAreEligibleForLinkingAndUnlinkingDistributedTracingInfo( } @SuppressWarnings("WeakerAccess") - protected void executeValidation(RequestInfo requestInfo, Endpoint endpoint, ChannelHandlerContext ctx) { + protected void executeValidation( + @NotNull RequestInfo requestInfo, + @NotNull Endpoint endpoint, + ChannelHandlerContext ctx + ) { runnableWithTracingAndMdc(() -> { // NOTE: The requestInfo.getContent() call should be here in this method because this is the first time // getContent() is called and will deserialize the payload here. For very large payloads it may take a diff --git a/riposte-core/src/main/java/com/nike/riposte/server/handler/RequestInfoSetterHandler.java b/riposte-core/src/main/java/com/nike/riposte/server/handler/RequestInfoSetterHandler.java index 1cf98b0f..1890d3e0 100644 --- a/riposte-core/src/main/java/com/nike/riposte/server/handler/RequestInfoSetterHandler.java +++ b/riposte-core/src/main/java/com/nike/riposte/server/handler/RequestInfoSetterHandler.java @@ -34,6 +34,7 @@ * * @author Nic Munroe */ +@SuppressWarnings("WeakerAccess") public class RequestInfoSetterHandler extends BaseInboundHandlerWithTracingAndMdcSupport { private static final Logger logger = LoggerFactory.getLogger(RequestInfoSetterHandler.class); diff --git a/riposte-core/src/main/java/com/nike/riposte/server/handler/ResponseFilterHandler.java b/riposte-core/src/main/java/com/nike/riposte/server/handler/ResponseFilterHandler.java index 889971e4..0de31919 100644 --- a/riposte-core/src/main/java/com/nike/riposte/server/handler/ResponseFilterHandler.java +++ b/riposte-core/src/main/java/com/nike/riposte/server/handler/ResponseFilterHandler.java @@ -76,7 +76,9 @@ protected void executeResponseFilters(ChannelHandlerContext ctx) { if (state.isResponseSendingStarted()) return; - // RequestHasBeenHandledVerificationHandler should have made sure that state.getResponseInfo() is not null. + // RequestHasBeenHandledVerificationHandler should have made sure that state.getResponseInfo() is not null, + // and ExceptionHandlingHandler should have made sure that state.getRequestInfo() is not null + // (even if no exception has occurred). ResponseInfo currentResponseInfo = state.getResponseInfo(); for (RequestAndResponseFilter filter : filtersInResponseProcessingOrder) { try { diff --git a/riposte-core/src/main/java/com/nike/riposte/server/handler/SecurityValidationHandler.java b/riposte-core/src/main/java/com/nike/riposte/server/handler/SecurityValidationHandler.java index 783454c2..05bc2569 100644 --- a/riposte-core/src/main/java/com/nike/riposte/server/handler/SecurityValidationHandler.java +++ b/riposte-core/src/main/java/com/nike/riposte/server/handler/SecurityValidationHandler.java @@ -59,7 +59,7 @@ public SecurityValidationHandler(RequestSecurityValidator securityValidator) { } @Override - public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) { if (shouldHandleDoChannelReadMessage(msg)) { HttpProcessingState httpProcessingState = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get(); if (httpProcessingState != null) { diff --git a/riposte-core/src/main/java/com/nike/riposte/server/http/ProxyRouterEndpoint.java b/riposte-core/src/main/java/com/nike/riposte/server/http/ProxyRouterEndpoint.java index 070e471d..816fdc03 100644 --- a/riposte-core/src/main/java/com/nike/riposte/server/http/ProxyRouterEndpoint.java +++ b/riposte-core/src/main/java/com/nike/riposte/server/http/ProxyRouterEndpoint.java @@ -4,6 +4,9 @@ import com.fasterxml.jackson.core.type.TypeReference; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -32,8 +35,11 @@ public abstract class ProxyRouterEndpoint implements Endpoint { * endpoint it may not know what downstream system to connect to or what to send until an unspecified amount of time * has passed (e.g. DNS or Eureka lookups), and we can't allow this method to block. */ - public abstract CompletableFuture getDownstreamRequestFirstChunkInfo( - RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx); + public abstract @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ); /** * This method allows for the inspection and optional alteration of the first chunk of the downstream system's @@ -67,9 +73,10 @@ public abstract CompletableFuture getDownstream * it into this method you can pass it via the {@link RequestInfo#getRequestAttributes()} map and it will be * available in {@code origRequestInfo} here when this method is called. */ - @SuppressWarnings("UnusedParameters") - public void handleDownstreamResponseFirstChunk(HttpResponse downstreamResponseFirstChunk, - RequestInfo origRequestInfo) { + public void handleDownstreamResponseFirstChunk( + @NotNull HttpResponse downstreamResponseFirstChunk, + @NotNull RequestInfo origRequestInfo + ) { // Do nothing by default } @@ -77,7 +84,7 @@ public void handleDownstreamResponseFirstChunk(HttpResponse downstreamResponseFi * Proxy router endpoints don't generally do anything with content, so return null by default. */ @Override - public TypeReference requestContentType() { + public @Nullable TypeReference requestContentType() { return null; } @@ -85,7 +92,7 @@ public TypeReference requestContentType() { * Proxy router endpoints don't generally validate content, so return false by default. */ @Override - public boolean isValidateRequestContent(@SuppressWarnings("UnusedParameters") RequestInfo request) { + public boolean isValidateRequestContent(@NotNull RequestInfo request) { return false; } @@ -104,8 +111,12 @@ public boolean isRequireRequestContent() { * passed through without modification. */ @SuppressWarnings("UnusedParameters") - protected HttpRequest generateSimplePassthroughRequest(RequestInfo incomingRequest, String downstreamPath, - HttpMethod downstreamMethod, ChannelHandlerContext ctx) { + protected @NotNull HttpRequest generateSimplePassthroughRequest( + @NotNull RequestInfo incomingRequest, + @NotNull String downstreamPath, + @NotNull HttpMethod downstreamMethod, + @NotNull ChannelHandlerContext ctx + ) { String queryString = extractQueryString(incomingRequest.getUri()); String downstreamUri = downstreamPath; // TODO: Add logic to support when downstreamPath already has a query string on it. The two query strings should be combined @@ -131,7 +142,7 @@ protected HttpRequest generateSimplePassthroughRequest(RequestInfo incomingRe * e.g. Passing in {@code "/some/path?"} will result in this method returning {@code "?"}. */ @SuppressWarnings("WeakerAccess") - protected String extractQueryString(String uri) { + protected @Nullable String extractQueryString(@NotNull String uri) { int questionMarkIndex = uri.indexOf('?'); if (questionMarkIndex == -1) return null; @@ -148,7 +159,7 @@ public static class DownstreamRequestFirstChunkInfo { /** * The host to call for the downstream request. */ - public final String host; + public final @NotNull String host; /** * The port to use for the downstream request. */ @@ -161,7 +172,7 @@ public static class DownstreamRequestFirstChunkInfo { * The first chunk info for the downstream request - contains uri/path, HTTP method, headers, and HTTP protocol * that should be used. */ - public final HttpRequest firstChunk; + public final @NotNull HttpRequest firstChunk; /** * An Optional containing a custom circuit breaker if a custom one should be used, or empty if the router * handler should use a default circuit breaker. If you don't want *any* circuit breaker to be used, set {@link @@ -169,7 +180,7 @@ public static class DownstreamRequestFirstChunkInfo { * all calls to the same host will use the same circuit breaker). If you need something more (or less) fine * grained than that then you'll need to provide a custom circuit breaker. */ - public final Optional> customCircuitBreaker; + public final @NotNull Optional> customCircuitBreaker; /** * Set this to true if you don't want *any* circuit breaker to be used - if this is false then {@link * #customCircuitBreaker} will be used to determine which circuit breaker to use (custom vs. default). @@ -204,7 +215,9 @@ public static class DownstreamRequestFirstChunkInfo { * @param firstChunk * the value for {@link #firstChunk} */ - public DownstreamRequestFirstChunkInfo(String host, int port, boolean isHttps, HttpRequest firstChunk) { + public DownstreamRequestFirstChunkInfo( + @NotNull String host, int port, boolean isHttps, @NotNull HttpRequest firstChunk + ) { this(host, port, isHttps, firstChunk, Optional.empty(), false); } @@ -224,13 +237,28 @@ public DownstreamRequestFirstChunkInfo(String host, int port, boolean isHttps, H * @param disableCircuitBreaker * the value for {@link #disableCircuitBreaker} */ - public DownstreamRequestFirstChunkInfo(String host, int port, boolean isHttps, HttpRequest firstChunk, - Optional> customCircuitBreaker, - boolean disableCircuitBreaker) { + @SuppressWarnings("ConstantConditions") + public DownstreamRequestFirstChunkInfo( + @NotNull String host, + int port, + boolean isHttps, + @NotNull HttpRequest firstChunk, + @NotNull Optional> customCircuitBreaker, + boolean disableCircuitBreaker + ) { + if (host == null) { + throw new IllegalArgumentException("host cannot be null."); + } + + if (firstChunk == null) { + throw new IllegalArgumentException("firstChunk cannot be null."); + } + this.host = host; this.port = port; this.isHttps = isHttps; this.firstChunk = firstChunk; + //noinspection OptionalAssignedToNull this.customCircuitBreaker = (customCircuitBreaker == null) ? Optional.empty() : customCircuitBreaker; this.disableCircuitBreaker = disableCircuitBreaker; } @@ -242,7 +270,7 @@ public DownstreamRequestFirstChunkInfo(String host, int port, boolean isHttps, H * WARNING: Setting this to true opens you up to security vulnerabilities - make sure you know what you're * doing. */ - public DownstreamRequestFirstChunkInfo withRelaxedHttpsValidation(boolean relaxedHttpsValidation) { + public @NotNull DownstreamRequestFirstChunkInfo withRelaxedHttpsValidation(boolean relaxedHttpsValidation) { this.relaxedHttpsValidation = relaxedHttpsValidation; return this; } @@ -250,7 +278,9 @@ public DownstreamRequestFirstChunkInfo withRelaxedHttpsValidation(boolean relaxe /** * Pass in false if you do not want SubSpans created around your downstream call */ - public DownstreamRequestFirstChunkInfo withPerformSubSpanAroundDownstreamCall(boolean performSubSpanAroundDownstreamCall) { + public @NotNull DownstreamRequestFirstChunkInfo withPerformSubSpanAroundDownstreamCall( + boolean performSubSpanAroundDownstreamCall + ) { this.performSubSpanAroundDownstreamCall = performSubSpanAroundDownstreamCall; return this; } @@ -258,7 +288,9 @@ public DownstreamRequestFirstChunkInfo withPerformSubSpanAroundDownstreamCall(bo /** * Pass in false if you do not want the standard tracing headers added to your downstream call */ - public DownstreamRequestFirstChunkInfo withAddTracingHeadersToDownstreamCall(boolean addTracingHeadersToDownstreamCall) { + public @NotNull DownstreamRequestFirstChunkInfo withAddTracingHeadersToDownstreamCall( + boolean addTracingHeadersToDownstreamCall + ) { this.addTracingHeadersToDownstreamCall = addTracingHeadersToDownstreamCall; return this; } @@ -268,7 +300,7 @@ public DownstreamRequestFirstChunkInfo withAddTracingHeadersToDownstreamCall(boo * Proxy router endpoints don't generally want to limit the request size they are proxying, so return 0 to disable */ @Override - public Integer maxRequestSizeInBytesOverride() { + public @Nullable Integer maxRequestSizeInBytesOverride() { return 0; } @@ -278,7 +310,7 @@ public Integer maxRequestSizeInBytesOverride() { * payload as it passes through. */ @Override - public boolean isDecompressRequestPayloadAllowed(RequestInfo request) { + public boolean isDecompressRequestPayloadAllowed(@NotNull RequestInfo request) { return false; } } diff --git a/riposte-core/src/main/java/com/nike/riposte/server/http/ResponseSender.java b/riposte-core/src/main/java/com/nike/riposte/server/http/ResponseSender.java index 5c28f32b..f0f7438b 100644 --- a/riposte-core/src/main/java/com/nike/riposte/server/http/ResponseSender.java +++ b/riposte-core/src/main/java/com/nike/riposte/server/http/ResponseSender.java @@ -800,12 +800,31 @@ public void sendErrorResponse(ChannelHandlerContext ctx, + "sendErrorResponse(...) only works with full responses"); } - responseInfo.getHeaders().set("error_uid", responseInfo.getContentForFullResponse().errorId()); + String errorIdValue = responseInfo.getContentForFullResponse().errorId(); + //noinspection ConstantConditions + if (errorIdValue == null) { + errorIdValue = UUID.randomUUID().toString(); + logger.warn( + "ErrorResponseBody.errorId() returned null - your service's error handler should never do this. " + + "Synthetic error ID created and sent back in response: {}", errorIdValue + ); + } + responseInfo.getHeaders().set("error_uid", errorIdValue); @SuppressWarnings("UnnecessaryLocalVariable") ErrorResponseBody bodyToSerialize = responseInfo.getContentForFullResponse(); if (bodyToSerialize != null) { - String errorBodyAsString = errorResponseBodySerializer.serializeErrorResponseBodyToString(bodyToSerialize); + String errorBodyAsString = null; + try { + errorBodyAsString = errorResponseBodySerializer.serializeErrorResponseBodyToString(bodyToSerialize); + } + catch (Exception ex) { + logger.error( + "An unexpected exception occurred while attempting to serialize an ErrorResponseBody - please fix " + + "your service's error handler. An empty response body will be used instead. error_id={}", + errorIdValue, ex + ); + } //noinspection unchecked ((ResponseInfo) responseInfo).setContentForFullResponse(errorBodyAsString); } diff --git a/riposte-core/src/main/java/com/nike/riposte/server/http/StandardEndpoint.java b/riposte-core/src/main/java/com/nike/riposte/server/http/StandardEndpoint.java index 06099267..b3f3827b 100644 --- a/riposte-core/src/main/java/com/nike/riposte/server/http/StandardEndpoint.java +++ b/riposte-core/src/main/java/com/nike/riposte/server/http/StandardEndpoint.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.type.TypeReference; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,8 +28,8 @@ public abstract class StandardEndpoint implements NonblockingEndpoint inferredTypeReference; + protected final @Nullable Type inputType; + protected final @Nullable TypeReference inferredTypeReference; /** * Uses some magic to determine the {@link #inputType} of the {@code I} generic type for this class (the {@code I} @@ -66,7 +67,6 @@ else if (inputType instanceof TypeVariable) { throw ex; } - //noinspection EqualsBetweenInconvertibleTypes this.inferredTypeReference = (inputType == null || Void.class.equals(inputType)) ? null : new TypeReference() { @@ -88,7 +88,7 @@ public Type getType() { * used. */ @Override - public TypeReference requestContentType() { + public @Nullable TypeReference requestContentType() { return inferredTypeReference; } } diff --git a/riposte-core/src/main/java/com/nike/riposte/server/http/impl/SimpleProxyRouterEndpoint.java b/riposte-core/src/main/java/com/nike/riposte/server/http/impl/SimpleProxyRouterEndpoint.java index 48a4e552..78720e1f 100644 --- a/riposte-core/src/main/java/com/nike/riposte/server/http/impl/SimpleProxyRouterEndpoint.java +++ b/riposte-core/src/main/java/com/nike/riposte/server/http/impl/SimpleProxyRouterEndpoint.java @@ -6,11 +6,14 @@ import com.nike.riposte.util.HttpUtils; import com.nike.riposte.util.Matcher; +import org.jetbrains.annotations.NotNull; + import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponse; /** @@ -20,16 +23,16 @@ * * @author Nic Munroe */ -@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +@SuppressWarnings({"OptionalUsedAsFieldOrParameterType", "WeakerAccess"}) public class SimpleProxyRouterEndpoint extends ProxyRouterEndpoint { - private final Matcher incomingRequestMatcher; - private final String downstreamDestinationHost; - private final int downstreamDestinationPort; - private final String downstreamDestinationUriPath; - private final boolean isDownstreamCallHttps; - private final Optional> customCircuitBreaker; - private final boolean disableCircuitBreaker; + protected final @NotNull Matcher incomingRequestMatcher; + protected final @NotNull String downstreamDestinationHost; + protected final int downstreamDestinationPort; + protected final @NotNull String downstreamDestinationUriPath; + protected final boolean isDownstreamCallHttps; + protected final @NotNull Optional> customCircuitBreaker; + protected final boolean disableCircuitBreaker; /** * Creates a new instance with the given arguments. @@ -55,13 +58,31 @@ public class SimpleProxyRouterEndpoint extends ProxyRouterEndpoint { * Set this to true if you don't want *any* circuit breaker to be used - if this is false then {@link * #customCircuitBreaker} will be used to determine which circuit breaker to use (custom vs. default). */ - public SimpleProxyRouterEndpoint(Matcher incomingRequestMatcher, - String downstreamDestinationHost, + @SuppressWarnings("ConstantConditions") + public SimpleProxyRouterEndpoint(@NotNull Matcher incomingRequestMatcher, + @NotNull String downstreamDestinationHost, int downstreamDestinationPort, - String downstreamDestinationUriPath, + @NotNull String downstreamDestinationUriPath, boolean isDownstreamCallHttps, - Optional> customCircuitBreaker, + @NotNull Optional> customCircuitBreaker, boolean disableCircuitBreaker) { + if (incomingRequestMatcher == null) { + throw new IllegalArgumentException("incomingRequestMatcher cannot be null."); + } + + if (downstreamDestinationHost == null) { + throw new IllegalArgumentException("downstreamDestinationHost cannot be null."); + } + + if (downstreamDestinationUriPath == null) { + throw new IllegalArgumentException("downstreamDestinationUriPath cannot be null."); + } + + //noinspection OptionalAssignedToNull + if (customCircuitBreaker == null) { + customCircuitBreaker = Optional.empty(); + } + this.incomingRequestMatcher = incomingRequestMatcher; this.downstreamDestinationHost = downstreamDestinationHost; this.downstreamDestinationPort = downstreamDestinationPort; @@ -98,14 +119,25 @@ public SimpleProxyRouterEndpoint(Matcher incomingRequestMatcher, } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return incomingRequestMatcher; } @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo( - RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, @NotNull Executor longRunningTaskExecutor, @NotNull ChannelHandlerContext ctx ) { + HttpMethod method = request.getMethod(); + if (method == null) { + CompletableFuture errorResult = new CompletableFuture<>(); + errorResult.completeExceptionally( + new IllegalArgumentException( + "Received a request with null request.getMethod(). This should never happen." + ) + ); + return errorResult; + } + return CompletableFuture.completedFuture( new DownstreamRequestFirstChunkInfo( downstreamDestinationHost, @@ -113,7 +145,7 @@ public CompletableFuture getDownstreamRequestFi isDownstreamCallHttps, generateSimplePassthroughRequest( request, HttpUtils.replaceUriPathVariables(request, downstreamDestinationUriPath), - request.getMethod(), ctx + method, ctx ), customCircuitBreaker, disableCircuitBreaker diff --git a/riposte-core/src/test/java/com/nike/riposte/server/channelpipeline/HttpChannelInitializerTest.java b/riposte-core/src/test/java/com/nike/riposte/server/channelpipeline/HttpChannelInitializerTest.java index 795de51c..2af9a15e 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/channelpipeline/HttpChannelInitializerTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/channelpipeline/HttpChannelInitializerTest.java @@ -46,6 +46,7 @@ import com.tngtech.java.junit.dataprovider.DataProviderRunner; import org.assertj.core.api.Assertions; +import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -118,7 +119,7 @@ private T extractField(Object obj, String fieldName) { private Endpoint getMockEndpoint(String path, HttpMethod... matchingMethods) { return new Endpoint() { @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { if (matchingMethods == null || matchingMethods.length == 0) return Matcher.match(path); else diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/ServerAsynchronousProcessingComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/ServerAsynchronousProcessingComponentTest.java index e855036a..cd7a188b 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/ServerAsynchronousProcessingComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/ServerAsynchronousProcessingComponentTest.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -186,7 +187,7 @@ public int numWorkerThreads() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -215,7 +216,11 @@ public static class TestNonblockingEndpoint extends StandardEndpoint>> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture>> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { String nettyWorkerThreadName = Thread.currentThread().getName(); nettyWorkerThreadsUsed.add(nettyWorkerThreadName); CompletableFuture>> result = CompletableFuture.supplyAsync(() -> doEndpointWork(nettyWorkerThreadName), executor); @@ -223,7 +228,7 @@ public CompletableFuture>> execute(RequestInfo> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( ResponseInfo.newBuilder(RESPONSE_PAYLOAD) .withHeaders(generateDefaultResponseHeaders(request)) @@ -298,7 +303,7 @@ public CompletableFuture> execute(RequestInfo request } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.POST); } @@ -321,7 +326,11 @@ private static class BasicEndpointWithDecompressionDisabled extends StandardEndp public static final String RESPONSE_PAYLOAD = "basic-endpoint-decompression-disabled-" + UUID.randomUUID().toString(); @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( ResponseInfo.newBuilder(RESPONSE_PAYLOAD) .withHeaders(generateDefaultResponseHeaders(request)) @@ -330,12 +339,12 @@ public CompletableFuture> execute(RequestInfo request } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.POST); } @Override - public boolean isDecompressRequestPayloadAllowed(RequestInfo request) { + public boolean isDecompressRequestPayloadAllowed(@NotNull RequestInfo request) { return false; } } @@ -347,8 +356,10 @@ protected static class DeserializationEndpointWithDecompressionEnabled extends S public static final String SOME_OBJ_FIELD_VALUE_HEADER_KEY = "some-obj-field-value"; @Override - public CompletableFuture> execute( - RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx ) { return CompletableFuture.completedFuture( ResponseInfo.newBuilder(RESPONSE_PAYLOAD) @@ -361,7 +372,7 @@ public CompletableFuture> execute( } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.POST); } @@ -398,7 +409,7 @@ public DecompressingRouterEndpointToDecompressionDisabledBackend(int port) { } @Override - public boolean isDecompressRequestPayloadAllowed(RequestInfo request) { + public boolean isDecompressRequestPayloadAllowed(@NotNull RequestInfo request) { return true; } } @@ -432,7 +443,7 @@ public PayloadDecompressionValidationConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyBeforeAndAfterSecurityRequestAndResponseFiltersComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyBeforeAndAfterSecurityRequestAndResponseFiltersComponentTest.java index fcf4e591..c645d603 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyBeforeAndAfterSecurityRequestAndResponseFiltersComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyBeforeAndAfterSecurityRequestAndResponseFiltersComponentTest.java @@ -14,6 +14,8 @@ import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -159,12 +161,12 @@ public RequestAndResponseFilterTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return appEndpoints; } @Override - public List requestAndResponseFilters() { + public @Nullable List<@NotNull RequestAndResponseFilter> requestAndResponseFilters() { return filters; } @@ -174,7 +176,7 @@ public int endpointsPort() { } @Override - public RequestSecurityValidator requestSecurityValidator() { + public @Nullable RequestSecurityValidator requestSecurityValidator() { return new TestRequestSecurityValidator(appEndpoints); } } @@ -191,7 +193,10 @@ public TestRequestSecurityValidator(Collection> endpointsToValidate) } @Override - public void validateSecureRequestForEndpoint(RequestInfo requestInfo, Endpoint endpoint) { + public void validateSecureRequestForEndpoint( + @NotNull RequestInfo requestInfo, + @NotNull Endpoint endpoint + ) { requestInfo.addRequestAttribute(SECURITY_VALIDATOR_EXECUTED_HEADER_KEY, true); if ("true".equals(requestInfo.getHeaders().get(FORCE_SECURITY_ERROR_HEADER_KEY))) { @@ -203,7 +208,7 @@ public void validateSecureRequestForEndpoint(RequestInfo requestInfo, Endpoin } @Override - public Collection> endpointsToValidate() { + public @NotNull Collection> endpointsToValidate() { return endpointsToValidate; } } @@ -213,12 +218,16 @@ private static class BasicEndpoint extends StandardEndpoint { public static final String MATCHING_PATH = "/basicEndpoint"; @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture(ResponseInfo.newBuilder().build()); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } @@ -262,19 +271,27 @@ public boolean shouldExecuteBeforeSecurityValidation() { } @Override - public RequestInfo filterRequestFirstChunkNoPayload(RequestInfo currentRequestInfo, ChannelHandlerContext ctx) { + public @Nullable RequestInfo filterRequestFirstChunkNoPayload( + @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx + ) { currentRequestInfo.getRequestAttributes().put(firstChunkReqMethodExecutedKey, true); return currentRequestInfo; } @Override - public RequestInfo filterRequestLastChunkWithFullPayload(RequestInfo currentRequestInfo, ChannelHandlerContext ctx) { + public @Nullable RequestInfo filterRequestLastChunkWithFullPayload( + @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx + ) { currentRequestInfo.getRequestAttributes().put(lastChunkReqMethodExecutedKey, true); return currentRequestInfo; } @Override - public ResponseInfo filterResponse(ResponseInfo responseInfo, RequestInfo requestInfo, ChannelHandlerContext ctx) { + public @Nullable ResponseInfo filterResponse( + @NotNull ResponseInfo responseInfo, + @NotNull RequestInfo requestInfo, + @NotNull ChannelHandlerContext ctx + ) { // Indicate whether or not the first/last chunk request methods were executed for this filter // so that the caller can assert based on what it expects. setHeader(responseInfo, diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyCornerCasesComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyCornerCasesComponentTest.java index 209e9fbf..9ca9c881 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyCornerCasesComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyCornerCasesComponentTest.java @@ -13,6 +13,7 @@ import com.nike.riposte.server.testutils.ComponentTestUtils.NettyHttpClientResponse; import com.nike.riposte.util.Matcher; +import org.jetbrains.annotations.NotNull; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -134,14 +135,16 @@ public static class BasicEndpoint extends StandardEndpoint { public static final String RESPONSE_PAYLOAD = "basic-endpoint-" + UUID.randomUUID().toString(); @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture(ResponseInfo.newBuilder(RESPONSE_PAYLOAD).build()); } } @@ -161,7 +164,7 @@ public DownstreamServerTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyDecoderFailedResultIsHandledTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyDecoderFailedResultIsHandledTest.java index 5137dd5c..2350aecc 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyDecoderFailedResultIsHandledTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyDecoderFailedResultIsHandledTest.java @@ -21,6 +21,8 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -303,7 +305,11 @@ private static class BasicEndpoint extends StandardEndpoint { public static final String RESPONSE_PAYLOAD = "basic-endpoint-" + UUID.randomUUID().toString(); @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { //need to do some work in a future to force the DecoderException to bubble up and return a 400 return CompletableFuture.supplyAsync(supplierWithTracingAndMdc(() -> { try { @@ -315,7 +321,7 @@ public CompletableFuture> execute(RequestInfo request } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -326,7 +332,11 @@ private static class DownstreamEndpoint extends StandardEndpoint { public static final String RESPONSE_PAYLOAD = "downstream-endpoint-" + UUID.randomUUID().toString(); @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { //need to do some work in a future to force the DecoderException to bubble up and return a 400 return CompletableFuture.supplyAsync(supplierWithTracingAndMdc(() -> { try { @@ -338,7 +348,7 @@ public CompletableFuture> execute(RequestInfo request } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -354,9 +364,11 @@ public RouterEndpoint(int downstreamPort) { } @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( new DownstreamRequestFirstChunkInfo( "127.0.0.1", downstreamPort, false, @@ -366,7 +378,7 @@ public CompletableFuture getDownstreamRequestFi } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -386,7 +398,7 @@ public BasicServerTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -396,7 +408,7 @@ public int endpointsPort() { } @Override - public HttpRequestDecoderConfig httpRequestDecoderConfig() { + public @Nullable HttpRequestDecoderConfig httpRequestDecoderConfig() { return CUSTOM_REQUEST_DECODER_CONFIG; } } @@ -416,7 +428,7 @@ public DownstreamServerTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -441,7 +453,7 @@ public ProxyTestingTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -451,7 +463,7 @@ public int endpointsPort() { } @Override - public HttpRequestDecoderConfig httpRequestDecoderConfig() { + public @Nullable HttpRequestDecoderConfig httpRequestDecoderConfig() { return CUSTOM_REQUEST_DECODER_CONFIG; } } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyDistributedTracingConfigBehaviorAndTagsComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyDistributedTracingConfigBehaviorAndTagsComponentTest.java index 821f64a0..e7d5fb4f 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyDistributedTracingConfigBehaviorAndTagsComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyDistributedTracingConfigBehaviorAndTagsComponentTest.java @@ -542,8 +542,10 @@ private static class DownstreamEndpoint extends StandardEndpoint { public static final String TRIGGER_DOWNSTREAM_ERROR_HEADER_KEY = "triggerDownstreamError"; @Override - public CompletableFuture> execute( - RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx ) { if ("true".equals(request.getHeaders().get(TRIGGER_DOWNSTREAM_ERROR_HEADER_KEY))) { throw new RuntimeException("intentional downstream exception"); @@ -555,7 +557,7 @@ public CompletableFuture> execute( } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -571,8 +573,10 @@ public RouterEndpoint(int downstreamPort) { } @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo( - RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx ) { String fooPathParam = request.getPathParam("fooPathParam"); String downstreamUri = DownstreamEndpoint.MATCHING_PATH_BASE + "/" + fooPathParam; @@ -588,7 +592,7 @@ public CompletableFuture getDownstreamRequestFi } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -611,7 +615,7 @@ public DownstreamServerTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -621,7 +625,7 @@ public int endpointsPort() { } @Override - public DistributedTracingConfig distributedTracingConfig() { + public @Nullable DistributedTracingConfig distributedTracingConfig() { return dtConfig; } } @@ -644,7 +648,7 @@ public ProxyTestingTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -654,7 +658,7 @@ public int endpointsPort() { } @Override - public DistributedTracingConfig distributedTracingConfig() { + public @Nullable DistributedTracingConfig distributedTracingConfig() { return dtConfig; } } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyErrorResponsePayloadHandlingComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyErrorResponsePayloadHandlingComponentTest.java index 990cf21d..75929499 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyErrorResponsePayloadHandlingComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyErrorResponsePayloadHandlingComponentTest.java @@ -21,6 +21,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -158,14 +160,16 @@ static class BlankPayloadErrorContractEndpoint extends StandardEndpoint> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { throw new BlankPayloadErrorContractException(); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } @@ -176,14 +180,16 @@ static class DelegatedErrorContractEndpoint extends StandardEndpoint static final String DESIRED_ERROR_MESSAGE_HEADER_KEY = "desired-error-message"; @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { throw new DelegatedErrorContractException(request.getHeaders().get(DESIRED_ERROR_MESSAGE_HEADER_KEY)); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } @@ -194,14 +200,16 @@ static class StringErrorContractEndpoint extends StandardEndpoint { static final String DESIRED_ERROR_PAYLOAD_HEADER_KEY = "desired-error-payload"; @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { throw new StringErrorContractException(request.getHeaders().get(DESIRED_ERROR_PAYLOAD_HEADER_KEY)); } @Override - public Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } static class DefaultErrorContractEndpoint extends StandardEndpoint { @@ -209,14 +217,16 @@ static class DefaultErrorContractEndpoint extends StandardEndpoint { static final String MATCHING_PATH = "/defaultErrorContractEndpoint"; @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { throw new RuntimeException("kaboom"); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } @@ -275,12 +285,12 @@ public static class ErrorResponsePayloadTestingServerConfig implements ServerCon } @Override - public RiposteUnhandledErrorHandler riposteUnhandledErrorHandler() { + public @NotNull RiposteUnhandledErrorHandler riposteUnhandledErrorHandler() { return new CustomRiposteUnhandledErrorHandler(projectApiErrors, exceptionHandlerUtils); } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -297,10 +307,12 @@ static class CustomRiposteUnhandledErrorHandler extends RiposteUnhandledExceptio } @Override - protected ErrorResponseBody prepareFrameworkRepresentation( - final DefaultErrorContractDTO errorContractDTO, - final int httpStatusCode, final Collection rawFilteredApiErrors, - final Throwable originalException, final RequestInfoForLogging request + protected @NotNull ErrorResponseBody prepareFrameworkRepresentation( + final @NotNull DefaultErrorContractDTO errorContractDTO, + final int httpStatusCode, + final @NotNull Collection rawFilteredApiErrors, + final @NotNull Throwable originalException, + final @NotNull RequestInfoForLogging request ) { Throwable cause = originalException.getCause(); @@ -325,12 +337,12 @@ ErrorResponseBody handleBlankErrorContract() { return new ErrorResponseBody() { @Override - public String errorId() { + public @NotNull String errorId() { return errorId; } @Override - public Object bodyToSerialize() { + public @Nullable Object bodyToSerialize() { return null; } }; @@ -341,12 +353,12 @@ ErrorResponseBody handleDelegatedErrorContract(DelegatedErrorContractException o return new ErrorResponseBody() { @Override - public String errorId() { + public @NotNull String errorId() { return errorId; } @Override - public Object bodyToSerialize() { + public @Nullable Object bodyToSerialize() { return new DelegatedErrorContract(originalException.message); } }; @@ -357,10 +369,10 @@ ErrorResponseBody handleStringErrorContract(StringErrorContractException origina return new ErrorResponseBody() { @Override - public String errorId() { return errorId; } + public @NotNull String errorId() { return errorId; } @Override - public Object bodyToSerialize() { return originalException.message; } + public @Nullable Object bodyToSerialize() { return originalException.message; } }; } } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyMiscellaneousFunctionalityComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyMiscellaneousFunctionalityComponentTest.java index c6c7acbf..3240705f 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyMiscellaneousFunctionalityComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyMiscellaneousFunctionalityComponentTest.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.NotNull; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -281,7 +282,7 @@ public MiscellaneousFunctionalityTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -298,12 +299,16 @@ public static class MultiMatcherEndpoint extends StandardEndpoint public static String body = UUID.randomUUID().toString(); @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture(ResponseInfo.newBuilder().withContentForFullResponse(body).build()); } @Override - public Matcher requestMatcher() { return MultiMatcher.match(Arrays.asList(INTERNAL_MATCHING_PATH, EXTERNAL_MATCHING_PATH)); } + public @NotNull Matcher requestMatcher() { return MultiMatcher.match(Arrays.asList(INTERNAL_MATCHING_PATH, EXTERNAL_MATCHING_PATH)); } } public static class OrderMattersMultiMatcherEndpoint extends StandardEndpoint { @@ -311,7 +316,11 @@ public static class OrderMattersMultiMatcherEndpoint extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( ResponseInfo.newBuilder() .withContentForFullResponse(generateResponseBodyForImportantPathParam(request.getPathParam("importantPathParam"))) @@ -320,7 +329,7 @@ public CompletableFuture> execute(RequestInfo request } @Override - public Matcher requestMatcher() { return MultiMatcher.match(Arrays.asList(INTERNAL_MATCHING_PATH, NON_INTERNAL_MATCHING_PATH)); } + public @NotNull Matcher requestMatcher() { return MultiMatcher.match(Arrays.asList(INTERNAL_MATCHING_PATH, NON_INTERNAL_MATCHING_PATH)); } public static String generateResponseBodyForImportantPathParam(String importantPathParam) { return "The path param from the request was: " + importantPathParam; @@ -333,7 +342,7 @@ public static class InvalidOrderingMultiMatcherEndpoint extends OrderMattersMult // Reverse the ordering from OrderMattersMultiMatcherEndpoint. This should make it so that INTERNAL_MATCHING_PATH is *never* reachable. @Override - public Matcher requestMatcher() { return MultiMatcher.match(Arrays.asList(NON_INTERNAL_MATCHING_PATH, INTERNAL_MATCHING_PATH)); } + public @NotNull Matcher requestMatcher() { return MultiMatcher.match(Arrays.asList(NON_INTERNAL_MATCHING_PATH, INTERNAL_MATCHING_PATH)); } } public static class EmptyMetadataErrorThrower extends StandardEndpoint { @@ -342,12 +351,16 @@ public static class EmptyMetadataErrorThrower extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { throw new ApiException(ERROR_NO_METADATA); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } @@ -359,12 +372,16 @@ public static class WithMetadataErrorThrower extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { throw new ApiException(ERROR_WITH_METADATA); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } @@ -382,9 +399,11 @@ public ProxyRouterResponseModificationEndpoint(int port) { } @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( new DownstreamRequestFirstChunkInfo( "127.0.0.1", port, false, @@ -394,14 +413,17 @@ public CompletableFuture getDownstreamRequestFi } @Override - public void handleDownstreamResponseFirstChunk(HttpResponse downstreamResponseFirstChunk, RequestInfo origRequestInfo) { + public void handleDownstreamResponseFirstChunk( + @NotNull HttpResponse downstreamResponseFirstChunk, + @NotNull RequestInfo origRequestInfo + ) { downstreamResponseFirstChunk.headers().set(ORIG_HTTP_STATUS_CODE_RESPONSE_HEADER_KEY, String.valueOf(downstreamResponseFirstChunk.status().code())); downstreamResponseFirstChunk.setStatus(new HttpResponseStatus(MODIFIED_HTTP_STATUS_RESPONSE_CODE, "junk status code")); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyMultipartRequestsWorkComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyMultipartRequestsWorkComponentTest.java index 0fea8297..37c4e982 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyMultipartRequestsWorkComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyMultipartRequestsWorkComponentTest.java @@ -17,6 +17,7 @@ import com.google.common.hash.Hashing; import org.apache.commons.io.IOUtils; +import org.jetbrains.annotations.NotNull; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -171,7 +172,7 @@ public MultipartTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -186,7 +187,11 @@ public static class MultipartTestEndpoint extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { List hashesFound = new ArrayList<>(); for (InterfaceHttpData multipartData : request.getMultipartParts()) { @@ -217,7 +222,7 @@ public CompletableFuture> execute(RequestInfo reque } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.POST); } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyPayloadHandlingComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyPayloadHandlingComponentTest.java index 571b5bd2..5ea88e2d 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyPayloadHandlingComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyPayloadHandlingComponentTest.java @@ -18,6 +18,8 @@ import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -324,7 +326,7 @@ public PayloadTypeHandlingTestConfig(int downstreamPortNonSsl, int downstreamPor } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -351,7 +353,7 @@ public DownstreamServerTestConfig(boolean isSsl) { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -397,7 +399,11 @@ public static class ByteArrayPayloadReturner extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { byte[] responsePayload = generateRandomBytes(15000); String responsePayloadHash = getHashForPayload(responsePayload); return CompletableFuture.completedFuture( @@ -408,7 +414,7 @@ public CompletableFuture> execute(RequestInfo reque } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.POST); } } @@ -418,7 +424,11 @@ public static class CharSequencePayloadReturner extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { CharSequence responsePayload = new StringBuilder(UUID.randomUUID().toString()); String responsePayloadHash = getHashForPayload(responsePayload.toString().getBytes(CharsetUtil.UTF_8)); return CompletableFuture.completedFuture( @@ -429,7 +439,7 @@ public CompletableFuture> execute(RequestInfo } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.POST); } } @@ -454,7 +464,11 @@ public static class SerializableObjectPayloadReturner extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { SerializableObject responsePayload = new SerializableObject(UUID.randomUUID().toString(), generateRandomBytes(32)); try { String responsePayloadAsJson = jsonSerializer.writeValueAsString(responsePayload); @@ -470,7 +484,7 @@ public CompletableFuture> execute(RequestInfo public static final String MATCHING_PATH = "/voidDeserializer"; @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { if (request.getContent() != null) throw new IllegalStateException("Since the deserialized type is Void, getContent() should return null. Instead it returned: " + request.getContent()); @@ -511,7 +529,7 @@ public CompletableFuture> execute(RequestInfo request } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -521,7 +539,11 @@ public static class StringTypeDeserializer extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { if (!request.getContent().equals(request.getRawContent())) { throw new IllegalStateException( "Since the deserialized type is String, getContent() should return the same thing as getRawContent(). getContent(): " + request @@ -534,7 +556,7 @@ public CompletableFuture> execute(RequestInfo reque } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -544,7 +566,11 @@ public static class ByteArrayTypeDeserializer extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { if (!request.getContent().equals(request.getRawContentBytes())) { throw new IllegalStateException( "Since the deserialized type is byte[], getContent() should return the same thing as " @@ -558,7 +584,7 @@ public CompletableFuture> execute(RequestInfo reque } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -568,7 +594,11 @@ public static class WidgetTypeDeserializer extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { if (request.getContent() == null) { throw new IllegalStateException( "getContent() should return a non-null value for deserializable content. getRawContent(): " + request.getRawContent()); @@ -595,7 +625,7 @@ protected String responseMessage() { } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -610,9 +640,11 @@ public DownstreamProxyNonSsl(int downstreamPort) { } @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( new DownstreamRequestFirstChunkInfo( "127.0.0.1", downstreamPort, false, @@ -622,7 +654,7 @@ public CompletableFuture getDownstreamRequestFi } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -637,9 +669,11 @@ public DownstreamProxySsl(int downstreamPort) { } @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( new DownstreamRequestFirstChunkInfo( "127.0.0.1", downstreamPort, true, @@ -649,7 +683,7 @@ public CompletableFuture getDownstreamRequestFi } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -663,7 +697,7 @@ public DownstreamEndpoint(boolean isSslServer) { } @Override - public TypeReference requestContentType() { + public @Nullable TypeReference requestContentType() { return new TypeReference() {}; } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyPreEndpointExecutionWorkChainComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyPreEndpointExecutionWorkChainComponentTest.java index 1b534506..1112d194 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyPreEndpointExecutionWorkChainComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyPreEndpointExecutionWorkChainComponentTest.java @@ -20,6 +20,8 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -372,14 +374,14 @@ private void verifyErrorReceived(ExtractableResponse response, ApiError expected private static RequestValidator customContentValidatorForWorkChainNotification() { return new RequestValidator() { @Override - public void validateRequestContent(RequestInfo request) { + public void validateRequestContent(@NotNull RequestInfo request) { contentValidationThreadNames.add(Thread.currentThread().getName()); if ("true".equals(request.getHeaders().get(BLOW_UP_IN_CONTENT_VALIDATOR_HEADER_KEY))) throw new ApiException(CONTENT_VALIDATOR_API_ERROR); } @Override - public void validateRequestContent(RequestInfo request, Class... validationGroups) { + public void validateRequestContent(@NotNull RequestInfo request, @Nullable Class... validationGroups) { contentValidationThreadNames.add(Thread.currentThread().getName()); if ("true".equals(request.getHeaders().get(BLOW_UP_IN_CONTENT_VALIDATOR_HEADER_KEY))) throw new ApiException(CONTENT_VALIDATOR_API_ERROR); @@ -388,10 +390,14 @@ public void validateRequestContent(RequestInfo request, Class... validatio } private static RequestSecurityValidator customSecurityValidatorForWorkChainNotification( - Collection> endpointsToValidate, boolean disableWorkChain) { + Collection> endpointsToValidate, boolean disableWorkChain + ) { return new RequestSecurityValidator() { @Override - public void validateSecureRequestForEndpoint(RequestInfo requestInfo, Endpoint endpoint) { + public void validateSecureRequestForEndpoint( + @NotNull RequestInfo requestInfo, + @NotNull Endpoint endpoint + ) { securityValidationThreadNames.add(Thread.currentThread().getName()); if ("true".equals(requestInfo.getHeaders().get(BLOW_UP_IN_SECURITY_VALIDATOR_HEADER_KEY))) @@ -399,7 +405,7 @@ public void validateSecureRequestForEndpoint(RequestInfo requestInfo, Endpoin } @Override - public Collection> endpointsToValidate() { + public @NotNull Collection> endpointsToValidate() { return endpointsToValidate; } @@ -446,7 +452,7 @@ public PreEndpointExecutionWorkChainTestConfig(boolean disableWorkChain) { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -456,12 +462,12 @@ public int endpointsPort() { } @Override - public RequestValidator requestContentValidationService() { + public @Nullable RequestValidator requestContentValidationService() { return customContentValidatorForWorkChainNotification; } @Override - public RequestSecurityValidator requestSecurityValidator() { + public @Nullable RequestSecurityValidator requestSecurityValidator() { return customSecurityValidatorForWorkChainNotification; } } @@ -471,25 +477,29 @@ public static class WorkChainEndpoint extends StandardEndpoint public static String WORK_CHAIN_MATCHING_PATH = "/workChain"; @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { endpointThreadNames.add(Thread.currentThread().getName() + "-NORMAL-ENDPOINT"); return CompletableFuture.completedFuture(ResponseInfo.newBuilder("work chain passed, post obj foo: " + request.getContent().foo).build()); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(WORK_CHAIN_MATCHING_PATH, HttpMethod.POST); } // Force asynchronous deserialization/validation @Override - public boolean shouldValidateAsynchronously(RequestInfo request) { + public boolean shouldValidateAsynchronously(@NotNull RequestInfo request) { return true; } // Use a deserializer that will keep track of when it was called and on what thread. @Override - public ObjectMapper customRequestContentDeserializer(RequestInfo request) { + public @Nullable ObjectMapper customRequestContentDeserializer(@NotNull RequestInfo request) { return customRequestContentDeserializerForWorkChainNotification(request); } } @@ -499,25 +509,29 @@ public static class NonWorkChainEndpoint extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { endpointThreadNames.add(Thread.currentThread().getName() + "-NONWORKCHAIN-ENDPOINT"); return CompletableFuture.completedFuture(ResponseInfo.newBuilder("non work chain passed, post obj foo: " + request.getContent().foo).build()); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(NON_WORK_CHAIN_MATCHING_PATH, HttpMethod.POST); } // Force synchronous deserialization/validation @Override - public boolean shouldValidateAsynchronously(RequestInfo request) { + public boolean shouldValidateAsynchronously(@NotNull RequestInfo request) { return false; } // Use a deserializer that will keep track of when it was called and on what thread. @Override - public ObjectMapper customRequestContentDeserializer(RequestInfo request) { + public @Nullable ObjectMapper customRequestContentDeserializer(@NotNull RequestInfo request) { return customRequestContentDeserializerForWorkChainNotification(request); } } @@ -527,9 +541,11 @@ public static class WorkChainProxyEndpoint extends ProxyRouterEndpoint { public static String PROXY_MATCHING_PATH = "/workChainProxy"; @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { endpointThreadNames.add(Thread.currentThread().getName() + "-PROXY-ENDPOINT"); int destinationPort = Integer.parseInt(request.getHeaders().get(PROXY_ROUTER_DESTINATION_PORT_HEADER_KEY)); return CompletableFuture.completedFuture( @@ -539,19 +555,19 @@ public CompletableFuture getDownstreamRequestFi } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(PROXY_MATCHING_PATH, HttpMethod.POST); } // Force asynchronous deserialization/validation (won't actually matter since proxy endpoints don't do content deserializaiton or validation) @Override - public boolean shouldValidateAsynchronously(RequestInfo request) { + public boolean shouldValidateAsynchronously(@NotNull RequestInfo request) { return true; } // Use a deserializer that will keep track of when it was called and on what thread. @Override - public ObjectMapper customRequestContentDeserializer(RequestInfo request) { + public @Nullable ObjectMapper customRequestContentDeserializer(@NotNull RequestInfo request) { return customRequestContentDeserializerForWorkChainNotification(request); } } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyEndpointsDoNotAlterRequestOrResponseByDefaultComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyEndpointsDoNotAlterRequestOrResponseByDefaultComponentTest.java index f2e751f1..d547998f 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyEndpointsDoNotAlterRequestOrResponseByDefaultComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyEndpointsDoNotAlterRequestOrResponseByDefaultComponentTest.java @@ -18,6 +18,8 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -647,9 +649,11 @@ public RouterEndpoint(int downstreamPort) { } @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( new DownstreamRequestFirstChunkInfo( "127.0.0.1", downstreamPort, false, @@ -659,7 +663,7 @@ public CompletableFuture getDownstreamRequestFi } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -679,21 +683,23 @@ public DownstreamServerTestConfig() { // We still have to have a non-empty collection though or Riposte won't start up. endpoints = singleton(new StandardEndpoint() { @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match("/should/never/match/anything/" + UUID.randomUUID().toString()); } @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { throw new UnsupportedOperationException("Should never reach here"); } }); } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -703,7 +709,7 @@ public int endpointsPort() { } @Override - public List pipelineCreateHooks() { + public @Nullable List<@NotNull PipelineCreateHook> pipelineCreateHooks() { return singletonList(pipeline -> { try { // Clear out the entire pipeline. We're going to build one from scratch. @@ -738,7 +744,7 @@ public ProxyTestingTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -748,7 +754,7 @@ public int endpointsPort() { } @Override - public List pipelineCreateHooks() { + public @Nullable List<@NotNull PipelineCreateHook> pipelineCreateHooks() { return singletonList(pipeline -> { pipeline.addFirst("recordProxyInboundRequest", new RecordProxyServerInboundRequest()); pipeline.addFirst("recordProxyOutboundResponse", new RecordProxyServerOutboundResponse()); diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyRequestCornerCasesComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyRequestCornerCasesComponentTest.java index 9e4d3f0a..9ffa04bc 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyRequestCornerCasesComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyRequestCornerCasesComponentTest.java @@ -18,6 +18,8 @@ import com.nike.riposte.server.testutils.ComponentTestUtils.NettyHttpClientResponse; import com.nike.riposte.util.Matcher; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -174,9 +176,11 @@ public RouterEndpointForwardingToShortCircuitError(int downstreamPort) { } @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( new DownstreamRequestFirstChunkInfo( "127.0.0.1", downstreamPort, false, @@ -186,7 +190,7 @@ public CompletableFuture getDownstreamRequestFi } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -219,14 +223,16 @@ public ShortCircuitingEndpoint() { } @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { throw new ApiException(FAIL_FAST_API_ERROR); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -237,14 +243,16 @@ public static class DelayEndpoint extends StandardEndpoint { public static final String RESPONSE_PAYLOAD = "delay-endpoint-" + UUID.randomUUID().toString(); @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.supplyAsync(() -> { try { Thread.sleep(DELAY_MILLIS); @@ -263,14 +271,16 @@ public static class LongerDelayEndpoint extends StandardEndpoint { public static final String RESPONSE_PAYLOAD = "longer-delay-endpoint-" + UUID.randomUUID().toString(); @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.supplyAsync(() -> { try { Thread.sleep(DELAY_MILLIS); @@ -298,7 +308,7 @@ public DownstreamServerTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -325,7 +335,7 @@ public RouterServerTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -335,7 +345,7 @@ public int endpointsPort() { } @Override - public List pipelineCreateHooks() { + public @Nullable List<@NotNull PipelineCreateHook> pipelineCreateHooks() { return singletonList(new ProxyRouterExplosionPipelineHook()); } @@ -343,7 +353,7 @@ public List pipelineCreateHooks() { public static class ProxyRouterExplosionPipelineHook implements PipelineCreateHook { @Override - public void executePipelineCreateHook(ChannelPipeline pipeline) { + public void executePipelineCreateHook(@NotNull ChannelPipeline pipeline) { pipeline.addBefore(PROXY_ROUTER_ENDPOINT_EXECUTION_HANDLER_NAME, "intentionalExplosionHandler", new IntentionalExplosionHandler()); diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyRouterTracingBehaviorComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyRouterTracingBehaviorComponentTest.java index 6a54cd39..f3b89735 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyRouterTracingBehaviorComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyProxyRouterTracingBehaviorComponentTest.java @@ -19,6 +19,7 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -268,7 +269,11 @@ static class DownstreamEndpoint extends StandardEndpoint { public static final String RECEIVED_SAMPLED_HEADER_KEY = "received-sampled"; @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { HttpHeaders reqHeaders = request.getHeaders(); return CompletableFuture.completedFuture( ResponseInfo.newBuilder(RESPONSE_PAYLOAD) @@ -284,7 +289,7 @@ public CompletableFuture> execute(RequestInfo request } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -301,10 +306,11 @@ public RouterEndpoint(int downstreamPort) { } @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { - + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { DownstreamRequestFirstChunkInfo target = new DownstreamRequestFirstChunkInfo( "127.0.0.1", downstreamPort, false, generateSimplePassthroughRequest(request, DownstreamEndpoint.MATCHING_PATH, request.getMethod(), ctx) @@ -325,7 +331,7 @@ public CompletableFuture getDownstreamRequestFi } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } } @@ -345,7 +351,7 @@ public DownstreamServerTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -370,7 +376,7 @@ public ProxyTestingTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyRequestAndResponseFilteringComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyRequestAndResponseFilteringComponentTest.java index fdf7123a..32fb5334 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyRequestAndResponseFilteringComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyRequestAndResponseFilteringComponentTest.java @@ -21,6 +21,8 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -379,12 +381,12 @@ public RequestAndResponseFilterTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @Override - public List requestAndResponseFilters() { + public @Nullable List<@NotNull RequestAndResponseFilter> requestAndResponseFilters() { return filters; } @@ -402,7 +404,11 @@ private static class BasicEndpoint extends StandardEndpoint { public static final ApiError FORCED_ERROR = new ApiErrorBase("FORCED_ERROR", 42, "forced error", 542); @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { if ("true".equals(request.getHeaders().get(FORCE_ERROR_HEADER_KEY))) throw ApiException.newBuilder().withApiErrors(FORCED_ERROR).build(); @@ -412,7 +418,7 @@ public CompletableFuture> execute(RequestInfo request } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } @@ -454,7 +460,9 @@ protected static class FirstFilterNormal implements RequestAndResponseFilter { public static final String FIRST_FILTER_RESPONSE_CUMULATIVE_HEADER_KEY = UUID.randomUUID().toString(); @Override - public RequestInfo filterRequestFirstChunkNoPayload(RequestInfo currentRequestInfo, ChannelHandlerContext ctx) { + public @Nullable RequestInfo filterRequestFirstChunkNoPayload( + @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx + ) { currentRequestInfo.getHeaders().set(FIRST_FILTER_ONLY_FIRST_CHUNK_REQ_HEADER_KEY, FIRST_FILTER_ONLY_FIRST_CHUNK_REQ_HEADER_VALUE); currentRequestInfo.getHeaders().set(COMMON_FILTER_REQUEST_FIRST_CHUNK_OVERRIDE_HEADER_KEY, FIRST_FILTER_REQUEST_FIRST_CHUNK_OVERRIDE_HEADER_VALUE); currentRequestInfo.getHeaders().add(COMMON_FILTER_REQUEST_FIRST_CHUNK_CUMULATIVE_HEADER_KEY, FIRST_FILTER_REQUEST_FIRST_CHUNK_CUMULATIVE_HEADER_VALUE); @@ -463,7 +471,9 @@ public RequestInfo filterRequestFirstChunkNoPayload(RequestInfo curren } @Override - public RequestInfo filterRequestLastChunkWithFullPayload(RequestInfo currentRequestInfo, ChannelHandlerContext ctx) { + public @Nullable RequestInfo filterRequestLastChunkWithFullPayload( + @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx + ) { currentRequestInfo.getHeaders().set(FIRST_FILTER_ONLY_LAST_CHUNK_REQ_HEADER_KEY, FIRST_FILTER_ONLY_LAST_CHUNK_REQ_HEADER_VALUE); currentRequestInfo.getHeaders().set(COMMON_FILTER_REQUEST_LAST_CHUNK_OVERRIDE_HEADER_KEY, FIRST_FILTER_REQUEST_LAST_CHUNK_OVERRIDE_HEADER_VALUE); currentRequestInfo.getHeaders().add(COMMON_FILTER_REQUEST_LAST_CHUNK_CUMULATIVE_HEADER_KEY, FIRST_FILTER_REQUEST_LAST_CHUNK_CUMULATIVE_HEADER_VALUE); @@ -472,7 +482,11 @@ public RequestInfo filterRequestLastChunkWithFullPayload(RequestInfo c } @Override - public ResponseInfo filterResponse(ResponseInfo currentResponseInfo, RequestInfo requestInfo, ChannelHandlerContext ctx) { + public @Nullable ResponseInfo filterResponse( + @NotNull ResponseInfo currentResponseInfo, + @NotNull RequestInfo requestInfo, + @NotNull ChannelHandlerContext ctx + ) { addRequestHeadersToResponseIfNotAlreadyDone(requestInfo, currentResponseInfo); currentResponseInfo.getHeaders().set(FIRST_FILTER_ONLY_RESPONSE_HEADER_KEY, FIRST_FILTER_ONLY_RESPONSE_HEADER_VALUE); @@ -509,8 +523,8 @@ protected static class SecondFilterShortCircuiting implements ShortCircuitingReq public static final String SHORT_CIRCUIT_LAST_CHUNK_RESPONSE_PAYLOAD = "short-circuit-last-chunk-" + UUID.randomUUID().toString(); @Override - public Pair, Optional>> filterRequestFirstChunkWithOptionalShortCircuitResponse( - RequestInfo currentRequestInfo, ChannelHandlerContext ctx + public @Nullable Pair, Optional>> filterRequestFirstChunkWithOptionalShortCircuitResponse( + @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx ) { currentRequestInfo.getHeaders().set(SECOND_FILTER_ONLY_FIRST_CHUNK_REQ_HEADER_KEY, SECOND_FILTER_ONLY_FIRST_CHUNK_REQ_HEADER_VALUE); currentRequestInfo.getHeaders().set(COMMON_FILTER_REQUEST_FIRST_CHUNK_OVERRIDE_HEADER_KEY, SECOND_FILTER_REQUEST_FIRST_CHUNK_OVERRIDE_HEADER_VALUE); @@ -525,8 +539,8 @@ public Pair, Optional>> filterRequestFirstChu } @Override - public Pair, Optional>> filterRequestLastChunkWithOptionalShortCircuitResponse( - RequestInfo currentRequestInfo, ChannelHandlerContext ctx + public @Nullable Pair, Optional>> filterRequestLastChunkWithOptionalShortCircuitResponse( + @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx ) { currentRequestInfo.getHeaders().set(SECOND_FILTER_ONLY_LAST_CHUNK_REQ_HEADER_KEY, SECOND_FILTER_ONLY_LAST_CHUNK_REQ_HEADER_VALUE); currentRequestInfo.getHeaders().set(COMMON_FILTER_REQUEST_LAST_CHUNK_OVERRIDE_HEADER_KEY, SECOND_FILTER_REQUEST_LAST_CHUNK_OVERRIDE_HEADER_VALUE); @@ -541,7 +555,11 @@ public Pair, Optional>> filterRequestLastChun } @Override - public ResponseInfo filterResponse(ResponseInfo currentResponseInfo, RequestInfo requestInfo, ChannelHandlerContext ctx) { + public @Nullable ResponseInfo filterResponse( + @NotNull ResponseInfo currentResponseInfo, + @NotNull RequestInfo requestInfo, + @NotNull ChannelHandlerContext ctx + ) { addRequestHeadersToResponseIfNotAlreadyDone(requestInfo, currentResponseInfo); currentResponseInfo.getHeaders().set(SECOND_FILTER_ONLY_RESPONSE_HEADER_KEY, SECOND_FILTER_ONLY_RESPONSE_HEADER_VALUE); @@ -571,7 +589,9 @@ protected static class ThirdFilterNormal implements RequestAndResponseFilter { public static final String THIRD_FILTER_RESPONSE_CUMULATIVE_HEADER_KEY = UUID.randomUUID().toString(); @Override - public RequestInfo filterRequestFirstChunkNoPayload(RequestInfo currentRequestInfo, ChannelHandlerContext ctx) { + public @Nullable RequestInfo filterRequestFirstChunkNoPayload( + @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx + ) { currentRequestInfo.getHeaders().set(THIRD_FILTER_ONLY_FIRST_CHUNK_REQ_HEADER_KEY, THIRD_FILTER_ONLY_FIRST_CHUNK_REQ_HEADER_VALUE); currentRequestInfo.getHeaders().set(COMMON_FILTER_REQUEST_FIRST_CHUNK_OVERRIDE_HEADER_KEY, THIRD_FILTER_REQUEST_FIRST_CHUNK_OVERRIDE_HEADER_VALUE); currentRequestInfo.getHeaders().add(COMMON_FILTER_REQUEST_FIRST_CHUNK_CUMULATIVE_HEADER_KEY, THIRD_FILTER_REQUEST_FIRST_CHUNK_CUMULATIVE_HEADER_VALUE); @@ -580,7 +600,9 @@ public RequestInfo filterRequestFirstChunkNoPayload(RequestInfo curren } @Override - public RequestInfo filterRequestLastChunkWithFullPayload(RequestInfo currentRequestInfo, ChannelHandlerContext ctx) { + public @Nullable RequestInfo filterRequestLastChunkWithFullPayload( + @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx + ) { currentRequestInfo.getHeaders().set(THIRD_FILTER_ONLY_LAST_CHUNK_REQ_HEADER_KEY, THIRD_FILTER_ONLY_LAST_CHUNK_REQ_HEADER_VALUE); currentRequestInfo.getHeaders().set(COMMON_FILTER_REQUEST_LAST_CHUNK_OVERRIDE_HEADER_KEY, THIRD_FILTER_REQUEST_LAST_CHUNK_OVERRIDE_HEADER_VALUE); currentRequestInfo.getHeaders().add(COMMON_FILTER_REQUEST_LAST_CHUNK_CUMULATIVE_HEADER_KEY, THIRD_FILTER_REQUEST_LAST_CHUNK_CUMULATIVE_HEADER_VALUE); @@ -589,7 +611,11 @@ public RequestInfo filterRequestLastChunkWithFullPayload(RequestInfo c } @Override - public ResponseInfo filterResponse(ResponseInfo currentResponseInfo, RequestInfo requestInfo, ChannelHandlerContext ctx) { + public @Nullable ResponseInfo filterResponse( + @NotNull ResponseInfo currentResponseInfo, + @NotNull RequestInfo requestInfo, + @NotNull ChannelHandlerContext ctx + ) { addRequestHeadersToResponseIfNotAlreadyDone(requestInfo, currentResponseInfo); currentResponseInfo.getHeaders().set(THIRD_FILTER_ONLY_RESPONSE_HEADER_KEY, THIRD_FILTER_ONLY_RESPONSE_HEADER_VALUE); diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyRequestSizeValidationComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyRequestSizeValidationComponentTest.java index 0ed9f5be..fe654c16 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyRequestSizeValidationComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyRequestSizeValidationComponentTest.java @@ -14,6 +14,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -259,14 +261,18 @@ private static class BasicEndpoint extends StandardEndpoint { public static final String RESPONSE_PAYLOAD = "basic-endpoint-" + UUID.randomUUID().toString(); @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( ResponseInfo.newBuilder(RESPONSE_PAYLOAD).build() ); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.POST); } } @@ -278,19 +284,23 @@ private static class BasicEndpointWithRequestSizeValidationOverride extends Stan public static Integer MAX_REQUEST_SIZE = 10; @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( ResponseInfo.newBuilder(RESPONSE_PAYLOAD).build() ); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.POST); } @Override - public Integer maxRequestSizeInBytesOverride() { + public @Nullable Integer maxRequestSizeInBytesOverride() { return MAX_REQUEST_SIZE; } } @@ -302,19 +312,23 @@ private static class BasicEndpointWithRequestSizeValidationDisabled extends Stan public static Integer MAX_REQUEST_SIZE = 0; @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( ResponseInfo.newBuilder(RESPONSE_PAYLOAD).build() ); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.POST); } @Override - public Integer maxRequestSizeInBytesOverride() { + public @Nullable Integer maxRequestSizeInBytesOverride() { return MAX_REQUEST_SIZE; } } @@ -341,7 +355,7 @@ public int maxRequestSizeInBytes() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyResponseHttpStatusCodeHandlingRfcCorrectnessComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyResponseHttpStatusCodeHandlingRfcCorrectnessComponentTest.java index e2bad02b..5e58cdd6 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyResponseHttpStatusCodeHandlingRfcCorrectnessComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyResponseHttpStatusCodeHandlingRfcCorrectnessComponentTest.java @@ -16,6 +16,8 @@ import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -346,7 +348,7 @@ public RouterServerConfig(int myPort, int downstreamPort) { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return Collections.singleton(proxyEndpoint); } @@ -373,7 +375,7 @@ public BackendServerConfig(int port) { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -388,7 +390,7 @@ public long workerChannelIdleTimeoutMillis() { } @Override - public List pipelineCreateHooks() { + public @Nullable List<@NotNull PipelineCreateHook> pipelineCreateHooks() { return singletonList(pipeline -> pipeline .addFirst("recordBackendRawOutboundResponse", new RecordBackendServerRawOutboundResponse()) ); @@ -407,7 +409,11 @@ public static class BackendEndpoint extends StandardEndpoint { public static final String NON_EMPTY_PAYLOAD = generatePayload(1000); @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { int statusCode = Integer.parseInt(request.getHeaders().get(DESIRED_RESPONSE_HTTP_STATUS_CODE_HEADER_KEY)); boolean returnEmptyPayload = "true".equals(request.getHeaders().get(SHOULD_RETURN_EMPTY_PAYLOAD_BODY_HEADER_KEY)); String callIdReceived = String.valueOf(request.getHeaders().get(CALL_ID_REQUEST_HEADER_KEY)); @@ -433,7 +439,7 @@ public CompletableFuture> execute(RequestInfo request } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } @@ -446,9 +452,11 @@ public static class AllowedToLieAboutContentLengthEndpoint extends StandardEndpo public static final String RESPONSE_PAYLOAD = generatePayload(1042); @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { int statusCode = Integer.parseInt(request.getHeaders().get(DESIRED_HTTP_RESPONSE_CODE)); return CompletableFuture.completedFuture( @@ -460,7 +468,7 @@ public CompletableFuture> execute(RequestInfo request } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET, HttpMethod.HEAD); } } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifySSLComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifySSLComponentTest.java index dd917460..c20cbc1d 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifySSLComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifySSLComponentTest.java @@ -9,6 +9,7 @@ import com.nike.riposte.server.testutils.ComponentTestUtils; import com.nike.riposte.util.Matcher; +import org.jetbrains.annotations.NotNull; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -80,7 +81,7 @@ public SSLTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -100,12 +101,16 @@ public static class SSLTestEndpoint extends StandardEndpoint { public static String MATCHING_PATH = "/ssl"; @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture(ResponseInfo.newBuilder(RESPONSE_STRING).build()); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifySmartHttpContentCompressorComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifySmartHttpContentCompressorComponentTest.java index 0923fb1d..bea536aa 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifySmartHttpContentCompressorComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifySmartHttpContentCompressorComponentTest.java @@ -15,6 +15,7 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.NotNull; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -148,7 +149,11 @@ private static class BasicEndpoint extends StandardEndpoint { public static final String DISABLE_COMPRESSION_HEADER_KEY = "disable-compression"; @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { String responsePayload = generatePayloadOfSizeInBytes( RESPONSE_PAYLOAD_PREFIX, Integer.parseInt(request.getHeaders().get(DESIRED_UNCOMPRESSED_PAYLOAD_SIZE_HEADER_KEY)) @@ -163,7 +168,7 @@ public CompletableFuture> execute(RequestInfo request } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } @@ -182,7 +187,7 @@ public ResponsePayloadCompressionServerConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyTimeoutsAndProxyConnectionPoolingWorksComponentTest.java b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyTimeoutsAndProxyConnectionPoolingWorksComponentTest.java index 8a08cd5c..8076642d 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyTimeoutsAndProxyConnectionPoolingWorksComponentTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/componenttest/VerifyTimeoutsAndProxyConnectionPoolingWorksComponentTest.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -333,7 +334,7 @@ public TimeoutsAndProxyTestServerConfig(long workerChannelIdleTimeoutMillis, } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -365,7 +366,11 @@ public static class LongDelayTestEndpoint extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { long delayMillis = Long.parseLong(request.getHeaders().get(DELAY_MILLIS_HEADER_KEY)); CompletableFuture> cf = new CompletableFuture<>(); @@ -376,7 +381,7 @@ public CompletableFuture> execute(RequestInfo reque } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } @@ -387,9 +392,11 @@ public static class ProxyLongDelayTestEndpoint extends ProxyRouterEndpoint { public static final String MATCHING_PATH = "/proxy" + LongDelayTestEndpoint.MATCHING_PATH; @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( new DownstreamRequestFirstChunkInfo( "127.0.0.1", @@ -401,7 +408,7 @@ public CompletableFuture getDownstreamRequestFi } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } @@ -411,12 +418,16 @@ public static class ChannelInfoTestEndpoint extends StandardEndpoint> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture(ResponseInfo.newBuilder(ctx.channel().toString()).build()); } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } @@ -427,9 +438,11 @@ public static class ProxyChannelInfoTestEndpoint extends ProxyRouterEndpoint { public static final String MATCHING_PATH = "/proxy" + ChannelInfoTestEndpoint.MATCHING_PATH; @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.completedFuture( new DownstreamRequestFirstChunkInfo( "127.0.0.1", @@ -441,7 +454,7 @@ public CompletableFuture getDownstreamRequestFi } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH, HttpMethod.GET); } } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/handler/NonblockingEndpointExecutionHandlerTest.java b/riposte-core/src/test/java/com/nike/riposte/server/handler/NonblockingEndpointExecutionHandlerTest.java index 3e7c7bff..089ad9b4 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/handler/NonblockingEndpointExecutionHandlerTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/handler/NonblockingEndpointExecutionHandlerTest.java @@ -683,4 +683,23 @@ public void asyncErrorCallback_does_not_fire_exceptionCaught_event_on_ctx_if_cha verify(ctxMock, times(0)).fireExceptionCaught(any(Throwable.class)); } + @Test + public void doExecuteEndpointFunction_provides_friendly_exception_message_if_endpoint_execute_method_returns_null() { + // given + doReturn(null).when(endpointMock) + .execute(any(RequestInfo.class), any(Executor.class), any(ChannelHandlerContext.class)); + + Function>> executeFunc = handlerSpy + .doExecuteEndpointFunction(requestInfo, endpointMock, null, ctxMock); + + // when + Throwable ex = catchThrowable(() -> executeFunc.apply(null)); + + // then + verify(endpointMock).execute(requestInfo, longRunningTaskExecutorMock, ctxMock); + assertThat(ex) + .isInstanceOf(NullPointerException.class) + .hasMessage("NonblockingEndpoint.execute() cannot return null."); + } + } \ No newline at end of file diff --git a/riposte-core/src/test/java/com/nike/riposte/server/http/ProxyRouterEndpointTest.java b/riposte-core/src/test/java/com/nike/riposte/server/http/ProxyRouterEndpointTest.java index 7fdfe065..966f216b 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/http/ProxyRouterEndpointTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/http/ProxyRouterEndpointTest.java @@ -2,10 +2,11 @@ import com.nike.riposte.server.http.ProxyRouterEndpoint.DownstreamRequestFirstChunkInfo; import com.nike.riposte.util.Matcher; + import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.HttpRequest; + +import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -13,7 +14,11 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpRequest; + import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.Mockito.mock; @RunWith(DataProviderRunner.class) @@ -25,12 +30,16 @@ public class ProxyRouterEndpointTest { public void setup() { defaultImpl = new ProxyRouterEndpoint() { @Override - public CompletableFuture getDownstreamRequestFirstChunkInfo(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture getDownstreamRequestFirstChunkInfo( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } }; @@ -69,6 +78,45 @@ public void downstreamRequestFirstChunkInfo_constructorSetsValuesAsExpected() { assertThat(downstreamRequestFirstChunkInfo.performSubSpanAroundDownstreamCall).isTrue(); } + @Test + public void downstreamRequestFirstChunkInfo_constructor_throws_IllegalArgumentException_if_passed_null_host() { + // when + Throwable ex = catchThrowable( + () -> new DownstreamRequestFirstChunkInfo(null, 8080, true, mock(HttpRequest.class)) + ); + + // then + assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("host cannot be null."); + } + + @Test + public void downstreamRequestFirstChunkInfo_constructor_throws_IllegalArgumentException_if_passed_null_firstChunk() { + // when + Throwable ex = catchThrowable( + () -> new DownstreamRequestFirstChunkInfo("localhost", 8080, true, null) + ); + + // then + assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("firstChunk cannot be null."); + } + + @Test + public void downstreamRequestFirstChunkInfo_defaults_to_empty_optional_if_passed_null_customCircuitBreaker_optional() { + // when + DownstreamRequestFirstChunkInfo downstreamRequestFirstChunkInfo = new DownstreamRequestFirstChunkInfo( + "localhost", 8080, true, mock(HttpRequest.class), null, false + ); + + // then + assertThat(downstreamRequestFirstChunkInfo.customCircuitBreaker) + .isNotNull() + .isEmpty(); + } + @Test @DataProvider(value = { "true", @@ -77,7 +125,7 @@ public void downstreamRequestFirstChunkInfo_constructorSetsValuesAsExpected() { public void downstreamRequestFirstChunkInfo_allowsOverrideOfRelaxHttpsValidationFlag(boolean flagValue) { // when DownstreamRequestFirstChunkInfo downstreamRequestFirstChunkInfo = - new DownstreamRequestFirstChunkInfo("localhost", 8080, true, null) + new DownstreamRequestFirstChunkInfo("localhost", 8080, true, mock(HttpRequest.class)) .withRelaxedHttpsValidation(flagValue); // then @@ -92,7 +140,7 @@ public void downstreamRequestFirstChunkInfo_allowsOverrideOfRelaxHttpsValidation public void downstreamRequestFirstChunkInfo_allowsOverrideOfAddTracingFlag(boolean flagValue) { // when DownstreamRequestFirstChunkInfo downstreamRequestFirstChunkInfo = - new DownstreamRequestFirstChunkInfo("localhost", 8080, true, null) + new DownstreamRequestFirstChunkInfo("localhost", 8080, true, mock(HttpRequest.class)) .withAddTracingHeadersToDownstreamCall(flagValue); // then @@ -107,10 +155,46 @@ public void downstreamRequestFirstChunkInfo_allowsOverrideOfAddTracingFlag(boole public void downstreamRequestFirstChunkInfo_allowsOverrideOfPerformSubspanOnDownstreamCall(boolean flagValue) { // when DownstreamRequestFirstChunkInfo downstreamRequestFirstChunkInfo = - new DownstreamRequestFirstChunkInfo("localhost", 8080, true, null) + new DownstreamRequestFirstChunkInfo("localhost", 8080, true, mock(HttpRequest.class)) .withPerformSubSpanAroundDownstreamCall(flagValue); // then assertThat(downstreamRequestFirstChunkInfo.performSubSpanAroundDownstreamCall).isEqualTo(flagValue); } + + public void downstreamRequestFirstChunkInfo_throws_IllegalArgumentException_if_constructed_with_null_firstChunk() { + // when + Throwable ex = catchThrowable( + () -> new DownstreamRequestFirstChunkInfo("localhost", 8080, true, null) + ); + + // then + assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("firstChunk cannot be null."); + } + + public void downstreamRequestFirstChunkInfo_throws_IllegalArgumentException_if_constructed_with_null_host() { + // when + Throwable ex = catchThrowable( + () -> new DownstreamRequestFirstChunkInfo(null, 8080, true, mock(HttpRequest.class)) + ); + + // then + assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("host cannot be null."); + } + + public void downstreamRequestFirstChunkInfo_uses_empty_option_if_constructed_with_null_circuit_breaker() { + // when + DownstreamRequestFirstChunkInfo instance = new DownstreamRequestFirstChunkInfo( + "localhost", 8080, true, mock(HttpRequest.class), null, false + ); + + // then + assertThat(instance.customCircuitBreaker) + .isNotNull() + .isEmpty(); + } } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/http/StandardEndpointTest.java b/riposte-core/src/test/java/com/nike/riposte/server/http/StandardEndpointTest.java index 3d9f86d6..30fd4e99 100644 --- a/riposte-core/src/test/java/com/nike/riposte/server/http/StandardEndpointTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/server/http/StandardEndpointTest.java @@ -2,6 +2,7 @@ import com.nike.riposte.util.Matcher; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import java.io.IOException; @@ -28,12 +29,16 @@ public void basic_default_methods_return_expected_values() { // given StandardEndpoint defaultInstance = new StandardEndpoint() { @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } }; @@ -49,13 +54,16 @@ public void constructor_correctly_infers_inputType_and_TypeReference_for_String_ List> stringInputInstances = Arrays.asList( new StandardEndpoint() { @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } }, @@ -78,13 +86,16 @@ public void constructor_correctly_infers_inputType_and_TypeReference_for_specifi List> fooObjectInputInstances = Arrays.asList( new StandardEndpoint() { @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } }, @@ -107,13 +118,16 @@ public void constructor_correctly_sets_TypeReference_to_null_when_inputType_is_V List> voidInputInstances = Arrays.asList( new StandardEndpoint() { @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } }, @@ -136,13 +150,16 @@ public void constructor_correctly_sets_TypeReference_to_null_when_inputType_is_r List voidInputInstances = Arrays.asList( new StandardEndpoint() { @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } @Override - public CompletableFuture execute(RequestInfo request, Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } }, @@ -183,64 +200,80 @@ public void constructor_throws_IllegalArgumentException_if_inputType_is_nonspeci private static class BasicGenericImpl extends StandardEndpoint { @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } } private static class BasicHardcodedFooImpl extends StandardEndpoint { @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } } private static class BasicHardcodedStringImpl extends StandardEndpoint { @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } } private static class BasicHardcodedVoidImpl extends StandardEndpoint { @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } } private static class BasicHardcodedRawTypeImpl extends StandardEndpoint { @Override - public CompletableFuture execute(RequestInfo request, Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } } diff --git a/riposte-core/src/test/java/com/nike/riposte/server/http/impl/SimpleProxyRouterEndpointTest.java b/riposte-core/src/test/java/com/nike/riposte/server/http/impl/SimpleProxyRouterEndpointTest.java new file mode 100644 index 00000000..b67e38dd --- /dev/null +++ b/riposte-core/src/test/java/com/nike/riposte/server/http/impl/SimpleProxyRouterEndpointTest.java @@ -0,0 +1,139 @@ +package com.nike.riposte.server.http.impl; + +import com.nike.fastbreak.CircuitBreaker; +import com.nike.riposte.server.http.ProxyRouterEndpoint.DownstreamRequestFirstChunkInfo; +import com.nike.riposte.server.http.RequestInfo; +import com.nike.riposte.util.Matcher; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +/** + * Tests the functionality of {@link SimpleProxyRouterEndpoint}. + * + * @author Nic Munroe + */ +@RunWith(DataProviderRunner.class) +public class SimpleProxyRouterEndpointTest { + + @DataProvider(value = { + "true | true", + "true | false", + "false | true", + "false | false", + }, splitBy = "\\|") + @Test + public void kitchen_sink_constructor_sets_fields_as_expected(boolean isHttps, boolean disableCircuitBreaker) { + // given + Matcher matcherMock = mock(Matcher.class); + String host = "fooHost"; + int port = 4242; + String path = "/barPath"; + Optional> circuitBreakerOpt = Optional.of(mock(CircuitBreaker.class)); + + // when + SimpleProxyRouterEndpoint instance = new SimpleProxyRouterEndpoint( + matcherMock, host, port, path, isHttps, circuitBreakerOpt, disableCircuitBreaker + ); + + // then + assertThat(instance.incomingRequestMatcher).isSameAs(instance.requestMatcher()).isSameAs(matcherMock); + assertThat(instance.downstreamDestinationHost).isEqualTo(host); + assertThat(instance.downstreamDestinationPort).isEqualTo(port); + assertThat(instance.downstreamDestinationUriPath).isEqualTo(path); + assertThat(instance.isDownstreamCallHttps).isEqualTo(isHttps); + assertThat(instance.customCircuitBreaker).isEqualTo(circuitBreakerOpt); + assertThat(instance.disableCircuitBreaker).isEqualTo(disableCircuitBreaker); + } + + private enum ConstructorNullArgScenario { + NULL_MATCHER(null, "fooHost", "/fooPath", "incomingRequestMatcher cannot be null."), + NULL_HOST(mock(Matcher.class), null, "/fooPath", "downstreamDestinationHost cannot be null."), + NULL_PATH(mock(Matcher.class), "fooHost", null, "downstreamDestinationUriPath cannot be null."); + + public final Matcher matcher; + public final String host; + public final String path; + public final String expectedExceptionMessage; + + ConstructorNullArgScenario(Matcher matcher, String host, String path, String expectedExceptionMessage) { + this.matcher = matcher; + this.host = host; + this.path = path; + this.expectedExceptionMessage = expectedExceptionMessage; + } + } + + @DataProvider(value = { + "NULL_MATCHER", + "NULL_HOST", + "NULL_PATH", + }, splitBy = "\\|") + @Test + public void kitchen_sink_constructor_throws_IllegalArgumentException_when_passed_invalid_null_arg( + ConstructorNullArgScenario scenario + ) { + // when + Throwable ex = catchThrowable(() -> new SimpleProxyRouterEndpoint( + scenario.matcher, scenario.host, 4242, scenario.path, false, Optional.empty(), false + )); + + // then + assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(scenario.expectedExceptionMessage); + } + + @Test + public void kitchen_sink_constructor_uses_empty_optional_when_passed_null_circuit_breaker_option() { + // when + SimpleProxyRouterEndpoint instance = new SimpleProxyRouterEndpoint( + mock(Matcher.class), "fooHost", 4242, "/fooPath", false, null, false + ); + + // then + assertThat(instance.customCircuitBreaker) + .isNotNull() + .isEmpty(); + } + + @Test + public void getDownstreamRequestFirstChunkInfo_returns_exceptionally_completed_future_if_passed_request_with_null_method() { + // given + SimpleProxyRouterEndpoint instance = new SimpleProxyRouterEndpoint( + mock(Matcher.class), "fooHost", 4242, "/barPath", false + ); + RequestInfo requestMock = mock(RequestInfo.class); + doReturn(null).when(requestMock).getMethod(); + + // when + CompletableFuture result = instance.getDownstreamRequestFirstChunkInfo( + requestMock, mock(Executor.class), mock(ChannelHandlerContext.class) + ); + + // then + assertThat(result).isCompletedExceptionally(); + Throwable ex = catchThrowable(result::get); + assertThat(ex).isInstanceOf(ExecutionException.class); + assertThat(ex.getCause()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Received a request with null request.getMethod(). This should never happen."); + } + +} \ No newline at end of file diff --git a/riposte-core/src/test/java/com/nike/riposte/util/ErrorContractSerializerHelperTest.java b/riposte-core/src/test/java/com/nike/riposte/util/ErrorContractSerializerHelperTest.java index 61cbd776..49e38033 100644 --- a/riposte-core/src/test/java/com/nike/riposte/util/ErrorContractSerializerHelperTest.java +++ b/riposte-core/src/test/java/com/nike/riposte/util/ErrorContractSerializerHelperTest.java @@ -8,6 +8,8 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,12 +38,12 @@ private enum ErrorResponseBodyScenario { NULL_INSTANCE(() -> null), NULL_BODY_TO_SERIALIZE(() -> new ErrorResponseBody() { @Override - public String errorId() { - return null; + public @NotNull String errorId() { + return "doesnotmatter"; } @Override - public Object bodyToSerialize() { + public @Nullable Object bodyToSerialize() { return null; } }); diff --git a/riposte-guice-typesafe-config/build.gradle b/riposte-guice-typesafe-config/build.gradle index dd0b0a4a..b4cbaf26 100644 --- a/riposte-guice-typesafe-config/build.gradle +++ b/riposte-guice-typesafe-config/build.gradle @@ -5,8 +5,11 @@ dependencies { project(":riposte-guice"), "com.typesafe:config:$typesafeConfigVersion" ) - + compileOnly( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", + ) testCompile ( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", "junit:junit:$junitVersion", "org.mockito:mockito-core:$mockitoVersion", "org.assertj:assertj-core:$assertJVersion", diff --git a/riposte-guice/build.gradle b/riposte-guice/build.gradle index 6b96d1da..96e1a6cb 100644 --- a/riposte-guice/build.gradle +++ b/riposte-guice/build.gradle @@ -5,8 +5,11 @@ dependencies { project(":riposte-core"), "com.google.inject:guice:$guiceVersion" ) - + compileOnly( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", + ) testCompile ( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", "junit:junit:$junitVersion", "org.mockito:mockito-core:$mockitoVersion", "com.nike.backstopper:backstopper-reusable-tests:$backstopperVersion" diff --git a/riposte-metrics-codahale-signalfx/build.gradle b/riposte-metrics-codahale-signalfx/build.gradle index aea6cecb..bb3ebf54 100644 --- a/riposte-metrics-codahale-signalfx/build.gradle +++ b/riposte-metrics-codahale-signalfx/build.gradle @@ -5,10 +5,13 @@ dependencies { project(":riposte-metrics-codahale"), "com.signalfx.public:signalfx-codahale:$signalFxCodahaleVersion" ) - + compileOnly( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", + ) testCompile ( project(":riposte-guice"), project(":riposte-core").sourceSets.test.output, + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", "org.assertj:assertj-core:$assertJVersion", "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", "junit:junit:$junitVersion", diff --git a/riposte-metrics-codahale-signalfx/src/main/java/com/nike/riposte/metrics/codahale/SignalFxAwareCodahaleMetricsCollector.java b/riposte-metrics-codahale-signalfx/src/main/java/com/nike/riposte/metrics/codahale/SignalFxAwareCodahaleMetricsCollector.java index 88cac616..9c0326cf 100644 --- a/riposte-metrics-codahale-signalfx/src/main/java/com/nike/riposte/metrics/codahale/SignalFxAwareCodahaleMetricsCollector.java +++ b/riposte-metrics-codahale-signalfx/src/main/java/com/nike/riposte/metrics/codahale/SignalFxAwareCodahaleMetricsCollector.java @@ -19,6 +19,9 @@ import com.signalfx.codahale.reporter.MetricMetadata.TaggerBase; import com.signalfx.codahale.reporter.SignalFxReporter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.Arrays; import java.util.List; @@ -32,9 +35,9 @@ @SuppressWarnings("WeakerAccess") public class SignalFxAwareCodahaleMetricsCollector extends CodahaleMetricsCollector { - protected final MetricMetadata metricMetadata; - protected final MetricBuilder timerBuilder; - protected final MetricBuilder histogramBuilder; + protected final @NotNull MetricMetadata metricMetadata; + protected final @NotNull MetricBuilder timerBuilder; + protected final @NotNull MetricBuilder histogramBuilder; /** * Creates a new instance with a *new* {@link MetricRegistry} - this means you should be careful to retrieve it @@ -49,7 +52,7 @@ public class SignalFxAwareCodahaleMetricsCollector extends CodahaleMetricsCollec * @param sfxReporterFactory The {@link SignalFxReporterFactory} to use to get the {@link * SignalFxReporter#getMetricMetadata()} and reporting frequency. */ - public SignalFxAwareCodahaleMetricsCollector(SignalFxReporterFactory sfxReporterFactory) { + public SignalFxAwareCodahaleMetricsCollector(@NotNull SignalFxReporterFactory sfxReporterFactory) { this(new MetricRegistry(), sfxReporterFactory); } @@ -64,8 +67,8 @@ public SignalFxAwareCodahaleMetricsCollector(SignalFxReporterFactory sfxReporter * @param sfxReporterFactory The {@link SignalFxReporterFactory} to use to get the {@link * SignalFxReporter#getMetricMetadata()} and reporting frequency. */ - public SignalFxAwareCodahaleMetricsCollector(MetricRegistry metricRegistry, - SignalFxReporterFactory sfxReporterFactory) { + public SignalFxAwareCodahaleMetricsCollector(@NotNull MetricRegistry metricRegistry, + @NotNull SignalFxReporterFactory sfxReporterFactory) { this(metricRegistry, sfxReporterFactory.getReporter(metricRegistry).getMetricMetadata(), new RollingWindowTimerBuilder(sfxReporterFactory.getInterval(), @@ -85,85 +88,128 @@ public SignalFxAwareCodahaleMetricsCollector(MetricRegistry metricRegistry, * @param histogramBuilder The histogram builder for building new histograms. It's recommended that this be a {@link * RollingWindowHistogramBuilder} that matches the reporting frequency of your app's {@link SignalFxReporter}. */ - public SignalFxAwareCodahaleMetricsCollector(MetricRegistry metricRegistry, - MetricMetadata metricMetadata, - MetricBuilder timerBuilder, - MetricBuilder histogramBuilder) { + @SuppressWarnings("ConstantConditions") + public SignalFxAwareCodahaleMetricsCollector(@NotNull MetricRegistry metricRegistry, + @NotNull MetricMetadata metricMetadata, + @NotNull MetricBuilder timerBuilder, + @NotNull MetricBuilder histogramBuilder) { super(metricRegistry); + + if (metricMetadata == null) { + throw new IllegalArgumentException("metricMetadata cannot be null."); + } + if (timerBuilder == null) { + throw new IllegalArgumentException("timerBuilder cannot be null."); + } + if (histogramBuilder == null) { + throw new IllegalArgumentException("histogramBuilder cannot be null."); + } + this.metricMetadata = metricMetadata; this.timerBuilder = timerBuilder; this.histogramBuilder = histogramBuilder; } @Override - public Timer getNamedTimer(String timerName) { + public @NotNull Timer getNamedTimer(@NotNull String timerName) { return getNamedTimer(timerName, (Iterable>)null); } @SafeVarargs - public final Timer getNamedTimer(String timerName, Pair... dimensions) { + public final @NotNull Timer getNamedTimer( + @NotNull String timerName, + @Nullable Pair... dimensions + ) { return getNamedTimer(timerName, convertDimensionsToList(dimensions)); } - public Timer getNamedTimer(String timerName, Iterable> dimensions) { + public @NotNull Timer getNamedTimer( + @NotNull String timerName, + @Nullable Iterable> dimensions + ) { return getNamedMetric(timerName, timerBuilder, dimensions); } @Override - public Meter getNamedMeter(String meterName) { + public @NotNull Meter getNamedMeter(@NotNull String meterName) { return getNamedMeter(meterName, (Iterable>)null); } @SafeVarargs - public final Meter getNamedMeter(String meterName, Pair... dimensions) { + public final @NotNull Meter getNamedMeter( + @NotNull String meterName, + @Nullable Pair... dimensions + ) { return getNamedMeter(meterName, convertDimensionsToList(dimensions)); } - public Meter getNamedMeter(String meterName, Iterable> dimensions) { + public @NotNull Meter getNamedMeter( + @NotNull String meterName, + @Nullable Iterable> dimensions + ) { return getNamedMetric(meterName, MetricBuilder.METERS, dimensions); } @Override - public Counter getNamedCounter(String counterName) { + public @NotNull Counter getNamedCounter(@NotNull String counterName) { return getNamedCounter(counterName, (Iterable>)null); } @SafeVarargs - public final Counter getNamedCounter(String counterName, Pair... dimensions) { + public final @NotNull Counter getNamedCounter( + @NotNull String counterName, + @Nullable Pair... dimensions + ) { return getNamedCounter(counterName, convertDimensionsToList(dimensions)); } - public Counter getNamedCounter(String counterName, Iterable> dimensions) { + public @NotNull Counter getNamedCounter( + @NotNull String counterName, + @Nullable Iterable> dimensions + ) { return getNamedMetric(counterName, MetricBuilder.COUNTERS, dimensions); } @Override - public Histogram getNamedHistogram(String histogramName) { + public @NotNull Histogram getNamedHistogram(@NotNull String histogramName) { return getNamedHistogram(histogramName, (Iterable>)null); } @SafeVarargs - public final Histogram getNamedHistogram(String histogramName, Pair... dimensions) { + public final @NotNull Histogram getNamedHistogram( + @NotNull String histogramName, + @Nullable Pair... dimensions + ) { return getNamedHistogram(histogramName, convertDimensionsToList(dimensions)); } - public Histogram getNamedHistogram(String histogramName, Iterable> dimensions) { + public @NotNull Histogram getNamedHistogram( + @NotNull String histogramName, + @Nullable Iterable> dimensions + ) { return getNamedMetric(histogramName, histogramBuilder, dimensions); } - public final M getNamedMetric(String metricName, MetricBuilder builder) { + public final @NotNull M getNamedMetric( + @NotNull String metricName, + @NotNull MetricBuilder builder + ) { return getNamedMetric(metricName, builder, (Iterable>)null); } @SafeVarargs - public final M getNamedMetric( - String metricName, MetricBuilder builder, Pair... dimensions + public final @NotNull M getNamedMetric( + @NotNull String metricName, + @NotNull MetricBuilder builder, + @Nullable Pair... dimensions ) { return getNamedMetric(metricName, builder, convertDimensionsToList(dimensions)); } - public M getNamedMetric( - String metricName, MetricBuilder builder, Iterable> dimensions + public @NotNull M getNamedMetric( + @NotNull String metricName, + @NotNull MetricBuilder builder, + @Nullable Iterable> dimensions ) { BuilderTagger builderTagger = metricMetadata.forBuilder(builder) .withMetricName(metricName); @@ -172,32 +218,36 @@ public M getNamedMetric( } @Override - public M registerNamedMetric(String metricName, M metric) { + public @NotNull M registerNamedMetric(@NotNull String metricName, @NotNull M metric) { return registerNamedMetric(metricName, metric, (Iterable>)null); } @SafeVarargs - public final M registerNamedMetric(String metricName, - M metric, - Pair... dimensions) { + public final @NotNull M registerNamedMetric( + @NotNull String metricName, + @NotNull M metric, + @Nullable Pair... dimensions + ) { return registerNamedMetric(metricName, metric, convertDimensionsToList(dimensions)); } - public M registerNamedMetric(String metricName, - M metric, - Iterable> dimensions) { + public @NotNull M registerNamedMetric( + @NotNull String metricName, + @NotNull M metric, + @Nullable Iterable> dimensions + ) { Tagger metricTagger = metricMetadata.forMetric(metric) .withMetricName(metricName); addDimensions(metricTagger, dimensions); return metricTagger.register(metricRegistry); } - protected List> convertDimensionsToList(Pair[] dimensions) { + protected List> convertDimensionsToList(@Nullable Pair[] dimensions) { return (dimensions == null) ? null : Arrays.asList(dimensions); } - protected > T addDimensions( - T builder, Iterable> dimensions + protected > @NotNull T addDimensions( + @NotNull T builder, @Nullable Iterable> dimensions ) { if (dimensions == null) return builder; diff --git a/riposte-metrics-codahale-signalfx/src/test/java/com/nike/riposte/metrics/codahale/SignalFxAwareCodahaleMetricsCollectorTest.java b/riposte-metrics-codahale-signalfx/src/test/java/com/nike/riposte/metrics/codahale/SignalFxAwareCodahaleMetricsCollectorTest.java index c1f05591..a1097c5a 100644 --- a/riposte-metrics-codahale-signalfx/src/test/java/com/nike/riposte/metrics/codahale/SignalFxAwareCodahaleMetricsCollectorTest.java +++ b/riposte-metrics-codahale-signalfx/src/test/java/com/nike/riposte/metrics/codahale/SignalFxAwareCodahaleMetricsCollectorTest.java @@ -33,6 +33,7 @@ import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doAnswer; @@ -205,6 +206,70 @@ public void kitchen_sink_constructor_sets_fields_as_expected() { assertThat(cmc.histogramBuilder).isSameAs(histogramBuilderMock); } + private enum NullArgScenario { + NULL_METRIC_REGISTRY( + true, false, false, false, + "registry cannot be null." + ), + NULL_METRIC_METADATA( + false, true, false, false, + "metricMetadata cannot be null." + ), + NULL_TIMER_BUILDER( + false, false, true, false, + "timerBuilder cannot be null." + ), + NULL_HISTORGRAM_BUILDER( + false, false, false, true, + "histogramBuilder cannot be null." + ); + + public final boolean metricRegistryIsNull; + public final boolean metricMetadataIsNull; + public final boolean timerBuilderIsNull; + public final boolean histogramBuilderIsNull; + public final String expectedExceptionMessage; + + NullArgScenario( + boolean metricRegistryIsNull, boolean metricMetadataIsNull, boolean timerBuilderIsNull, + boolean histogramBuilderIsNull, + String expectedExceptionMessage + ) { + this.metricRegistryIsNull = metricRegistryIsNull; + this.metricMetadataIsNull = metricMetadataIsNull; + this.timerBuilderIsNull = timerBuilderIsNull; + this.histogramBuilderIsNull = histogramBuilderIsNull; + this.expectedExceptionMessage = expectedExceptionMessage; + } + } + + @DataProvider(value = { + "NULL_METRIC_REGISTRY", + "NULL_METRIC_METADATA", + "NULL_TIMER_BUILDER", + "NULL_HISTORGRAM_BUILDER", + }) + @Test + public void kitchen_sink_constructor_throws_IllegalArgumentException_when_passed_null_arguments( + NullArgScenario scenario + ) { + // given + MetricRegistry registry = scenario.metricRegistryIsNull ? null : metricRegistryMock; + MetricMetadata metadata = scenario.metricMetadataIsNull ? null : metricMetadataMock; + MetricBuilder timerBuilder = scenario.timerBuilderIsNull ? null : timerBuilderMock; + MetricBuilder histogramBuilder = scenario.histogramBuilderIsNull ? null : histogramBuilderMock; + + // when + Throwable ex = catchThrowable( + () -> new SignalFxAwareCodahaleMetricsCollector(registry, metadata, timerBuilder, histogramBuilder) + ); + + // then + assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(scenario.expectedExceptionMessage); + } + private void verifyMetricCreation( MetricBuilder metricBuilder, BuilderTagger builderTaggerMock, String metricName, M expectedMetricResult, M actualMetricResult diff --git a/riposte-metrics-codahale/build.gradle b/riposte-metrics-codahale/build.gradle index a0549afd..61c80c17 100644 --- a/riposte-metrics-codahale/build.gradle +++ b/riposte-metrics-codahale/build.gradle @@ -7,10 +7,13 @@ dependencies { "io.dropwizard.metrics:metrics-jvm:$codahaleMetricsVersion", "io.dropwizard.metrics:metrics-graphite:$codahaleMetricsVersion" ) - + compileOnly( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", + ) testCompile ( project(":riposte-guice"), project(":riposte-core").sourceSets.test.output, + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", "org.assertj:assertj-core:$assertJVersion", "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", "junit:junit:$junitVersion", diff --git a/riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/CodahaleMetricsCollector.java b/riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/CodahaleMetricsCollector.java index 3b7eba5c..bc240e80 100644 --- a/riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/CodahaleMetricsCollector.java +++ b/riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/CodahaleMetricsCollector.java @@ -11,6 +11,8 @@ import com.codahale.metrics.Timer; import com.codahale.metrics.Timer.Context; +import org.jetbrains.annotations.NotNull; + import java.util.Map; import java.util.concurrent.Callable; import java.util.function.BiConsumer; @@ -29,21 +31,26 @@ @SuppressWarnings("WeakerAccess") public class CodahaleMetricsCollector implements MetricsCollector { - protected final MetricRegistry metricRegistry; + protected final @NotNull MetricRegistry metricRegistry; public CodahaleMetricsCollector() { this(new MetricRegistry()); } - public CodahaleMetricsCollector(MetricRegistry registry) { + public CodahaleMetricsCollector(@NotNull MetricRegistry registry) { + //noinspection ConstantConditions + if (registry == null) { + throw new IllegalArgumentException("registry cannot be null."); + } this.metricRegistry = registry; } - public MetricRegistry getMetricRegistry() { + public @NotNull MetricRegistry getMetricRegistry() { return metricRegistry; } - public CodahaleMetricsCollector registerAll(String prefix, MetricSet metricSet) { + @SuppressWarnings("UnusedReturnValue") + public @NotNull CodahaleMetricsCollector registerAll(String prefix, @NotNull MetricSet metricSet) { for (Map.Entry entry : metricSet.getMetrics().entrySet()) { if (entry.getValue() instanceof MetricSet) { registerAll(prefix + "." + entry.getKey(), (MetricSet) entry.getValue()); @@ -55,28 +62,28 @@ public CodahaleMetricsCollector registerAll(String prefix, MetricSet metricSet) return this; } - public Timer getNamedTimer(String timerName) { + public @NotNull Timer getNamedTimer(@NotNull String timerName) { return metricRegistry.timer(timerName); } - public Meter getNamedMeter(String meterName) { + public @NotNull Meter getNamedMeter(@NotNull String meterName) { return metricRegistry.meter(meterName); } - public Counter getNamedCounter(String counterName) { + public @NotNull Counter getNamedCounter(@NotNull String counterName) { return metricRegistry.counter(counterName); } - public Histogram getNamedHistogram(String histogramName) { + public @NotNull Histogram getNamedHistogram(@NotNull String histogramName) { return metricRegistry.histogram(histogramName); } - public M registerNamedMetric(String metricName, M metric) { + public @NotNull M registerNamedMetric(@NotNull String metricName, @NotNull M metric) { return metricRegistry.register(metricName, metric); } @Override - public R timed(Function f, T arg, String timerName) { + public R timed(@NotNull Function f, T arg, @NotNull String timerName) { final Context context = getNamedTimer(timerName).time(); try { return f.apply(arg); @@ -88,7 +95,7 @@ public R timed(Function f, T arg, String timerName) { } @Override - public void timed(Runnable r, String timerName) { + public void timed(@NotNull Runnable r, @NotNull String timerName) { final Context context = getNamedTimer(timerName).time(); try { r.run(); @@ -99,7 +106,7 @@ public void timed(Runnable r, String timerName) { } @Override - public V timed(Callable c, String timerName) throws Exception { + public V timed(@NotNull Callable c, @NotNull String timerName) throws Exception { final Context context = getNamedTimer(timerName).time(); try { return c.call(); @@ -110,7 +117,7 @@ public V timed(Callable c, String timerName) throws Exception { } @Override - public void timed(Consumer c, T arg, String timerName) { + public void timed(@NotNull Consumer c, T arg, @NotNull String timerName) { final Context context = getNamedTimer(timerName).time(); try { c.accept(arg); @@ -121,7 +128,7 @@ public void timed(Consumer c, T arg, String timerName) { } @Override - public void timed(BiConsumer bc, T arg1, U arg2, String timerName) { + public void timed(@NotNull BiConsumer bc, T arg1, U arg2, @NotNull String timerName) { final Context context = getNamedTimer(timerName).time(); try { bc.accept(arg1, arg2); @@ -132,7 +139,7 @@ public void timed(BiConsumer bc, T arg1, U arg2, String timerName) } @Override - public R timed(BiFunction bf, T arg1, U arg2, String timerName) { + public R timed(@NotNull BiFunction bf, T arg1, U arg2, @NotNull String timerName) { final Context context = getNamedTimer(timerName).time(); try { return bf.apply(arg1, arg2); @@ -144,7 +151,7 @@ public R timed(BiFunction bf, T arg1, U arg2, String timerNam } @Override - public void metered(Runnable r, String meterName, long events) { + public void metered(@NotNull Runnable r, @NotNull String meterName, long events) { final Meter meter = getNamedMeter(meterName); try { r.run(); @@ -155,7 +162,7 @@ public void metered(Runnable r, String meterName, long events) { } @Override - public V metered(Callable c, String meterName, long events) throws Exception { + public V metered(@NotNull Callable c, @NotNull String meterName, long events) throws Exception { final Meter meter = getNamedMeter(meterName); try { return c.call(); @@ -166,7 +173,7 @@ public V metered(Callable c, String meterName, long events) throws Except } @Override - public R metered(Function f, T arg, String meterName, long events) { + public R metered(@NotNull Function f, T arg, @NotNull String meterName, long events) { final Meter meter = getNamedMeter(meterName); try { return f.apply(arg); @@ -177,7 +184,7 @@ public R metered(Function f, T arg, String meterName, long events) } @Override - public void metered(Consumer c, T arg, String meterName, long events) { + public void metered(@NotNull Consumer c, T arg, @NotNull String meterName, long events) { final Meter meter = getNamedMeter(meterName); try { c.accept(arg); @@ -188,7 +195,7 @@ public void metered(Consumer c, T arg, String meterName, long events) { } @Override - public void metered(BiConsumer bc, T arg1, U arg2, String meterName, long events) { + public void metered(@NotNull BiConsumer bc, T arg1, U arg2, @NotNull String meterName, long events) { final Meter meter = getNamedMeter(meterName); try { bc.accept(arg1, arg2); @@ -199,7 +206,7 @@ public void metered(BiConsumer bc, T arg1, U arg2, String meterName } @Override - public R metered(BiFunction bf, T arg1, U arg2, String meterName, long events) { + public R metered(@NotNull BiFunction bf, T arg1, U arg2, @NotNull String meterName, long events) { final Meter meter = getNamedMeter(meterName); try { return bf.apply(arg1, arg2); @@ -210,7 +217,7 @@ public R metered(BiFunction bf, T arg1, U arg2, String meterN } @Override - public void counted(Runnable r, String counterName, long delta) { + public void counted(@NotNull Runnable r, @NotNull String counterName, long delta) { final Counter counter = getNamedCounter(counterName); try { r.run(); @@ -221,7 +228,7 @@ public void counted(Runnable r, String counterName, long delta) { } @Override - public V counted(Callable c, String counterName, long delta) throws Exception { + public V counted(@NotNull Callable c, @NotNull String counterName, long delta) throws Exception { final Counter counter = getNamedCounter(counterName); try { return c.call(); @@ -232,7 +239,7 @@ public V counted(Callable c, String counterName, long delta) throws Excep } @Override - public R counted(Function f, T arg, String counterName, long delta) { + public R counted(@NotNull Function f, T arg, @NotNull String counterName, long delta) { final Counter counter = getNamedCounter(counterName); try { return f.apply(arg); @@ -243,7 +250,7 @@ public R counted(Function f, T arg, String counterName, long delta) } @Override - public void counted(Consumer c, T arg, String counterName, long delta) { + public void counted(@NotNull Consumer c, T arg, @NotNull String counterName, long delta) { final Counter counter = getNamedCounter(counterName); try { c.accept(arg); @@ -255,7 +262,7 @@ public void counted(Consumer c, T arg, String counterName, long delta) { } @Override - public void counted(BiConsumer bc, T arg1, U arg2, String counterName, long delta) { + public void counted(@NotNull BiConsumer bc, T arg1, U arg2, @NotNull String counterName, long delta) { final Counter counter = getNamedCounter(counterName); try { bc.accept(arg1, arg2); @@ -266,7 +273,7 @@ public void counted(BiConsumer bc, T arg1, U arg2, String counterNa } @Override - public R counted(BiFunction bf, T arg1, U arg2, String counterName, long delta) { + public R counted(@NotNull BiFunction bf, T arg1, U arg2, @NotNull String counterName, long delta) { final Counter counter = getNamedCounter(counterName); try { return bf.apply(arg1, arg2); @@ -276,7 +283,7 @@ public R counted(BiFunction bf, T arg1, U arg2, String counte } } - protected static void processCounter(Counter counter, long delta) { + protected static void processCounter(@NotNull Counter counter, long delta) { if (delta > 0) { counter.inc(delta); } diff --git a/riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/CodahaleMetricsListener.java b/riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/CodahaleMetricsListener.java index 34e2f4c0..3fd2d2b7 100644 --- a/riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/CodahaleMetricsListener.java +++ b/riposte-metrics-codahale/src/main/java/com/nike/riposte/metrics/codahale/CodahaleMetricsListener.java @@ -19,6 +19,8 @@ import com.codahale.metrics.Reservoir; import com.codahale.metrics.SlidingTimeWindowReservoir; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -242,7 +244,7 @@ protected void addServerConfigMetrics(ServerConfig config) { } @Override - public void onEvent(ServerMetricsEvent event, Object value) { + public void onEvent(@NotNull ServerMetricsEvent event, @Nullable Object value) { try { if (ServerMetricsEvent.REQUEST_RECEIVED.equals(event)) { inflightRequests.inc(); diff --git a/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/MetricsComponentTest.java b/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/MetricsComponentTest.java index 26abc703..0c761110 100644 --- a/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/MetricsComponentTest.java +++ b/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/MetricsComponentTest.java @@ -20,6 +20,8 @@ import com.codahale.metrics.Timer; import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -343,7 +345,7 @@ public MetricsTestConfig() { } @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -353,13 +355,13 @@ public int endpointsPort() { } @Override - public CompletableFuture appInfo() { + public @Nullable CompletableFuture<@Nullable AppInfo> appInfo() { return CompletableFuture .completedFuture(new AppInfoImpl("someappid", "someenvironment", "us-west-2", "jenkins")); } @Override - public MetricsListener metricsListener() { + public @Nullable MetricsListener metricsListener() { return metricsListener; } } @@ -389,9 +391,11 @@ public static class TestNonblockingEndpoint extends StandardEndpoint>> execute(final RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture>> execute( + @NotNull final RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { String nettyWorkerThreadName = Thread.currentThread().getName(); //nettyWorkerThreadsUsed.add(nettyWorkerThreadName); CompletableFuture>> result = @@ -400,7 +404,7 @@ public CompletableFuture>> execute(final Reques } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(NON_BLOCKING_MATCHING_PATH); } diff --git a/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/CodahaleMetricsCollectorTest.java b/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/CodahaleMetricsCollectorTest.java index 5d3af1da..efdb1f04 100644 --- a/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/CodahaleMetricsCollectorTest.java +++ b/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/CodahaleMetricsCollectorTest.java @@ -7,6 +7,8 @@ import java.util.concurrent.Callable; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -27,6 +29,17 @@ public void setUp() throws Exception { mc = new CodahaleMetricsCollector(); } + @Test + public void constructor_throws_IllegalArgumentException_if_passed_null_MetricRegistry() { + // when + Throwable ex = catchThrowable(() -> new CodahaleMetricsCollector(null)); + + // then + assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("registry cannot be null."); + } + /** * Test method for {@link com.nike.riposte.metrics.codahale.CodahaleMetricsCollector#getMetricRegistry()}. */ diff --git a/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/CodahaleMetricsListenerTest.java b/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/CodahaleMetricsListenerTest.java index 59f3ac71..341215df 100644 --- a/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/CodahaleMetricsListenerTest.java +++ b/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/CodahaleMetricsListenerTest.java @@ -26,6 +26,7 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -118,7 +119,7 @@ public void beforeMethod() { ); @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -965,14 +966,16 @@ public DummyEndpoint(Matcher matcher) { } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return matcher; } @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } } diff --git a/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/impl/EndpointMetricsHandlerDefaultImplTest.java b/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/impl/EndpointMetricsHandlerDefaultImplTest.java index 37096a56..8556e3b9 100644 --- a/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/impl/EndpointMetricsHandlerDefaultImplTest.java +++ b/riposte-metrics-codahale/src/test/java/com/nike/riposte/metrics/codahale/impl/EndpointMetricsHandlerDefaultImplTest.java @@ -18,6 +18,7 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -94,7 +95,7 @@ public void beforeMethod() { ); @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @@ -604,14 +605,16 @@ public DummyEndpoint(Matcher matcher) { } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return matcher; } @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } } diff --git a/riposte-service-registration-eureka/build.gradle b/riposte-service-registration-eureka/build.gradle index f2743844..9febdc87 100644 --- a/riposte-service-registration-eureka/build.gradle +++ b/riposte-service-registration-eureka/build.gradle @@ -5,10 +5,13 @@ dependencies { project(":riposte-core"), "com.netflix.eureka:eureka-client:$eurekaClientVersion" ) - + compileOnly( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", + ) testCompile ( project(":riposte-guice"), project(":riposte-core").sourceSets.test.output, + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", "org.assertj:assertj-core:$assertJVersion", "junit:junit:$junitVersion", "org.mockito:mockito-core:$mockitoVersion", diff --git a/riposte-service-registration-eureka/src/main/java/com/nike/riposte/serviceregistration/eureka/EurekaServerHook.java b/riposte-service-registration-eureka/src/main/java/com/nike/riposte/serviceregistration/eureka/EurekaServerHook.java index 16d9bd40..3f2579c4 100644 --- a/riposte-service-registration-eureka/src/main/java/com/nike/riposte/serviceregistration/eureka/EurekaServerHook.java +++ b/riposte-service-registration-eureka/src/main/java/com/nike/riposte/serviceregistration/eureka/EurekaServerHook.java @@ -4,6 +4,7 @@ import com.nike.riposte.server.hooks.PostServerStartupHook; import com.nike.riposte.server.hooks.ServerShutdownHook; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +53,7 @@ public EurekaServerHook(Supplier eurekaIsDisabledPropertySupplier, } @Override - public void executePostServerStartupHook(ServerConfig serverConfig, Channel channel) { + public void executePostServerStartupHook(@NotNull ServerConfig serverConfig, @NotNull Channel channel) { try { // register logger.info("About to register with Eureka"); @@ -65,7 +66,7 @@ public void executePostServerStartupHook(ServerConfig serverConfig, Channel chan } @Override - public void executeServerShutdownHook(ServerConfig serverConfig, Channel channel) { + public void executeServerShutdownHook(@NotNull ServerConfig serverConfig, @NotNull Channel channel) { try { // deregister logger.info("About to de-register with Eureka"); diff --git a/riposte-servlet-api-adapter/build.gradle b/riposte-servlet-api-adapter/build.gradle index 0f703be5..9e49ac2c 100644 --- a/riposte-servlet-api-adapter/build.gradle +++ b/riposte-servlet-api-adapter/build.gradle @@ -5,8 +5,11 @@ dependencies { project(":riposte-spi"), "javax.servlet:javax.servlet-api:$servletApiVersion" ) - + compileOnly( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", + ) testCompile ( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", "junit:junit:$junitVersion", "org.mockito:mockito-core:$mockitoVersion", "org.assertj:assertj-core:$assertJVersion", diff --git a/riposte-spi/build.gradle b/riposte-spi/build.gradle index 6a8e56e3..640ce891 100644 --- a/riposte-spi/build.gradle +++ b/riposte-spi/build.gradle @@ -11,7 +11,7 @@ dependencies { "org.slf4j:log4j-over-slf4j:$slf4jVersion" ) compileOnly( - "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", ) testCompile ( "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", diff --git a/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RequestInfoForLoggingRiposteAdapter.java b/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RequestInfoForLoggingRiposteAdapter.java index e3686043..ef826676 100644 --- a/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RequestInfoForLoggingRiposteAdapter.java +++ b/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RequestInfoForLoggingRiposteAdapter.java @@ -4,6 +4,9 @@ import com.nike.riposte.server.http.RequestInfo; import com.nike.riposte.util.HttpUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -17,10 +20,11 @@ @SuppressWarnings("WeakerAccess") public class RequestInfoForLoggingRiposteAdapter implements RequestInfoForLogging { - private final RequestInfo request; - private Map> headersMapCache; + private final @NotNull RequestInfo request; + private @Nullable Map> headersMapCache; - public RequestInfoForLoggingRiposteAdapter(RequestInfo request) { + public RequestInfoForLoggingRiposteAdapter(@NotNull RequestInfo request) { + //noinspection ConstantConditions if (request == null) throw new IllegalArgumentException("request cannot be null"); @@ -28,22 +32,22 @@ public RequestInfoForLoggingRiposteAdapter(RequestInfo request) { } @Override - public String getRequestUri() { + public @NotNull String getRequestUri() { return request.getPath(); } @Override - public String getRequestHttpMethod() { + public @NotNull String getRequestHttpMethod() { return String.valueOf(request.getMethod()); } @Override - public String getQueryString() { + public @Nullable String getQueryString() { return HttpUtils.extractQueryString(request.getUri()); } @Override - public Map> getHeadersMap() { + public @NotNull Map> getHeadersMap() { if (headersMapCache == null) { Map> headersMap = new HashMap<>(); @@ -64,22 +68,31 @@ public Map> getHeadersMap() { } @Override - public String getHeader(String headerName) { + public @Nullable String getHeader(String headerName) { return request.getHeaders().get(headerName); } @Override - public List getHeaders(String headerName) { + public @Nullable List getHeaders(String headerName) { return getHeadersMap().get(headerName); } @Override - public Object getAttribute(String key) { + public @Nullable Object getAttribute(String key) { return null; } @Override - public String getBody() throws GetBodyException { - return request.getRawContent(); + public @NotNull String getBody() throws GetBodyException { + try { + String result = request.getRawContent(); + if (result == null) { + result = ""; + } + return result; + } + catch (Exception ex) { + throw new GetBodyException("An error occurred while trying to extract the request body.", ex); + } } } \ No newline at end of file diff --git a/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RiposteApiExceptionHandler.java b/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RiposteApiExceptionHandler.java index a124b9fc..e4ec466c 100644 --- a/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RiposteApiExceptionHandler.java +++ b/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RiposteApiExceptionHandler.java @@ -16,6 +16,9 @@ import com.nike.riposte.server.error.handler.RiposteErrorHandler; import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.Collection; import java.util.List; @@ -32,14 +35,14 @@ public class RiposteApiExceptionHandler extends ApiExceptionHandlerBase apiExceptionHandlerListenerList, - ApiExceptionHandlerUtils utils) { + public RiposteApiExceptionHandler(@NotNull ProjectApiErrors projectApiErrors, + @NotNull List apiExceptionHandlerListenerList, + @NotNull ApiExceptionHandlerUtils utils) { super(projectApiErrors, apiExceptionHandlerListenerList, utils); } @Override - public ErrorResponseInfo maybeHandleError(Throwable error, RequestInfo requestInfo) + public @Nullable ErrorResponseInfo maybeHandleError(@NotNull Throwable error, @NotNull RequestInfo requestInfo) throws UnexpectedMajorErrorHandlingError { try { com.nike.backstopper.handler.ErrorResponseInfo backstopperErrorResponseInfo = @@ -56,11 +59,13 @@ public ErrorResponseInfo maybeHandleError(Throwable error, RequestInfo reques } @Override - protected ErrorResponseBody prepareFrameworkRepresentation(DefaultErrorContractDTO errorContractDTO, - int httpStatusCode, - Collection rawFilteredApiErrors, - Throwable originalException, - RequestInfoForLogging request) { + protected @NotNull ErrorResponseBody prepareFrameworkRepresentation( + @NotNull DefaultErrorContractDTO errorContractDTO, + int httpStatusCode, + @NotNull Collection rawFilteredApiErrors, + @NotNull Throwable originalException, + @NotNull RequestInfoForLogging request + ) { return new ErrorResponseBodyImpl(errorContractDTO); } diff --git a/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RiposteUnhandledExceptionHandler.java b/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RiposteUnhandledExceptionHandler.java index 737ac896..dbc9f929 100644 --- a/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RiposteUnhandledExceptionHandler.java +++ b/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/RiposteUnhandledExceptionHandler.java @@ -13,6 +13,8 @@ import com.nike.riposte.server.error.handler.RiposteUnhandledErrorHandler; import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; + import java.util.Collection; import java.util.Collections; import java.util.List; @@ -36,25 +38,32 @@ public class RiposteUnhandledExceptionHandler extends UnhandledExceptionHandlerB protected final int genericServiceErrorHttpStatusCode; @Inject - public RiposteUnhandledExceptionHandler(ProjectApiErrors projectApiErrors, ApiExceptionHandlerUtils utils) { + public RiposteUnhandledExceptionHandler( + @NotNull ProjectApiErrors projectApiErrors, + @NotNull ApiExceptionHandlerUtils utils + ) { super(projectApiErrors, utils); singletonGenericServiceError = Collections.singleton(projectApiErrors.getGenericServiceError()); genericServiceErrorHttpStatusCode = projectApiErrors.getGenericServiceError().getHttpStatusCode(); } @Override - protected ErrorResponseBody prepareFrameworkRepresentation(DefaultErrorContractDTO errorContractDTO, - int httpStatusCode, - Collection rawFilteredApiErrors, - Throwable originalException, - RequestInfoForLogging request) { + protected @NotNull ErrorResponseBody prepareFrameworkRepresentation( + @NotNull DefaultErrorContractDTO errorContractDTO, + int httpStatusCode, + @NotNull Collection rawFilteredApiErrors, + @NotNull Throwable originalException, + @NotNull RequestInfoForLogging request + ) { return new ErrorResponseBodyImpl(errorContractDTO); } @Override protected com.nike.backstopper.handler.ErrorResponseInfo generateLastDitchFallbackErrorResponseInfo( - Throwable ex, RequestInfoForLogging request, String errorUid, - Map> headersForResponseWithErrorUid + @NotNull Throwable ex, + @NotNull RequestInfoForLogging request, + @NotNull String errorUid, + @NotNull Map> headersForResponseWithErrorUid ) { return new com.nike.backstopper.handler.ErrorResponseInfo<>( genericServiceErrorHttpStatusCode, @@ -64,7 +73,7 @@ protected com.nike.backstopper.handler.ErrorResponseInfo gene } @Override - public ErrorResponseInfo handleError(Throwable error, RequestInfo requestInfo) { + public @NotNull ErrorResponseInfo handleError(@NotNull Throwable error, @NotNull RequestInfo requestInfo) { com.nike.backstopper.handler.ErrorResponseInfo backstopperErrorResponseInfo = handleException(error, new RequestInfoForLoggingRiposteAdapter(requestInfo)); diff --git a/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/config/BackstopperRiposteConfigHelper.java b/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/config/BackstopperRiposteConfigHelper.java index 135c8942..8e230739 100644 --- a/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/config/BackstopperRiposteConfigHelper.java +++ b/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/config/BackstopperRiposteConfigHelper.java @@ -13,6 +13,8 @@ import com.nike.riposte.server.error.handler.RiposteErrorHandler; import com.nike.riposte.server.error.handler.RiposteUnhandledErrorHandler; +import org.jetbrains.annotations.NotNull; + import java.util.Arrays; import java.util.List; @@ -32,8 +34,10 @@ protected BackstopperRiposteConfigHelper() { /* do nothing */ } * Returns a {@link RiposteErrorHandler} that uses the given {@link ProjectApiErrors}, and {@link * #defaultHandlerListeners(ProjectApiErrors, ApiExceptionHandlerUtils)} for the error handler listeners. */ - public static RiposteErrorHandler defaultErrorHandler(ProjectApiErrors projectApiErrors, - ApiExceptionHandlerUtils utils) { + public static @NotNull RiposteErrorHandler defaultErrorHandler( + @NotNull ProjectApiErrors projectApiErrors, + @NotNull ApiExceptionHandlerUtils utils + ) { return new RiposteApiExceptionHandler( projectApiErrors, defaultHandlerListeners(projectApiErrors, utils), @@ -44,8 +48,10 @@ public static RiposteErrorHandler defaultErrorHandler(ProjectApiErrors projectAp /** * Returns a {@link RiposteUnhandledErrorHandler} that uses the given {@link ProjectApiErrors}. */ - public static RiposteUnhandledErrorHandler defaultUnhandledErrorHandler(ProjectApiErrors projectApiErrors, - ApiExceptionHandlerUtils utils) { + public static @NotNull RiposteUnhandledErrorHandler defaultUnhandledErrorHandler( + @NotNull ProjectApiErrors projectApiErrors, + @NotNull ApiExceptionHandlerUtils utils + ) { return new RiposteUnhandledExceptionHandler(projectApiErrors, utils); } @@ -53,8 +59,10 @@ public static RiposteUnhandledErrorHandler defaultUnhandledErrorHandler(ProjectA * Returns the default list of {@link ApiExceptionHandlerListener}s that should work for most applications without * any further additions. */ - public static List defaultHandlerListeners(ProjectApiErrors projectApiErrors, - ApiExceptionHandlerUtils utils) { + public static @NotNull List defaultHandlerListeners( + @NotNull ProjectApiErrors projectApiErrors, + @NotNull ApiExceptionHandlerUtils utils + ) { return Arrays.asList(new GenericApiExceptionHandlerListener(), new ServersideValidationErrorHandlerListener(projectApiErrors, utils), new ClientDataValidationErrorHandlerListener(projectApiErrors, utils), diff --git a/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/listener/impl/BackstopperRiposteFrameworkErrorHandlerListener.java b/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/listener/impl/BackstopperRiposteFrameworkErrorHandlerListener.java index acd56bee..d1707410 100644 --- a/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/listener/impl/BackstopperRiposteFrameworkErrorHandlerListener.java +++ b/riposte-spi/src/main/java/com/nike/backstopper/handler/riposte/listener/impl/BackstopperRiposteFrameworkErrorHandlerListener.java @@ -32,6 +32,8 @@ import com.nike.riposte.server.error.exception.TooManyOpenChannelsException; import com.nike.riposte.server.error.exception.Unauthorized401Exception; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,22 +64,23 @@ public class BackstopperRiposteFrameworkErrorHandlerListener implements ApiExcep private final Logger logger = LoggerFactory.getLogger(this.getClass()); - public final ApiError CIRCUIT_BREAKER_GENERIC_API_ERROR; - public final ApiError CIRCUIT_BREAKER_OPEN_API_ERROR; - public final ApiError CIRCUIT_BREAKER_TIMEOUT_API_ERROR; + public final @NotNull ApiError CIRCUIT_BREAKER_GENERIC_API_ERROR; + public final @NotNull ApiError CIRCUIT_BREAKER_OPEN_API_ERROR; + public final @NotNull ApiError CIRCUIT_BREAKER_TIMEOUT_API_ERROR; protected final String TOO_LONG_FRAME_LINE_METADATA_MESSAGE = "The request contained a HTTP line that was longer than the maximum allowed"; protected final String TOO_LONG_FRAME_HEADER_METADATA_MESSAGE = "The combined size of the request's HTTP headers was more than the maximum allowed"; - protected final ApiError TOO_LONG_FRAME_LINE_API_ERROR_BASE; - protected final ApiError TOO_LONG_FRAME_HEADER_API_ERROR_BASE; + protected final @NotNull ApiError TOO_LONG_FRAME_LINE_API_ERROR_BASE; + protected final @NotNull ApiError TOO_LONG_FRAME_HEADER_API_ERROR_BASE; - protected final ProjectApiErrors projectApiErrors; + protected final @NotNull ProjectApiErrors projectApiErrors; @Inject - public BackstopperRiposteFrameworkErrorHandlerListener(ProjectApiErrors projectApiErrors) { + public BackstopperRiposteFrameworkErrorHandlerListener(@NotNull ProjectApiErrors projectApiErrors) { + //noinspection ConstantConditions if (projectApiErrors == null) throw new IllegalArgumentException("ProjectApiErrors cannot be null"); @@ -336,8 +339,8 @@ public ApiExceptionHandlerListenerResult shouldHandleException(Throwable ex) { } @SafeVarargs - protected final List> withBaseExceptionMessage( - Throwable ex, Pair... extraLogMessages + protected final @NotNull List> withBaseExceptionMessage( + @NotNull Throwable ex, @Nullable Pair... extraLogMessages ) { List> logPairs = new ArrayList<>(); ApiExceptionHandlerUtils.DEFAULT_IMPL.addBaseExceptionMessageToExtraDetailsForLogging(ex, logPairs); @@ -347,14 +350,14 @@ protected final List> withBaseExceptionMessage( return logPairs; } - protected final Pair causeDetailsForLogs(Throwable orig) { + protected final @NotNull Pair causeDetailsForLogs(@NotNull Throwable orig) { Throwable cause = orig.getCause(); String causeDetails = (cause == null) ? "NO_CAUSE" : cause.toString(); return Pair.of("exception_cause_details", ApiExceptionHandlerUtils.DEFAULT_IMPL.quotesToApostrophes(causeDetails)); } - protected ApiError generateTooLongFrameApiError(TooLongFrameException ex) { + protected @NotNull ApiError generateTooLongFrameApiError(@NotNull TooLongFrameException ex) { String exMessage = String.valueOf(ex.getMessage()); Integer tooLongFrameMaxSize = extractTooLongFrameMaxSizeFromExceptionMessage(ex); Map maxSizeMetadata = new HashMap<>(); @@ -379,7 +382,7 @@ protected ApiError generateTooLongFrameApiError(TooLongFrameException ex) { ); } - private Integer extractTooLongFrameMaxSizeFromExceptionMessage(TooLongFrameException ex) { + private @Nullable Integer extractTooLongFrameMaxSizeFromExceptionMessage(@NotNull TooLongFrameException ex) { String exMessage = ex.getMessage(); if (exMessage == null || !exMessage.endsWith(" bytes.")) { @@ -399,11 +402,11 @@ private Integer extractTooLongFrameMaxSizeFromExceptionMessage(TooLongFrameExcep } } - protected SortedApiErrorSet singletonError(ApiError apiError) { + protected @NotNull SortedApiErrorSet singletonError(@NotNull ApiError apiError) { return new SortedApiErrorSet(Collections.singleton(apiError)); } - protected ApiError getApiErrorForCircuitBreakerException(CircuitBreakerException cbe) { + protected @NotNull ApiError getApiErrorForCircuitBreakerException(@NotNull CircuitBreakerException cbe) { if (cbe instanceof CircuitBreakerOpenException) return CIRCUIT_BREAKER_OPEN_API_ERROR; else if (cbe instanceof CircuitBreakerTimeoutException) diff --git a/riposte-spi/src/main/java/com/nike/backstopper/model/riposte/ErrorResponseBodyImpl.java b/riposte-spi/src/main/java/com/nike/backstopper/model/riposte/ErrorResponseBodyImpl.java index a6eb2473..fd1d61ff 100644 --- a/riposte-spi/src/main/java/com/nike/backstopper/model/riposte/ErrorResponseBodyImpl.java +++ b/riposte-spi/src/main/java/com/nike/backstopper/model/riposte/ErrorResponseBodyImpl.java @@ -5,6 +5,8 @@ import com.nike.backstopper.model.DefaultErrorDTO; import com.nike.riposte.server.error.handler.ErrorResponseBody; +import org.jetbrains.annotations.NotNull; + import java.util.Collection; /** @@ -22,20 +24,40 @@ protected ErrorResponseBodyImpl() { super(); } - public ErrorResponseBodyImpl(DefaultErrorContractDTO copy) { - super(copy); + public ErrorResponseBodyImpl(@NotNull DefaultErrorContractDTO copy) { + super(ensureNonNullDtoAndErrorId(copy)); } - public ErrorResponseBodyImpl(String error_id, Collection apiErrors) { + public ErrorResponseBodyImpl(@NotNull String error_id, Collection apiErrors) { super(error_id, apiErrors); + //noinspection ConstantConditions + if (error_id == null) { + throw new IllegalArgumentException("error_id cannot be null."); + } } - public ErrorResponseBodyImpl(String error_id, Collection errorsToCopy, Void passInNullForThisArg) { + public ErrorResponseBodyImpl(@NotNull String error_id, Collection errorsToCopy, Void passInNullForThisArg) { super(error_id, errorsToCopy, passInNullForThisArg); + //noinspection ConstantConditions + if (error_id == null) { + throw new IllegalArgumentException("error_id cannot be null."); + } + } + + private static DefaultErrorContractDTO ensureNonNullDtoAndErrorId(DefaultErrorContractDTO copy) { + if (copy == null) { + throw new IllegalArgumentException("The DefaultErrorContractDTO copy arg cannot be null."); + } + + if (copy.error_id == null) { + throw new IllegalArgumentException("The DefaultErrorContractDTO.error_id value cannot be null."); + } + + return copy; } @Override - public String errorId() { + public @NotNull String errorId() { return error_id; } } diff --git a/riposte-spi/src/main/java/com/nike/backstopper/model/riposte/ErrorResponseInfoImpl.java b/riposte-spi/src/main/java/com/nike/backstopper/model/riposte/ErrorResponseInfoImpl.java index a211ecab..07a563e5 100644 --- a/riposte-spi/src/main/java/com/nike/backstopper/model/riposte/ErrorResponseInfoImpl.java +++ b/riposte-spi/src/main/java/com/nike/backstopper/model/riposte/ErrorResponseInfoImpl.java @@ -3,6 +3,9 @@ import com.nike.riposte.server.error.handler.ErrorResponseBody; import com.nike.riposte.server.error.handler.ErrorResponseInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,7 +21,7 @@ public class ErrorResponseInfoImpl implements ErrorResponseInfo { /** * The response body content for the error that should be sent to the user. */ - public final ErrorResponseBody errorResponseBody; + public final @NotNull ErrorResponseBody errorResponseBody; /** * The HTTP status code that should be returned in the response to the user. This is not automatically registered on * the framework's response - you should set this yourself on the response after you call an error handler. @@ -30,10 +33,15 @@ public class ErrorResponseInfoImpl implements ErrorResponseInfo { * these yourself on the response after you call an error handler. This will never be null - it will be an empty map * if there are no headers to add. */ - public final Map> headersToAddToResponse = new HashMap<>(); + public final @Nullable Map> headersToAddToResponse = new HashMap<>(); - public ErrorResponseInfoImpl(ErrorResponseBody errorResponseBody, int httpStatusCode, - Map> headersToAddToResponse) { + public ErrorResponseInfoImpl(@NotNull ErrorResponseBody errorResponseBody, + int httpStatusCode, + @Nullable Map> headersToAddToResponse) { + //noinspection ConstantConditions + if (errorResponseBody == null) { + throw new IllegalArgumentException("errorResponseBody cannot be null."); + } this.errorResponseBody = errorResponseBody; this.httpStatusCode = httpStatusCode; if (headersToAddToResponse != null) @@ -41,13 +49,14 @@ public ErrorResponseInfoImpl(ErrorResponseBody errorResponseBody, int httpStatus } public ErrorResponseInfoImpl( - com.nike.backstopper.handler.ErrorResponseInfo backstopperErrorResponseInfo) { + @NotNull com.nike.backstopper.handler.ErrorResponseInfo backstopperErrorResponseInfo + ) { this(backstopperErrorResponseInfo.frameworkRepresentationObj, backstopperErrorResponseInfo.httpStatusCode, backstopperErrorResponseInfo.headersToAddToResponse); } @Override - public ErrorResponseBody getErrorResponseBody() { + public @NotNull ErrorResponseBody getErrorResponseBody() { return errorResponseBody; } @@ -57,7 +66,7 @@ public int getErrorHttpStatusCode() { } @Override - public Map> getExtraHeadersToAddToResponse() { + public @Nullable Map> getExtraHeadersToAddToResponse() { return headersToAddToResponse; } } diff --git a/riposte-spi/src/main/java/com/nike/backstopper/service/riposte/BackstopperRiposteValidatorAdapter.java b/riposte-spi/src/main/java/com/nike/backstopper/service/riposte/BackstopperRiposteValidatorAdapter.java index 4f892ad9..4c4e1798 100644 --- a/riposte-spi/src/main/java/com/nike/backstopper/service/riposte/BackstopperRiposteValidatorAdapter.java +++ b/riposte-spi/src/main/java/com/nike/backstopper/service/riposte/BackstopperRiposteValidatorAdapter.java @@ -4,6 +4,9 @@ import com.nike.riposte.server.error.validation.RequestValidator; import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import javax.inject.Inject; import javax.inject.Singleton; @@ -24,12 +27,12 @@ public BackstopperRiposteValidatorAdapter(ClientDataValidationService clientData } @Override - public void validateRequestContent(RequestInfo request) { + public void validateRequestContent(@NotNull RequestInfo request) { clientDataValidationService.validateObjectsFailFast(request.getContent()); } @Override - public void validateRequestContent(RequestInfo request, Class... validationGroups) { + public void validateRequestContent(@NotNull RequestInfo request, @Nullable Class... validationGroups) { clientDataValidationService.validateObjectsWithGroupsFailFast(validationGroups, request.getContent()); } } diff --git a/riposte-spi/src/main/java/com/nike/riposte/metrics/MetricsCollector.java b/riposte-spi/src/main/java/com/nike/riposte/metrics/MetricsCollector.java index 51043cca..193d8fee 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/metrics/MetricsCollector.java +++ b/riposte-spi/src/main/java/com/nike/riposte/metrics/MetricsCollector.java @@ -1,5 +1,7 @@ package com.nike.riposte.metrics; +import org.jetbrains.annotations.NotNull; + import java.util.concurrent.Callable; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -26,7 +28,7 @@ public interface MetricsCollector { * @param timerName * The name of the timer to update with the measurements */ - void timed(Runnable r, String timerName); + void timed(@NotNull Runnable r, @NotNull String timerName); /** * Execute some code and note how long it ran @@ -38,7 +40,7 @@ public interface MetricsCollector { * * @return an instance of V */ - V timed(Callable c, String timerName) throws Exception; + V timed(@NotNull Callable c, @NotNull String timerName) throws Exception; /** * Execute some code and note how long it ran @@ -52,7 +54,7 @@ public interface MetricsCollector { * * @return The results of the function */ - R timed(Function f, T arg, String timerName); + R timed(@NotNull Function f, T arg, @NotNull String timerName); /** * Execute some code and note how long it ran @@ -64,7 +66,7 @@ public interface MetricsCollector { * @param timerName * The name of the timer to update */ - void timed(Consumer c, T arg, String timerName); + void timed(@NotNull Consumer c, T arg, @NotNull String timerName); /** * Execute some code and note how long it ran @@ -78,7 +80,7 @@ public interface MetricsCollector { * @param timerName * The name of the timer to update */ - void timed(BiConsumer bc, T arg1, U arg2, String timerName); + void timed(@NotNull BiConsumer bc, T arg1, U arg2, @NotNull String timerName); /** * Execute some code and note how long it ran @@ -94,7 +96,7 @@ public interface MetricsCollector { * * @return The resutls of processing */ - R timed(BiFunction bf, T arg1, U arg2, String timerName); + R timed(@NotNull BiFunction bf, T arg1, U arg2, @NotNull String timerName); /** * Execute some code and update the specified event rate metric. This invocation represents a single event. @@ -104,7 +106,7 @@ public interface MetricsCollector { * @param meterName * The meter to update */ - default void metered(Runnable r, String meterName) { + default void metered(@NotNull Runnable r, @NotNull String meterName) { metered(r, meterName, 1L); } @@ -118,7 +120,7 @@ default void metered(Runnable r, String meterName) { * @param events * The number of events that this invocation represents */ - void metered(Runnable r, String meterName, long events); + void metered(@NotNull Runnable r, @NotNull String meterName, long events); /** * Execute some code and update the specified event rate metric @@ -132,7 +134,7 @@ default void metered(Runnable r, String meterName) { * * @return The result of processing */ - V metered(Callable c, String meterName, long events) throws Exception; + V metered(@NotNull Callable c, @NotNull String meterName, long events) throws Exception; /** * Execute some code and update the specified event rate metric. This invocation represents a single event @@ -144,7 +146,7 @@ default void metered(Runnable r, String meterName) { * * @return The result of processing */ - default V metered(Callable c, String meterName) throws Exception { + default V metered(@NotNull Callable c, @NotNull String meterName) throws Exception { return metered(c, meterName, 1L); } @@ -162,7 +164,7 @@ default V metered(Callable c, String meterName) throws Exception { * * @return The result of processing */ - R metered(Function f, T arg, String meterName, long events); + R metered(@NotNull Function f, T arg, @NotNull String meterName, long events); /** * Execute some code and update the specified event rate metric. This invocation represents a single event @@ -176,7 +178,7 @@ default V metered(Callable c, String meterName) throws Exception { * * @return The result of processing */ - default R metered(Function f, T arg, String meterName) { + default R metered(@NotNull Function f, T arg, @NotNull String meterName) { return metered(f, arg, meterName, 1L); } @@ -192,7 +194,7 @@ default R metered(Function f, T arg, String meterName) { * @param events * the number of events this invocation represents */ - void metered(Consumer c, T arg, String meterName, long events); + void metered(@NotNull Consumer c, T arg, @NotNull String meterName, long events); /** * Execute some code and update the specified event rate metric. This invocation represents a single event @@ -204,7 +206,7 @@ default R metered(Function f, T arg, String meterName) { * @param meterName * The meter to update */ - default void metered(Consumer c, T arg, String meterName) { + default void metered(@NotNull Consumer c, T arg, @NotNull String meterName) { metered(c, arg, meterName, 1L); } @@ -222,7 +224,7 @@ default void metered(Consumer c, T arg, String meterName) { * @param events * the number of events this invocation represents */ - void metered(BiConsumer bc, T arg1, U arg2, String meterName, long events); + void metered(@NotNull BiConsumer bc, T arg1, U arg2, @NotNull String meterName, long events); /** * Execute some code and update the specified event rate metric. This invocation represents a single event @@ -236,7 +238,7 @@ default void metered(Consumer c, T arg, String meterName) { * @param meterName * The meter to update */ - default void metered(BiConsumer bc, T arg1, U arg2, String meterName) { + default void metered(@NotNull BiConsumer bc, T arg1, U arg2, @NotNull String meterName) { metered(bc, arg1, arg2, meterName, 1L); } @@ -254,7 +256,7 @@ default void metered(BiConsumer bc, T arg1, U arg2, String meterNam * @param events * the number of events this invocation represents */ - R metered(BiFunction bf, T arg1, U arg2, String meterName, long events); + R metered(@NotNull BiFunction bf, T arg1, U arg2, @NotNull String meterName, long events); /** * Execute some code and update the specified event rate metric. This invocation represents a single event @@ -268,7 +270,7 @@ default void metered(BiConsumer bc, T arg1, U arg2, String meterNam * @param meterName * The meter to update */ - default R metered(BiFunction bf, T arg1, U arg2, String meterName) { + default R metered(@NotNull BiFunction bf, T arg1, U arg2, @NotNull String meterName) { return metered(bf, arg1, arg2, meterName, 1L); } @@ -280,7 +282,7 @@ default R metered(BiFunction bf, T arg1, U arg2, String meter * @param counterName * The name of the counter to update */ - default void counted(Runnable r, String counterName) { + default void counted(@NotNull Runnable r, @NotNull String counterName) { counted(r, counterName, 1L); } @@ -294,7 +296,7 @@ default void counted(Runnable r, String counterName) { * @param delta * The amount by which to change the counter */ - void counted(Runnable r, String counterName, long delta); + void counted(@NotNull Runnable r, @NotNull String counterName, long delta); /** * Execute some code and upate the specified counter @@ -308,7 +310,7 @@ default void counted(Runnable r, String counterName) { * * @return The result of processing */ - V counted(Callable c, String counterName, long delta) throws Exception; + V counted(@NotNull Callable c, @NotNull String counterName, long delta) throws Exception; /** * Execute some code and upate the specified counter. This invocation increments the counter by 1 @@ -320,7 +322,7 @@ default void counted(Runnable r, String counterName) { * * @return The result of processing */ - default V counted(Callable c, String meterName) throws Exception { + default V counted(@NotNull Callable c, @NotNull String meterName) throws Exception { return counted(c, meterName, 1L); } @@ -338,7 +340,7 @@ default V counted(Callable c, String meterName) throws Exception { * * @return The result of processing */ - R counted(Function f, T arg, String counterName, long delta); + R counted(@NotNull Function f, T arg, @NotNull String counterName, long delta); /** * Execute some code and update the specified counter. This invocation increments the counter by 1 @@ -352,7 +354,7 @@ default V counted(Callable c, String meterName) throws Exception { * * @return The result of processing */ - default R counted(Function f, T arg, String counterName) { + default R counted(@NotNull Function f, T arg, @NotNull String counterName) { return counted(f, arg, counterName, 1L); } @@ -368,7 +370,7 @@ default R counted(Function f, T arg, String counterName) { * @param delta * the amount by which to udpate the counter */ - void counted(Consumer c, T arg, String counterName, long delta); + void counted(@NotNull Consumer c, T arg, @NotNull String counterName, long delta); /** * Execute some code and update the specified counter. This invocation increments the counter by 1 @@ -380,7 +382,7 @@ default R counted(Function f, T arg, String counterName) { * @param counterName * The name of the counter to update */ - default void counted(Consumer c, T arg, String counterName) { + default void counted(@NotNull Consumer c, T arg, @NotNull String counterName) { counted(c, arg, counterName, 1L); } @@ -398,7 +400,7 @@ default void counted(Consumer c, T arg, String counterName) { * @param delta * the amount by which to udpate the counter */ - void counted(BiConsumer bc, T arg1, U arg2, String counterName, long delta); + void counted(@NotNull BiConsumer bc, T arg1, U arg2, @NotNull String counterName, long delta); /** * Execute some code and update the specified counter. The invocation increments the counter by 1 @@ -412,7 +414,7 @@ default void counted(Consumer c, T arg, String counterName) { * @param counterName * The name of the counter to update */ - default void counted(BiConsumer bc, T arg1, U arg2, String counterName) { + default void counted(@NotNull BiConsumer bc, T arg1, U arg2, @NotNull String counterName) { counted(bc, arg1, arg2, counterName, 1L); } @@ -432,7 +434,7 @@ default void counted(BiConsumer bc, T arg1, U arg2, String counterN * * @return The result of processing */ - R counted(BiFunction bf, T arg1, U arg2, String counterName, long delta); + R counted(@NotNull BiFunction bf, T arg1, U arg2, @NotNull String counterName, long delta); /** * Execute some code and update the specified counter. This invocation increments the counter by 1 @@ -448,7 +450,7 @@ default void counted(BiConsumer bc, T arg1, U arg2, String counterN * * @return The result of processing */ - default R counted(BiFunction bf, T arg1, U arg2, String counterName) { + default R counted(@NotNull BiFunction bf, T arg1, U arg2, @NotNull String counterName) { return counted(bf, arg1, arg2, counterName, 1L); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/metrics/MetricsListener.java b/riposte-spi/src/main/java/com/nike/riposte/metrics/MetricsListener.java index 0c374a02..ac20da4c 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/metrics/MetricsListener.java +++ b/riposte-spi/src/main/java/com/nike/riposte/metrics/MetricsListener.java @@ -2,6 +2,9 @@ import com.nike.riposte.server.metrics.ServerMetricsEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + /** * Interface for handling {@link ServerMetricsEvent}s. Concrete implementations should track and report on metrics * around general server statistics and health (e.g. counters for inflight, processed, and failed requests, histograms @@ -21,5 +24,5 @@ public interface MetricsListener { * @param value This should be a {@code HttpProcessingState} object, but may be null depending what happened during * the request. */ - void onEvent(ServerMetricsEvent event, Object value); + void onEvent(@NotNull ServerMetricsEvent event, @Nullable Object value); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/config/AppInfo.java b/riposte-spi/src/main/java/com/nike/riposte/server/config/AppInfo.java index 2c376818..3a682698 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/config/AppInfo.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/config/AppInfo.java @@ -1,5 +1,7 @@ package com.nike.riposte.server.config; +import org.jetbrains.annotations.NotNull; + /** * Interface for app & instance info required for metrics and healthcheck/service registry. See {@link * com.nike.riposte.server.config.impl.AppInfoImpl} for a basic DTO style implementation and static helper methods for @@ -15,25 +17,26 @@ public interface AppInfo { String UNKNOWN_VALUE = "unknown"; /** - * @return the AppId/name for this service (like activity). This should always be a valid value - never {@link - * #UNKNOWN_VALUE}. + * @return the AppId/name for this service (like {@code foo-svc}). This should always be a valid value - never + * {@link #UNKNOWN_VALUE} if at all possible. */ - String appId(); + @NotNull String appId(); /** - * @return the environment for the AppId (like test or prod). This should always be a valid value - never {@link - * #UNKNOWN_VALUE}. + * @return the environment for the AppId (like {@code test} or {@code prod}). This should always be a valid + * value - never {@link #UNKNOWN_VALUE} if at all possible. */ - String environment(); + @NotNull String environment(); /** - * @return the datacenter/region for the AppId (like us-west-2). + * @return the datacenter/region for the AppId (like {@code us-west-2}), or {@link #UNKNOWN_VALUE} if the + * datacenter/region could not be determined. */ - - String dataCenter(); + @NotNull String dataCenter(); /** - * @return the instanceId/ip/hostname of this machine/VM running the AppId. + * @return the instanceId/ip/hostname of this machine/VM running the AppId service, or {@link #UNKNOWN_VALUE} if + * the instanceId/ip/hostname could not be determined. */ - String instanceId(); + @NotNull String instanceId(); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/config/ServerConfig.java b/riposte-spi/src/main/java/com/nike/riposte/server/config/ServerConfig.java index 27e390e0..d4d53056 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/config/ServerConfig.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/config/ServerConfig.java @@ -25,6 +25,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.security.cert.CertificateException; import java.util.Collection; import java.util.List; @@ -85,7 +88,7 @@ public interface ServerConfig { /** * @return The {@link Endpoint}s that should be registered for this application. */ - Collection> appEndpoints(); + @NotNull Collection<@NotNull Endpoint> appEndpoints(); /** * @return The list of {@link RequestAndResponseFilter}s that should be applied to requests for this application. @@ -96,7 +99,7 @@ public interface ServerConfig { * information on what these filters can do and how to use them. This method can safely return null if you have no * filters for your application. */ - default List requestAndResponseFilters() { + default @Nullable List<@NotNull RequestAndResponseFilter> requestAndResponseFilters() { return null; } @@ -111,7 +114,7 @@ default List requestAndResponseFilters() { * app. In practice this usually means copy/pasting this method and simply supplying the correct {@link * ProjectApiErrors} for the app. The rest is usually fine for defaults. */ - default RiposteErrorHandler riposteErrorHandler() { + default @NotNull RiposteErrorHandler riposteErrorHandler() { ProjectApiErrors projectApiErrors = new SampleProjectApiErrorsBase() { @Override protected List getProjectSpecificApiErrors() { @@ -138,7 +141,7 @@ protected ProjectSpecificErrorCodeRange getProjectSpecificErrorCodeRange() { * app. In practice this usually means copy/pasting this method and simply supplying the correct {@link * ProjectApiErrors} for the app. The rest is usually fine for defaults. */ - default RiposteUnhandledErrorHandler riposteUnhandledErrorHandler() { + default @NotNull RiposteUnhandledErrorHandler riposteUnhandledErrorHandler() { ProjectApiErrors projectApiErrors = new SampleProjectApiErrorsBase() { @Override protected List getProjectSpecificApiErrors() { @@ -160,19 +163,21 @@ protected ProjectSpecificErrorCodeRange getProjectSpecificErrorCodeRange() { * to the caller. This can safely be null - if this is null then a default serializer will be chosen for you, but if * you want custom serialization you can override this method and return whatever you want. */ - default ErrorResponseBodySerializer errorResponseBodySerializer() { + default @Nullable ErrorResponseBodySerializer errorResponseBodySerializer() { return null; } /** - * @return A new self-signed SSL certificate. + * @return A new self-signed SSL certificate by default. Override this to use a custom SSL/TLS context. + * NOTE: In order for Riposte to use this, {@link #isEndpointsUseSsl()} must return true. If you don't need + * SSL/TLS support, then this method can safely return null. * * @throws SSLException * if there is a problem creating the {@link SslContext}. * @throws CertificateException * if there is a problem creating the certificate used by {@link SslContext}. */ - default SslContext createSslContext() throws SSLException, CertificateException { + default @Nullable SslContext createSslContext() throws SSLException, CertificateException { SelfSignedCertificate ssc = new SelfSignedCertificate("localhost"); return SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); } @@ -192,7 +197,7 @@ default SslContext createSslContext() throws SSLException, CertificateException * You'll need to have a JSR 303 validator implementation in your classpath (e.g. the * org.hibernate:hibernate-validator library, which is the reference impl for JSR 303) for that code to work. */ - default RequestValidator requestContentValidationService() { + default @Nullable RequestValidator requestContentValidationService() { return null; } @@ -200,7 +205,7 @@ default RequestValidator requestContentValidationService() { * @return The default deserializer for incoming request content. This can safely be null - if this is null then a * blank empty-constructor {@link ObjectMapper#ObjectMapper()} will be used. */ - default ObjectMapper defaultRequestContentDeserializer() { + default @Nullable ObjectMapper defaultRequestContentDeserializer() { return null; } @@ -208,7 +213,7 @@ default ObjectMapper defaultRequestContentDeserializer() { * @return The default serializer for outgoing response content. This can safely be null - if this is null then a * blank empty-constructor {@link ObjectMapper#ObjectMapper()} will be used. */ - default ObjectMapper defaultResponseContentSerializer() { + default @Nullable ObjectMapper defaultResponseContentSerializer() { return null; } @@ -275,7 +280,7 @@ default int numBossThreads() { * return null if you want to use the default. Default is recommended unless you have a good reason to override and * know what you're doing. */ - default ThreadFactory bossThreadFactory() { + default @Nullable ThreadFactory bossThreadFactory() { return null; } @@ -292,7 +297,7 @@ default int numWorkerThreads() { * return null if you want to use the default. Default is recommended unless you have a good reason to override and * know what you're doing. */ - default ThreadFactory workerThreadFactory() { + default @Nullable ThreadFactory workerThreadFactory() { return null; } @@ -426,7 +431,7 @@ default int responseCompressionThresholdBytes() { * downstream calls, and many databases have non-blocking drivers that work asynchronously via futures. If you want * the maximum scalability for your app then this executor (or the default if this is null) should *NEVER* be used. */ - default Executor longRunningTaskExecutor() { + default @Nullable Executor longRunningTaskExecutor() { return null; } @@ -434,7 +439,7 @@ default Executor longRunningTaskExecutor() { * @return The {@link MetricsListener} that should be used for collecting and reporting Riposte server metrics. This * can be null - if it is null then no Riposte server metrics will be collected. */ - default MetricsListener metricsListener() { + default @Nullable MetricsListener metricsListener() { return null; } @@ -443,7 +448,7 @@ default MetricsListener metricsListener() { * then no access logging will be performed. The default {@link AccessLogger} is fairly robust and extensible, so * this method can simply {@code return new AccessLogger();} for many applications. */ - default AccessLogger accessLogger() { + default @Nullable AccessLogger accessLogger() { return null; } @@ -464,7 +469,7 @@ default AccessLogger accessLogger() { * for creating "local" instances of the {@link AppInfo} interface if you're never going to be deployed in a cloud * environment. */ - default CompletableFuture appInfo() { + default @Nullable CompletableFuture<@Nullable AppInfo> appInfo() { return null; } @@ -472,7 +477,7 @@ default CompletableFuture appInfo() { * @return The list of {@link PostServerStartupHook} that allows you to implement logic that is automatically * executed after the Riposte server is launched. Null is allowed if you have no hooks to execute. */ - default List postServerStartupHooks() { + default @Nullable List<@NotNull PostServerStartupHook> postServerStartupHooks() { return null; } @@ -480,7 +485,7 @@ default List postServerStartupHooks() { * @return The list of {@link PreServerStartupHook} that allows you to implement logic that is automatically * executed before the Riposte server is launched. Null is allowed if you have no hooks to execute. */ - default List preServerStartupHooks() { + default @Nullable List<@NotNull PreServerStartupHook> preServerStartupHooks() { return null; } @@ -488,7 +493,7 @@ default List preServerStartupHooks() { * @return The list of {@link ServerShutdownHook} that allows you to implement logic that is automatically executed * when the Riposte server is shutdown. Null is allowed if you have no hooks to execute. */ - default List serverShutdownHooks() { + default @Nullable List<@NotNull ServerShutdownHook> serverShutdownHooks() { return null; } @@ -496,7 +501,7 @@ default List serverShutdownHooks() { * @return The list of {@link PipelineCreateHook} that allows you to modify the channel pipeline created by Riposte * for handling new channels. Null is allowed if you have no hooks to execute. */ - default List pipelineCreateHooks() { + default @Nullable List<@NotNull PipelineCreateHook> pipelineCreateHooks() { return null; } @@ -505,7 +510,7 @@ default List pipelineCreateHooks() { * want to use the default. Most of the time the default is fine - only override this if you're sure you know what * you want. */ - default ChannelInitializer customChannelInitializer() { + default @Nullable ChannelInitializer customChannelInitializer() { return null; } @@ -513,7 +518,7 @@ default ChannelInitializer customChannelInitializer() { * @return The request security validator that should be used before allowing endpoints to execute, or null if you * have no security validation to do. */ - default RequestSecurityValidator requestSecurityValidator() { + default @Nullable RequestSecurityValidator requestSecurityValidator() { return null; } @@ -522,7 +527,7 @@ default RequestSecurityValidator requestSecurityValidator() { * tracing, or null if you don't have any user ID header keys. Headers are searched in the given list order, with * the first one found winning (in the case where more than one user ID header key was passed at the same time). */ - default List userIdHeaderKeys() { + default @Nullable List<@NotNull String> userIdHeaderKeys() { return null; } @@ -537,7 +542,7 @@ default List userIdHeaderKeys() { *

The default values are 4096 bytes for max initial line length, 8192 bytes for max combined header line length, * and 8192 max chunk size. See the javadocs for {@link HttpRequestDecoderConfig} and its methods for more details. */ - default HttpRequestDecoderConfig httpRequestDecoderConfig() { + default @Nullable HttpRequestDecoderConfig httpRequestDecoderConfig() { return null; } @@ -554,7 +559,7 @@ default HttpRequestDecoderConfig httpRequestDecoderConfig() { * check for this and fail to startup otherwise. See the javadocs for {@link DistributedTracingConfig} for more * details. */ - default DistributedTracingConfig distributedTracingConfig() { + default @Nullable DistributedTracingConfig distributedTracingConfig() { return null; } @@ -575,7 +580,7 @@ interface HttpRequestDecoderConfig { * Statically accessible implementation of the {@link HttpRequestDecoderConfig} interface that returns the * default values. */ - HttpRequestDecoderConfig DEFAULT_IMPL = new HttpRequestDecoderConfig() {}; + @NotNull HttpRequestDecoderConfig DEFAULT_IMPL = new HttpRequestDecoderConfig() {}; /** * Defaults to 4096. Please see the javadocs on {@link io.netty.handler.codec.http.HttpRequestDecoder} for full diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/config/impl/AppInfoImpl.java b/riposte-spi/src/main/java/com/nike/riposte/server/config/impl/AppInfoImpl.java index 6a51770c..d1d3b2fa 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/config/impl/AppInfoImpl.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/config/impl/AppInfoImpl.java @@ -2,6 +2,8 @@ import com.nike.riposte.server.config.AppInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,17 +23,51 @@ public class AppInfoImpl implements AppInfo { // NOTE: Can't be final - would break the ability to do exception unit testing private static LocalHostnameGetter LOCAL_HOSTNAME_GETTER = new LocalHostnameGetter(); - public final String appId; - public final String environment; - public final String dataCenter; - public final String instanceId; + public final @NotNull String appId; + public final @NotNull String environment; + public final @NotNull String dataCenter; + public final @NotNull String instanceId; // Intentionally protected - this is here for deserialization support only. protected AppInfoImpl() { - this(null, null, null, null); + this(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE); } - public AppInfoImpl(String appId, String environment, String dataCenter, String instanceId) { + /** + * Creates a new instance with the given values. + * + * @param appId The AppId/name for this service (like {@code foo-svc}). As per the javadocs for {@link + * AppInfo#appId()} - this should never be null, and should never be {@link #UNKNOWN_VALUE}. + * @param environment The environment for the AppId (like {@code test} or {@code prod}). As per the javadocs for + * {@link AppInfo#environment()} - this should never be null, and should never be {@link #UNKNOWN_VALUE}. + * @param dataCenter The datacenter/region for the AppId (like {@code us-west-2}). You can safely pass null if you + * can't determine this information, and this will default to {@link #UNKNOWN_VALUE}. + * @param instanceId Te instanceId/ip/hostname of this machine/VM running the AppId service. You can safely pass + * null if you can't determine this information, and this will default to {@link #UNKNOWN_VALUE}. + */ + @SuppressWarnings("ConstantConditions") + public AppInfoImpl( + @NotNull String appId, + @NotNull String environment, + @Nullable String dataCenter, + @Nullable String instanceId + ) { + if (appId == null) { + appId = UNKNOWN_VALUE; + } + + if (environment == null) { + environment = UNKNOWN_VALUE; + } + + if (dataCenter == null) { + dataCenter = UNKNOWN_VALUE; + } + + if (instanceId == null) { + instanceId = UNKNOWN_VALUE; + } + this.appId = appId; this.environment = environment; this.dataCenter = dataCenter; @@ -39,22 +75,22 @@ public AppInfoImpl(String appId, String environment, String dataCenter, String i } @Override - public String appId() { + public @NotNull String appId() { return appId; } @Override - public String environment() { + public @NotNull String environment() { return environment; } @Override - public String dataCenter() { + public @NotNull String dataCenter() { return dataCenter; } @Override - public String instanceId() { + public @NotNull String instanceId() { return instanceId; } @@ -66,7 +102,7 @@ public String instanceId() { * ID a different way and call {@link #createLocalInstance(String)} instead, because if {@link #detectAppId()} * returns null then this method will throw an {@link IllegalStateException}. */ - public static AppInfoImpl createLocalInstance() { + public static @NotNull AppInfoImpl createLocalInstance() { String appId = detectAppId(); if (appId == null) { throw new IllegalStateException("Unable to autodetect app ID. Please call createLocalInstance(String) " @@ -92,7 +128,7 @@ protected static String getLocalHostName() throws UnknownHostException { * using {@code InetAddress.getLocalHost().getHostName()} (or {@link AppInfo#UNKNOWN_VALUE} if that threw an * error). */ - public static AppInfoImpl createLocalInstance(String appId) { + public static @NotNull AppInfoImpl createLocalInstance(@NotNull String appId) { String environment = "local"; String datacenter = "local"; @@ -117,7 +153,7 @@ public static AppInfoImpl createLocalInstance(String appId) { *

  • archaius.deployment.applicationId
  • eureka.name
  • NOTE: A return value of null does not * necessarily mean there is no appId, it just means this code doesn't know how to extract it. */ - public static String detectAppId() { + public static @Nullable String detectAppId() { // Attempt to get it from @appId or archaius.deployment.applicationId System properties first - used by Archaius // or any Riposte app that follows the Archaius conventions. String appId = System.getProperty("@appId"); @@ -145,7 +181,7 @@ public static String detectAppId() { * of null does not necessarily mean there is no environment, it just means this code doesn't know how to extract * it. */ - public static String detectEnvironment() { + public static @Nullable String detectEnvironment() { // Attempt to get it from @environment or archaius.deployment.environment System properties first - used by // Archaius or any Riposte app that follows the Archaius conventions. String environment = System.getProperty("@environment"); diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseBody.java b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseBody.java index 41fde875..cbca706d 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseBody.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseBody.java @@ -2,6 +2,9 @@ import com.nike.backstopper.model.riposte.ErrorResponseBodyImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + /** * Represents the response body content for an error response. The only thing strictly required is {@link #errorId()}, * although it's recommended that you have a consistent error contract for all errors. {@link #errorId()} will be @@ -22,15 +25,15 @@ public interface ErrorResponseBody { /** * @return The unique ID associated with this error. This is usually just the string value of a {@link - * java.util.UUID}. + * java.util.UUID}. Should never be null. */ - String errorId(); + @NotNull String errorId(); /** * @return The object that should be serialized into the response body payload, or null if you want a blank/empty * response body payload. */ - default Object bodyToSerialize() { + default @Nullable Object bodyToSerialize() { return this; } } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseBodySerializer.java b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseBodySerializer.java index 1b7f3cda..0e1b64e0 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseBodySerializer.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseBodySerializer.java @@ -1,5 +1,7 @@ package com.nike.riposte.server.error.handler; +import org.jetbrains.annotations.Nullable; + /** * Interface for a class that knows how to serialize a {@link ErrorResponseBody} to a string. * @@ -19,6 +21,6 @@ public interface ErrorResponseBodySerializer { * should be returned to the caller (null will be returned if {@link ErrorResponseBody#bodyToSerialize()} or * the {@code errorResponseBody} parameter itself is null). */ - String serializeErrorResponseBodyToString(ErrorResponseBody errorResponseBody); + @Nullable String serializeErrorResponseBodyToString(@Nullable ErrorResponseBody errorResponseBody); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseInfo.java b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseInfo.java index 986b372d..0c1bc85f 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseInfo.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/ErrorResponseInfo.java @@ -2,6 +2,9 @@ import com.nike.backstopper.model.riposte.ErrorResponseInfoImpl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.List; import java.util.Map; @@ -18,9 +21,11 @@ public interface ErrorResponseInfo { /** - * @return The response body content that should be sent to the user. + * @return The response body content that should be sent to the user. This should never return null, because + * {@link ErrorResponseBody#errorId()} is required - if you want an empty response body payload, then the + * returned {@link ErrorResponseBody#bodyToSerialize()} can be null. */ - ErrorResponseBody getErrorResponseBody(); + @NotNull ErrorResponseBody getErrorResponseBody(); /** * @return The HTTP status code that should be returned to the user with the response. @@ -28,8 +33,9 @@ public interface ErrorResponseInfo { int getErrorHttpStatusCode(); /** - * @return A map of any extra headers that should be added to the response sent to the user. + * @return A map of any extra headers that should be added to the response sent to the user. You can safely return + * null if there are no extra headers to add to the response. */ - Map> getExtraHeadersToAddToResponse(); + @Nullable Map> getExtraHeadersToAddToResponse(); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/RiposteErrorHandler.java b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/RiposteErrorHandler.java index bcd02c14..52d5139b 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/RiposteErrorHandler.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/RiposteErrorHandler.java @@ -4,6 +4,9 @@ import com.nike.riposte.server.error.exception.UnexpectedMajorErrorHandlingError; import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + /** * Interface describing an error handler for Netty that takes in the error that occurred along with some info useful for * logging and returns an {@link ErrorResponseInfo} that can be used to build the response sent to the user (or null if @@ -33,7 +36,7 @@ public interface RiposteErrorHandler { * This should never be thrown - if it is it indicates something major went wrong in the handler and is likely a * bug that should be fixed. */ - ErrorResponseInfo maybeHandleError(Throwable error, RequestInfo requestInfo) + @Nullable ErrorResponseInfo maybeHandleError(@NotNull Throwable error, @NotNull RequestInfo requestInfo) throws UnexpectedMajorErrorHandlingError; } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/RiposteUnhandledErrorHandler.java b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/RiposteUnhandledErrorHandler.java index b3e5a301..b643f98d 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/RiposteUnhandledErrorHandler.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/RiposteUnhandledErrorHandler.java @@ -3,8 +3,10 @@ import com.nike.backstopper.handler.riposte.RiposteUnhandledExceptionHandler; import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; + /** - * Interface describing a "backstop / last chance" error handler for Netty that is guaranteed to handle the given error + * Interface describing a "backstop / last chance" error handler for Riposte that is guaranteed to handle the given error * by returning a generic error response. *

    * You can create your own instance of this class, however it's highly recommended that you just use the prebuilt {@link @@ -26,6 +28,6 @@ public interface RiposteUnhandledErrorHandler { * response back to the user. This should never return null. The various arguments should be used to log as much * info as possible on the request and the error to make debugging easier. */ - ErrorResponseInfo handleError(Throwable error, RequestInfo requestInfo); + @NotNull ErrorResponseInfo handleError(@NotNull Throwable error, @NotNull RequestInfo requestInfo); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/impl/DelegatedErrorResponseBody.java b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/impl/DelegatedErrorResponseBody.java index 0781e70d..11b6730b 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/impl/DelegatedErrorResponseBody.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/error/handler/impl/DelegatedErrorResponseBody.java @@ -2,37 +2,46 @@ import com.nike.riposte.server.error.handler.ErrorResponseBody; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + /** * A basic implementation of {@link ErrorResponseBody} that delegates serialization ({@link #bodyToSerialize()}) to * some other object you specify. * * @author Nic Munroe */ +@SuppressWarnings("WeakerAccess") public class DelegatedErrorResponseBody implements ErrorResponseBody { - protected final String errorId; - protected final Object bodyToSerialize; + protected final @NotNull String errorId; + protected final @Nullable Object bodyToSerialize; /** * Creates a new instance that uses the given errorId for {@link #errorId()} and the given bodyToSerialize for * {@link #bodyToSerialize()}. * * @param errorId This will be used as the {@link #errorId()}. It's recommended that you use a - * {@link java.util.UUID#randomUUID()} for this. - * @param bodyToSerialize This will be used as the {@link #bodyToSerialize()}. + * {@link java.util.UUID#randomUUID()} for this. Cannot be null. + * @param bodyToSerialize This will be used as the {@link #bodyToSerialize()}. Can be null - if you pass null + * then an empty response body will be used. */ - public DelegatedErrorResponseBody(String errorId, Object bodyToSerialize) { + public DelegatedErrorResponseBody(@NotNull String errorId, @Nullable Object bodyToSerialize) { + //noinspection ConstantConditions + if (errorId == null) { + throw new IllegalArgumentException("errorId cannot be null."); + } this.errorId = errorId; this.bodyToSerialize = bodyToSerialize; } @Override - public String errorId() { + public @NotNull String errorId() { return errorId; } @Override - public Object bodyToSerialize() { + public @Nullable Object bodyToSerialize() { return bodyToSerialize; } } \ No newline at end of file diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/error/validation/RequestSecurityValidator.java b/riposte-spi/src/main/java/com/nike/riposte/server/error/validation/RequestSecurityValidator.java index aa058f47..026b3cb9 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/error/validation/RequestSecurityValidator.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/error/validation/RequestSecurityValidator.java @@ -3,6 +3,8 @@ import com.nike.riposte.server.http.Endpoint; import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; + import java.util.Collection; /** @@ -32,14 +34,17 @@ public interface RequestSecurityValidator { String REQUEST_SECURITY_ATTRIBUTE_KEY = "REQUEST_SECURITY_ATTRIBUTE"; /** - * Performs security validation on the given request {@link RequestInfo}, for the given {@link Endpoint}. + * Performs security validation on the given request {@link RequestInfo}, for the given {@link Endpoint}. The + * given {@link Endpoint} will always be found in {@link #endpointsToValidate()}. */ - void validateSecureRequestForEndpoint(RequestInfo requestInfo, Endpoint endpoint); + void validateSecureRequestForEndpoint(@NotNull RequestInfo requestInfo, @NotNull Endpoint endpoint); /** - * The collection of endpoints that should be run through this security validator. + * The collection of endpoints that should be run through this security validator. This should never return null - + * use an empty collection if you have no endpoints to validate (although in that case {@link + * #validateSecureRequestForEndpoint(RequestInfo, Endpoint)} would never be called). */ - Collection> endpointsToValidate(); + @NotNull Collection> endpointsToValidate(); /** * @return true if this security validator is fast enough that {@link #validateSecureRequestForEndpoint(RequestInfo, diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/error/validation/RequestValidator.java b/riposte-spi/src/main/java/com/nike/riposte/server/error/validation/RequestValidator.java index 1c1fe065..13ba60a6 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/error/validation/RequestValidator.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/error/validation/RequestValidator.java @@ -4,6 +4,9 @@ import com.nike.riposte.server.http.Endpoint; import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + /** * Interface for a request validator. Concrete implementations might perform JSR 303 validation, or custom validation * based on specific object types, or anything else your application needs. If an endpoint wants the request content @@ -25,14 +28,21 @@ public interface RequestValidator { /** * Performs default validation on the given request's {@link RequestInfo#getContent()}. If this implementation uses * JSR 303 validation then this method call indicates using the default group. + * + * @param request The request whose {@link RequestInfo#getContent()} needs validating. Should never be null. */ - void validateRequestContent(RequestInfo request); + void validateRequestContent(@NotNull RequestInfo request); /** * Performs validation on the given request's {@link RequestInfo#getContent()} using the given validation groups. If * this implementation uses JSR 303 validation then this method call indicates using the given groups with the * {@code javax.validation.Validator}. + * + * @param request The request whose {@link RequestInfo#getContent()} needs validating. Should never be null. + * @param validationGroups The validation groups to use when validating the given request's content. This can + * safely be null if you have no groups to apply, however in that case you should probably simply call + * {@link #validateRequestContent(RequestInfo)} instead. */ - void validateRequestContent(RequestInfo request, Class... validationGroups); + void validateRequestContent(@NotNull RequestInfo request, @Nullable Class... validationGroups); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PipelineCreateHook.java b/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PipelineCreateHook.java index 97204043..6cdeab2f 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PipelineCreateHook.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PipelineCreateHook.java @@ -1,11 +1,14 @@ package com.nike.riposte.server.hooks; +import org.jetbrains.annotations.NotNull; + import io.netty.channel.ChannelPipeline; /** * Hook for pipeline create events - allows to modify the pipeline before the channel is initialized. */ +@FunctionalInterface public interface PipelineCreateHook { - void executePipelineCreateHook(ChannelPipeline pipeline); + void executePipelineCreateHook(@NotNull ChannelPipeline pipeline); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PostServerStartupHook.java b/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PostServerStartupHook.java index 7f471092..d660aacf 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PostServerStartupHook.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PostServerStartupHook.java @@ -2,12 +2,15 @@ import com.nike.riposte.server.config.ServerConfig; +import org.jetbrains.annotations.NotNull; + import io.netty.channel.Channel; /** * Hook for post server startup events. */ +@FunctionalInterface public interface PostServerStartupHook { - void executePostServerStartupHook(ServerConfig serverConfig, Channel channel); + void executePostServerStartupHook(@NotNull ServerConfig serverConfig, @NotNull Channel channel); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PreServerStartupHook.java b/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PreServerStartupHook.java index aa73336e..c2252889 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PreServerStartupHook.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/hooks/PreServerStartupHook.java @@ -1,11 +1,14 @@ package com.nike.riposte.server.hooks; +import org.jetbrains.annotations.NotNull; + import io.netty.bootstrap.ServerBootstrap; /** * Hook for pre server startup events. */ +@FunctionalInterface public interface PreServerStartupHook { - void executePreServerStartupHook(ServerBootstrap bootstrap); + void executePreServerStartupHook(@NotNull ServerBootstrap bootstrap); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/hooks/ServerShutdownHook.java b/riposte-spi/src/main/java/com/nike/riposte/server/hooks/ServerShutdownHook.java index 65cabecf..6d9ee582 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/hooks/ServerShutdownHook.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/hooks/ServerShutdownHook.java @@ -2,12 +2,15 @@ import com.nike.riposte.server.config.ServerConfig; +import org.jetbrains.annotations.NotNull; + import io.netty.channel.Channel; /** * Hook for server shutdown events. */ +@FunctionalInterface public interface ServerShutdownHook { - void executeServerShutdownHook(ServerConfig serverConfig, Channel channel); + void executeServerShutdownHook(@NotNull ServerConfig serverConfig, @NotNull Channel channel); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/Endpoint.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/Endpoint.java index fad31c97..93320634 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/Endpoint.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/Endpoint.java @@ -7,6 +7,9 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + /** * Interface for an endpoint. Concrete implementations must implement {@link #requestMatcher()} to define what requests * they want to handle. If your endpoint expects request body content (e.g. POST or PUT requests) then you'll probably @@ -29,16 +32,16 @@ public interface Endpoint { * @return The {@link Matcher} for determining whether this endpoint should be called for a given request. Concrete * implementations will usually just call and return {@link Matcher#match(String, * io.netty.handler.codec.http.HttpMethod...)} or one of the other common static construction helper methods in - * {@link Matcher}, although you can do whatever you want if you have custom requirements. + * {@link Matcher}, although you can do whatever you want if you have custom requirements. This cannot return null. */ - Matcher requestMatcher(); + @NotNull Matcher requestMatcher(); /** * @return The overall timeout value in milliseconds that you want for this specific endpoint, or null if you want * to use the app-wide default timeout returned by {@link * ServerConfig#defaultCompletableFutureTimeoutInMillisForNonblockingEndpoints()}. */ - default Long completableFutureTimeoutOverrideMillis() { + default @Nullable Long completableFutureTimeoutOverrideMillis() { // Return null by default so that the app-wide timeout value will be used unless you override this method. return null; } @@ -61,7 +64,7 @@ default Long completableFutureTimeoutOverrideMillis() { * itself works - see the source code of the {@link TypeReference#TypeReference()} constructor for details ({@code * StandardEndpoint} does this if you want a more direct example). */ - default TypeReference requestContentType() { + default @Nullable TypeReference requestContentType() { return null; } @@ -71,7 +74,7 @@ default TypeReference requestContentType() { * method must return true *and* {@link #requestContentType()} must return a non-null value (in order for the * content to be deserialized) if you want validation done on the content. */ - default boolean isValidateRequestContent(@SuppressWarnings("UnusedParameters") RequestInfo request) { + default boolean isValidateRequestContent(@NotNull RequestInfo request) { return true; } @@ -98,7 +101,7 @@ default boolean isRequireRequestContent() { * should performance test your system to make sure this default will work for your payloads, and adjust what this * method returns if needed. */ - default boolean shouldValidateAsynchronously(RequestInfo request) { + default boolean shouldValidateAsynchronously(@NotNull RequestInfo request) { return request.getRawContentLengthInBytes() > 50000; } @@ -107,7 +110,8 @@ default boolean shouldValidateAsynchronously(RequestInfo request) { * endpoint, or null if you just want to use the default validation group. This is primarily used for validating the * same object type in different ways in different situations (e.g. JSR 303 validation groups). */ - default Class[] validationGroups(@SuppressWarnings("UnusedParameters") RequestInfo request) { + @SuppressWarnings("unused") + default @Nullable Class[] validationGroups(@NotNull RequestInfo request) { return null; } @@ -117,8 +121,7 @@ default Class[] validationGroups(@SuppressWarnings("UnusedParameters") Reques * this returns null then the {@link ServerConfig#defaultRequestContentDeserializer()} * will be used. */ - default ObjectMapper customRequestContentDeserializer( - @SuppressWarnings("UnusedParameters") RequestInfo request) { + default @Nullable ObjectMapper customRequestContentDeserializer(@NotNull RequestInfo request) { return null; } @@ -127,7 +130,8 @@ default ObjectMapper customRequestContentDeserializer( * can safely return null - if this returns null then the {@link ServerConfig#defaultResponseContentSerializer()} * will be used. */ - default ObjectMapper customResponseContentSerializer(@SuppressWarnings("UnusedParameters") RequestInfo request) { + @SuppressWarnings("unused") + default @Nullable ObjectMapper customResponseContentSerializer(@NotNull RequestInfo request) { return null; } @@ -135,11 +139,11 @@ default ObjectMapper customResponseContentSerializer(@SuppressWarnings("UnusedPa * @return The max request size in bytes that you want to allow for this specific endpoint, or null if you want to * use the app-wide default max request size returned by {@link ServerConfig#maxRequestSizeInBytes()}. * - * If you would like to disable validation on this endpoint, return 0 or less. + * If you would like to disable max-size validation on this endpoint, return 0 or less. * * If you would like to default to the global configured limit, return null. */ - default Integer maxRequestSizeInBytesOverride() { + default @Nullable Integer maxRequestSizeInBytesOverride() { // Return null by default so that the app-wide max request size will be used unless you override this method. return null; } @@ -152,7 +156,7 @@ default Integer maxRequestSizeInBytesOverride() { * RequestInfo#isCompleteRequestWithAllChunks()} will return false for the given request and all of the * get-content-related methods in the request will return null. */ - default boolean isDecompressRequestPayloadAllowed(@SuppressWarnings("unused") RequestInfo request) { + default boolean isDecompressRequestPayloadAllowed(@NotNull RequestInfo request) { return true; } } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/NonblockingEndpoint.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/NonblockingEndpoint.java index fd738a87..4f95c565 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/NonblockingEndpoint.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/NonblockingEndpoint.java @@ -1,5 +1,8 @@ package com.nike.riposte.server.http; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Supplier; @@ -101,8 +104,11 @@ public interface NonblockingEndpoint extends Endpoint { * pipeline manipulation. Just make sure you know *EXACTLY* what you're doing when you use it, otherwise you could * break your application in subtle ways without even realizing it. */ - CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, - ChannelHandlerContext ctx); + @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ); /** * @param request @@ -116,7 +122,10 @@ CompletableFuture> execute(RequestInfo request, Executor long * special info based on the request you can do so. */ @SuppressWarnings("UnusedParameters") - default Throwable getCustomTimeoutExceptionCause(RequestInfo request, ChannelHandlerContext ctx) { + default @Nullable Throwable getCustomTimeoutExceptionCause( + @NotNull RequestInfo request, + @NotNull ChannelHandlerContext ctx + ) { return null; } } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/RequestInfo.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/RequestInfo.java index fd90ba2d..ef3b5ca8 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/RequestInfo.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/RequestInfo.java @@ -3,6 +3,9 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.nio.charset.Charset; import java.util.List; import java.util.Map; @@ -33,11 +36,10 @@ * * @author Nic Munroe */ -@SuppressWarnings("UnnecessaryInterfaceModifier") public interface RequestInfo { - public static final String NONE_OR_UNKNOWN_TAG = "none_or_unknown"; - public static final Charset DEFAULT_CONTENT_CHARSET = CharsetUtil.UTF_8; + String NONE_OR_UNKNOWN_TAG = "none_or_unknown"; + Charset DEFAULT_CONTENT_CHARSET = CharsetUtil.UTF_8; /** * The full URI associated with this request. This will be the raw, potentially encoded, value sent to the server. @@ -47,7 +49,7 @@ public interface RequestInfo { * * Will never be null - the empty string will be used if no URI information was provided. */ - public String getUri(); + @NotNull String getUri(); /** * The path-only portion of the URI. Will *not* include the query string (use {@link #getUri()} if you want the @@ -59,19 +61,20 @@ public interface RequestInfo { * * Will never be null - the empty string will be used if no path information could be extracted. */ - public String getPath(); + @NotNull String getPath(); /** - * The method associated with this request, or null if no method was provided. + * The method associated with this request. This may be null if some issue prevented a real HTTP method from being + * determined (i.e. due to an invalid request). */ - public HttpMethod getMethod(); + @Nullable HttpMethod getMethod(); /** * The headers associated with this request. There may or may not be {@link #getTrailingHeaders()} associated with * this request as well. Will never be null - an empty {@link DefaultHttpHeaders} will be used if no headers were * provided. */ - public HttpHeaders getHeaders(); + @NotNull HttpHeaders getHeaders(); /** * The trailing headers associated with this request. This will be empty if {@link #isCompleteRequestWithAllChunks} @@ -80,20 +83,21 @@ public interface RequestInfo { * #isCompleteRequestWithAllChunks()} to determine whether this field is empty because we're waiting on all the * content to finish arriving or because the request has no trailing headers associated with it. */ - public HttpHeaders getTrailingHeaders(); + @NotNull HttpHeaders getTrailingHeaders(); /** * The {@link QueryStringDecoder} containing the query parameters associated with this request. Will never be null - * if no {@link QueryStringDecoder} was provided then one will be created based on {@link #getUri()}. */ - public QueryStringDecoder getQueryParams(); + @NotNull QueryStringDecoder getQueryParams(); /** * Helper method for extracting the single query parameter value for the given key from {@link #getQueryParams()}, * or null if no such query parameter is available. If the value for the given key is a multi-value list, then only * the first item in the multi-value list will be returned. */ - public default String getQueryParamSingle(String key) { + default @Nullable String getQueryParamSingle(@NotNull String key) { + //noinspection ConstantConditions if (getQueryParams() == null || getQueryParams().parameters() == null) return null; @@ -116,13 +120,13 @@ public default String getQueryParamSingle(String key) { *

    * Will never be null - an empty map will be used if no path parameters could be determined. */ - public Map getPathParams(); + @NotNull Map getPathParams(); /** * Helper method that is a shortcut for calling {@code getPathParams().get(key)}. Will return null if there's no * path parameter mapping for the given key. */ - public default String getPathParam(String key) { + default @Nullable String getPathParam(@NotNull String key) { return getPathParams().get(key); } @@ -136,8 +140,10 @@ public default String getPathParam(String key) { * For example, if the passed-in path template was "/app/{appId}/user/{userId}" and the actual {@link #getPath()} * for this request was "/app/foo/user/bar", then this method would populate the path parameter map with the * mappings "appId"->"foo", "userId"->"bar", which you could retrieve by calling {@link #getPathParams()}. + * + * @return this instance. */ - public RequestInfo setPathParamsBasedOnPathTemplate(String pathTemplate); + @NotNull RequestInfo setPathParamsBasedOnPathTemplate(@NotNull String pathTemplate); /** * Returns the total size of the raw content in bytes. This will be 0 until {@link #addContentChunk(HttpContent)} @@ -146,7 +152,7 @@ public default String getPathParam(String key) { * is returning 0 because we're waiting on all the content to finish arriving or because the request has no content * associated with it. */ - public int getRawContentLengthInBytes(); + int getRawContentLengthInBytes(); /** * Returns the raw content associated with this request as a byte array. This will be null until {@link @@ -160,7 +166,7 @@ public default String getPathParam(String key) { * conversion process should only happen once, and when it is done this method should call {@link * #releaseContentChunks()} before returning. */ - public byte[] getRawContentBytes(); + @Nullable byte[] getRawContentBytes(); /** * Returns the raw content associated with this request (as retrieved from {@link #getRawContentBytes()}) as a @@ -181,7 +187,7 @@ public default String getPathParam(String key) { * {@link #getMultipartParts()} (if that method was called to lazy-load multipart processing). Therefore you should * only call this method if absolutely necessary in order to keep memory pressure as low as possible. */ - public String getRawContent(); + @Nullable String getRawContent(); /** * IMPORTANT NOTE: THIS WILL RETURN NULL UNTIL {@link #setupContentDeserializer(ObjectMapper, TypeReference)} IS @@ -200,7 +206,7 @@ public default String getPathParam(String key) { * provided. This will be null until {@link #setupContentDeserializer(ObjectMapper, TypeReference)} is called and * {@link #getRawContentBytes()} is non-null. */ - public T getContent(); + @Nullable T getContent(); /** * Returns true if this request is a multipart request, false otherwise. If this is true and {@link @@ -208,7 +214,7 @@ public default String getPathParam(String key) { * io.netty.handler.codec.http.multipart.HttpPostRequestDecoder#isMultipart(HttpRequest)} for details on what * constitutes a multipart request. */ - public boolean isMultipartRequest(); + boolean isMultipartRequest(); /** * Returns the list of multipart data objects if and only if {@link #isMultipartRequest()} is true and {@link @@ -235,7 +241,7 @@ public default String getPathParam(String key) { * io.netty.handler.codec.http.multipart.FileUpload} * *

  • - * {@link InterfaceHttpData.HttpDataType#InternalAttribute}: You can cast the object to a {@link + * {@link InterfaceHttpData.HttpDataType#InternalAttribute}: You can cast the object to a {@code * io.netty.handler.codec.http.multipart.InternalAttribute}, however this should never happen in reality * as this class is for internal use only. *
  • @@ -253,47 +259,53 @@ public default String getPathParam(String key) { * prevent memory leaks. The pipeline will handle this for you automatically, however you can call it yourself * in endpoint code if you know you will never need it again and want to aggressively release resources. */ - public List getMultipartParts(); + @Nullable List getMultipartParts(); /** * Keeps track of the passed-in deserializer and type reference for the purpose of deserializing {@link * #getRawContentBytes()} into the desired object type when {@link #getContent()} is called. This method is called * as part of the default pipeline, so individual endpoints should never need to worry about this, however it's * important to note that this method must be called for {@link #getContent()} to have any shot at deserializing. + * + * @return this instance. */ - public RequestInfo setupContentDeserializer(ObjectMapper deserializer, TypeReference typeReference); + @NotNull RequestInfo setupContentDeserializer( + @NotNull ObjectMapper deserializer, + @NotNull TypeReference typeReference + ); /** * @return true if {@link #setupContentDeserializer(ObjectMapper, TypeReference)} was called and passed valid * deserialization info such that content can be deserialized and returned properly from {@link #getContent()}, * false otherwise. Individual endpoints should never need to worry about this. */ - public boolean isContentDeserializerSetup(); + boolean isContentDeserializerSetup(); /** * The cookies associated with this request. Will never be null - an empty set will be used if no cookie information * was provided. */ - public Set getCookies(); + @NotNull Set getCookies(); /** * The charset used to convert the content chunks (added via {@link #addContentChunk(HttpContent)}) into {@link * #getRawContent()}. This will never be null - {@link #DEFAULT_CONTENT_CHARSET} will be used if a charset could not * be determined from the headers. */ - public Charset getContentCharset(); + @NotNull Charset getContentCharset(); /** - * The protocol version associated with this request, or null if this information was not provided. + * The protocol version associated with this request. This may be null if some issue prevented a real protocol + * version from being determined (i.e. due to an invalid request). */ - public HttpVersion getProtocolVersion(); + @Nullable HttpVersion getProtocolVersion(); /** * Whether or not the request is eligible for a keep-alive connection. This is calculated based on HTTP standards * and is related to both the Connection header and the {@link #getProtocolVersion()}. See {@link * HttpHeaders#isKeepAlive(HttpMessage)} for an example of the logic that is used. */ - public boolean isKeepAliveRequested(); + boolean isKeepAliveRequested(); /** * Adds the given content chunk to the list of chunks this instance is tracking and returns current request size @@ -324,31 +336,33 @@ public default String getPathParam(String key) { * final chunk being added or in the case that the request never causes {@link #getRawContentBytes()} to be called. * Individual endpoints do not need to worry about this issue - it's a problem for the server to solve. */ - public int addContentChunk(HttpContent chunk); + int addContentChunk(@NotNull HttpContent chunk); /** * Returns true if this request represents a 100% complete request with all chunks and trailing headers populated, * or false if this is a partial request and we're still waiting for more chunks. */ - public boolean isCompleteRequestWithAllChunks(); + boolean isCompleteRequestWithAllChunks(); /** * Method to add any object as an attribute (state) for the RequestInfo object. The attributes will have the same - * lifespan as the RequestInfo object. + * lifespan as the RequestInfo object. Do not pass null for either the attribute name or value - if you need + * to clear an attribute, call {@link #getRequestAttributes()} to get the attribute map directly, and then + * call {@link Map#remove(Object)} on the attribute map. * * @param attributeName * name for the object * @param attributeValue * object set as an attribute */ - public void addRequestAttribute(String attributeName, Object attributeValue); + void addRequestAttribute(@NotNull String attributeName, @NotNull Object attributeValue); /** * The map of request attributes for this request info. * * @return The map of attributes, or an empty map when no attributes are set (will never return null). */ - public Map getRequestAttributes(); + @NotNull Map getRequestAttributes(); /** * Calls {@link #releaseContentChunks()}, {@link #releaseMultipartData()}, and releases any other resources held by @@ -357,7 +371,7 @@ public default String getPathParam(String key) { * automatically released without any further code changes. Individual endpoints do not need to worry about this * issue - it's a problem for the server to solve. */ - public void releaseAllResources(); + void releaseAllResources(); /** * Calls {@link ReferenceCountUtil#release(Object)} on all the items in the content chunk list to release any @@ -373,7 +387,7 @@ public default String getPathParam(String key) { * before the final chunk being added or in the case that the request never causes {@link #getRawContentBytes()} to * be called. Individual endpoints do not need to worry about this issue - it's a problem for the server to solve. */ - public void releaseContentChunks(); + void releaseContentChunks(); /** * Calls {@link io.netty.handler.codec.http.multipart.HttpPostMultipartRequestDecoder#destroy()} on the cached @@ -387,7 +401,7 @@ public default String getPathParam(String key) { * application wants to aggressively release resources before the request is fully processed. Individual endpoints * do not need to worry about this issue - it's a problem for the server to solve. */ - public void releaseMultipartData(); + void releaseMultipartData(); /** * This will return the path template set by {@link #setPathParamsBasedOnPathTemplate(String)}, @@ -400,6 +414,6 @@ public default String getPathParam(String key) { *

    Will never be null - an empty string will be used if {@link #setPathParamsBasedOnPathTemplate(String)} * was never called (should only happen in error cases where an endpoint was not called). */ - public String getPathTemplate(); + @NotNull String getPathTemplate(); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/ResponseInfo.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/ResponseInfo.java index 66865aff..0fcda5dc 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/ResponseInfo.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/ResponseInfo.java @@ -3,10 +3,14 @@ import com.nike.riposte.server.http.impl.ChunkedResponseInfo.ChunkedResponseInfoBuilder; import com.nike.riposte.server.http.impl.FullResponseInfo.FullResponseInfoBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.nio.charset.Charset; import java.util.Set; import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.util.CharsetUtil; @@ -18,19 +22,18 @@ * * @author Nic Munroe */ -@SuppressWarnings("UnnecessaryInterfaceModifier") public interface ResponseInfo { /** * The default mime type that should be used by the response sender if {@link #getDesiredContentWriterMimeType()} is * null and the mime type is not specified in the headers. */ - public static final String DEFAULT_MIME_TYPE = "application/json"; + String DEFAULT_MIME_TYPE = "application/json"; /** * The default content encoding charset that should be used if {@link #getDesiredContentWriterEncoding()} is null * and the charset is not specified in the headers. */ - public static final Charset DEFAULT_CONTENT_ENCODING = CharsetUtil.UTF_8; + Charset DEFAULT_CONTENT_ENCODING = CharsetUtil.UTF_8; /** * The HTTP status code to return. Can be null - if this is null then the response sender will use a default value @@ -41,12 +44,12 @@ public interface ResponseInfo { * observers (e.g. {@link com.nike.riposte.server.logging.AccessLogger} or {@link * com.nike.riposte.metrics.MetricsListener}) know what was sent to the user after the fact. */ - public Integer getHttpStatusCode(); + @Nullable Integer getHttpStatusCode(); /** * @return {@link #getHttpStatusCode()}, or the given default if {@link #getHttpStatusCode()} is null. */ - public default int getHttpStatusCodeWithDefault(int defaultIfNull) { + default int getHttpStatusCodeWithDefault(int defaultIfNull) { Integer actual = getHttpStatusCode(); if (actual == null) return defaultIfNull; @@ -58,13 +61,13 @@ public default int getHttpStatusCodeWithDefault(int defaultIfNull) { * Sets the value of the HTTP status code that the response sender should use. See {@link #getHttpStatusCode()} for * more information on how this is used. */ - public void setHttpStatusCode(Integer httpStatusCode); + void setHttpStatusCode(@Nullable Integer httpStatusCode); /** * The response headers to send. This will never be null (it will default to a blank {@link DefaultHttpHeaders} if * necessary). */ - public HttpHeaders getHeaders(); + @NotNull HttpHeaders getHeaders(); /** * Returns true if this response is a chunked response, false if it is a full ready-to-send-everything response. If @@ -72,7 +75,7 @@ public default int getHttpStatusCodeWithDefault(int defaultIfNull) { * content chunks to send to the user as it won't be stored in this object. Otherwise response senders should use * {@link #getContentForFullResponse()}. */ - public boolean isChunkedResponse(); + boolean isChunkedResponse(); /** * The response content for a *FULL* (not chunked) response. Can be null - if this is null then the response writer @@ -81,7 +84,7 @@ public default int getHttpStatusCodeWithDefault(int defaultIfNull) { * ALWAYS CHECK {@link #isChunkedResponse()} BEFORE CALLING THIS METHOD! If {@link #isChunkedResponse()} is * true then this method will throw an {@link IllegalStateException}. */ - public T getContentForFullResponse(); + @Nullable T getContentForFullResponse(); /** * Sets the response content for a *FULL* (not chunked) response. Can be null - if this is null then the response @@ -91,13 +94,14 @@ public default int getHttpStatusCodeWithDefault(int defaultIfNull) { * IllegalStateException} will be thrown. Similarly you can only call this method if {@link * #isResponseSendingLastChunkSent()} is false otherwise an {@link IllegalStateException} will be thrown. */ - public void setContentForFullResponse(T contentForFullResponse); + void setContentForFullResponse(@Nullable T contentForFullResponse); /** * The mime type (e.g. application/json, or text/html) to use when sending {@link #getContentForFullResponse()} or * chunks (when {@link #isChunkedResponse()} is true). This will be used along with {@link - * #getDesiredContentWriterEncoding()} to populate the outgoing {@link HttpHeaders.Names#CONTENT_TYPE} header if - * non-null. + * #getDesiredContentWriterEncoding()} to populate the outgoing {@link HttpHeaderNames#CONTENT_TYPE} header if + * non-null. This may return null - if this returns null then the response sender will use {@link + * #DEFAULT_MIME_TYPE} when sending the response. *

    * NOTE: This and {@link #getDesiredContentWriterEncoding()} will be used to override any Content-Type header in * {@link #getHeaders()} if non-null, so if you want the Content-Type header from {@link #getHeaders()} to be the @@ -107,42 +111,43 @@ public default int getHttpStatusCodeWithDefault(int defaultIfNull) { * NOTE: This WILL NOT include a charset in this string. The charset is specified via {@link * #getDesiredContentWriterEncoding()}. */ - public String getDesiredContentWriterMimeType(); + @Nullable String getDesiredContentWriterMimeType(); /** * Sets the desired content writer mime type. See {@link #getDesiredContentWriterMimeType()} for more information on * how this is used. */ - public void setDesiredContentWriterMimeType(String desiredContentWriterMimeType); + void setDesiredContentWriterMimeType(@Nullable String desiredContentWriterMimeType); /** * The charset/encoding that should be used when sending {@link #getContentForFullResponse()} or chunks (when {@link * #isChunkedResponse()} is true). This will be used to encode the bytes that are sent and will be used along with - * {@link #getDesiredContentWriterMimeType()} to populate the outgoing {@link HttpHeaders.Names#CONTENT_TYPE} header - * if non-null. + * {@link #getDesiredContentWriterMimeType()} to populate the outgoing {@link HttpHeaderNames#CONTENT_TYPE} header + * if non-null. This may return null - if this returns null then the response sender will use {@link + * #DEFAULT_CONTENT_ENCODING} when sending the response. *

    * NOTE: This and {@link #getDesiredContentWriterMimeType()} will be used to override any Content-Type header in * {@link #getHeaders()} if non-null, so if you want the Content-Type header from {@link #getHeaders()} to be the * one that is sent to the user (e.g. if you're doing a reverse proxy/edge router/domain router style endpoint) then * make sure this is null. */ - public Charset getDesiredContentWriterEncoding(); + @Nullable Charset getDesiredContentWriterEncoding(); /** * Sets the desired content writer encoding. See {@link #getDesiredContentWriterEncoding()} for more information on * how this is used. */ - public void setDesiredContentWriterEncoding(Charset desiredContentWriterEncoding); + void setDesiredContentWriterEncoding(@Nullable Charset desiredContentWriterEncoding); /** * The cookies to send. If this is null or empty then no cookies will be output to the user. */ - public Set getCookies(); + @Nullable Set getCookies(); /** * Sets the cookies to send. If this is null or empty then no cookies will be output to the user. */ - public void setCookies(Set cookies); + void setCookies(@Nullable Set cookies); /** * Returns true if the response to the user should be sent unchanged/uncompressed. If this is false then the @@ -152,7 +157,7 @@ public default int getHttpStatusCodeWithDefault(int defaultIfNull) { * The builder defaults this to false (allowing normal compression rules to apply) - if you want to force a full * sized response then set this to true. */ - public boolean isPreventCompressedOutput(); + boolean isPreventCompressedOutput(); /** * Pass in true if the response to the user should be sent unchanged/uncompressed. If this is false then the @@ -163,7 +168,7 @@ public default int getHttpStatusCodeWithDefault(int defaultIfNull) { * sized response then set this to true. */ @SuppressWarnings("unused") - public void setPreventCompressedOutput(boolean preventCompressedOutput); + void setPreventCompressedOutput(boolean preventCompressedOutput); /** * The *uncompressed* response content length of the content that was sent to the user in bytes - 0 for empty @@ -182,13 +187,13 @@ public default int getHttpStatusCodeWithDefault(int defaultIfNull) { * accurate number until after the last chunk is sent (use {@link #isResponseSendingLastChunkSent()} to verify when * it is done). */ - public Long getUncompressedRawContentLength(); + @Nullable Long getUncompressedRawContentLength(); /** * Sets the uncompressed raw content length. See {@link #getUncompressedRawContentLength()} for details on how this * is used. */ - public void setUncompressedRawContentLength(Long uncompressedRawContentLength); + void setUncompressedRawContentLength(@Nullable Long uncompressedRawContentLength); /** * The *final* response content length of the content that was sent to the user in bytes - 0 for empty responses. @@ -204,38 +209,38 @@ public default int getHttpStatusCodeWithDefault(int defaultIfNull) { * outgoing bytes, and add to or set this value appropriately (you may need to add to this value cumulatively * depending on if there are multiple chunks, etc). */ - public Long getFinalContentLength(); + @Nullable Long getFinalContentLength(); /** * Sets the final content length. See {@link #getFinalContentLength()} for details on how this is used. */ - public void setFinalContentLength(Long finalContentLength); + void setFinalContentLength(@Nullable Long finalContentLength); /** * Returns true if at least one chunk of the response (the headers chunk) has been sent to the user, false if * nothing has been sent to the user yet. This is not a chunked-response only field - it is valid and usable no * matter what {@link #isChunkedResponse()} returns. */ - public boolean isResponseSendingStarted(); + boolean isResponseSendingStarted(); /** * The response sender should call this and pass in true when the first header chunk is sent to the user. This is * not a chunked-response only field - it is valid and usable no matter what {@link #isChunkedResponse()} returns. */ - public void setResponseSendingStarted(boolean responseSendingStarted); + void setResponseSendingStarted(boolean responseSendingStarted); /** * Returns true if the last chunk of the response has been sent to the user and the response sending is therefore * complete, false if the response is not fully sent yet. This is not a chunked-response only field - it is valid * and usable no matter what {@link #isChunkedResponse()} returns. */ - public boolean isResponseSendingLastChunkSent(); + boolean isResponseSendingLastChunkSent(); /** * The response sender should call this and pass in true when the last chunk is sent to the user. This is not a * chunked-response only field - it is valid and usable no matter what {@link #isChunkedResponse()} returns. */ - public void setResponseSendingLastChunkSent(boolean responseSendingLastChunkSent); + void setResponseSendingLastChunkSent(boolean responseSendingLastChunkSent); /** * Returns true if and only if this is a "final response" before this connection closes. This should only @@ -244,13 +249,13 @@ public default int getHttpStatusCodeWithDefault(int defaultIfNull) { * com.nike.riposte.server.error.exception.TooManyOpenChannelsException} is thrown. This should default to false in * normal implementations. */ - public boolean isForceConnectionCloseAfterResponseSent(); + boolean isForceConnectionCloseAfterResponseSent(); /** * Sets the {@link #isForceConnectionCloseAfterResponseSent()} field - see that method for details on how this is * used. This should default to false in normal implementations. */ - public void setForceConnectionCloseAfterResponseSent(boolean forceConnectionCloseAfterResponseSent); + void setForceConnectionCloseAfterResponseSent(boolean forceConnectionCloseAfterResponseSent); /** * @return A new blank builder for full responses (not chunked responses). If you know the content you want to @@ -260,14 +265,14 @@ public default int getHttpStatusCodeWithDefault(int defaultIfNull) { * but if you use the other method it would look like: {@code ResponseInfo responseInfo = * ResponseInfo.newBuilder("someString").build()}. */ - public static FullResponseInfoBuilder newBuilder() { + static @NotNull FullResponseInfoBuilder newBuilder() { return new FullResponseInfoBuilder<>(); } /** * @return A new builder for full responses (not chunked responses) with the given content already populated. */ - public static FullResponseInfoBuilder newBuilder(T content) { + static @NotNull FullResponseInfoBuilder newBuilder(@Nullable T content) { return new FullResponseInfoBuilder().withContentForFullResponse(content); } @@ -277,7 +282,7 @@ public static FullResponseInfoBuilder newBuilder(T content) { * #newBuilder()} or {@link #newBuilder(Object)}). If you're unsure which one you should be using, chances are it * should *not* be this one. */ - public static ChunkedResponseInfoBuilder newChunkedResponseBuilder() { + static @NotNull ChunkedResponseInfoBuilder newChunkedResponseBuilder() { return new ChunkedResponseInfoBuilder(); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/RequestAndResponseFilter.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/RequestAndResponseFilter.java index 581e2136..fc9c09cb 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/RequestAndResponseFilter.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/RequestAndResponseFilter.java @@ -8,6 +8,9 @@ import com.nike.riposte.server.http.impl.ChunkedResponseInfo; import com.nike.riposte.server.http.impl.FullResponseInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.Optional; import io.netty.channel.ChannelHandlerContext; @@ -67,7 +70,10 @@ public interface RequestAndResponseFilter { * what you return so be careful. Null can safely be returned - if null is returned then the original request * will continue to be used. */ - RequestInfo filterRequestFirstChunkNoPayload(RequestInfo currentRequestInfo, ChannelHandlerContext ctx); + @Nullable RequestInfo filterRequestFirstChunkNoPayload( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx + ); /** * Called by the application after the last chunk of a HTTP request arrives. The {@code currentRequestInfo} will now @@ -100,8 +106,10 @@ public interface RequestAndResponseFilter { * what you return so be careful. Null can safely be returned - if null is returned then the original request * will continue to be used. */ - RequestInfo filterRequestLastChunkWithFullPayload(RequestInfo currentRequestInfo, - ChannelHandlerContext ctx); + @Nullable RequestInfo filterRequestLastChunkWithFullPayload( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx + ); /** * Called by the application when the first chunk of the response is about to be sent to the client. Depending on @@ -134,8 +142,11 @@ RequestInfo filterRequestLastChunkWithFullPayload(RequestInfo currentR * FullResponseInfo} must map to {@link FullResponseInfo}. This is another reason it's best to simply adjust the * original response when possible. */ - ResponseInfo filterResponse(ResponseInfo currentResponseInfo, RequestInfo requestInfo, - ChannelHandlerContext ctx); + @Nullable ResponseInfo filterResponse( + @NotNull ResponseInfo currentResponseInfo, + @NotNull RequestInfo requestInfo, + @NotNull ChannelHandlerContext ctx + ); /** * This method determines which request filtering methods are used. If this returns true indicating a short @@ -182,8 +193,9 @@ default boolean isShortCircuitRequestFilter() { * the pair or for the request object in the pair then the original request will continue to be used, and if null is * returned for the pair or for the response object in the pair then no short circuiting will be performed. */ - default Pair, Optional>> filterRequestFirstChunkWithOptionalShortCircuitResponse( - RequestInfo currentRequestInfo, ChannelHandlerContext ctx + default @Nullable Pair, Optional>> filterRequestFirstChunkWithOptionalShortCircuitResponse( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx ) { throw new UnsupportedOperationException( "Not implemented - should only be called if isShortCircuitRequestFilter() is true"); @@ -226,8 +238,9 @@ default Pair, Optional>> filterRequestFirstCh * the pair or for the request object in the pair then the original request will continue to be used, and if null is * returned for the pair or for the response object in the pair then no short circuiting will be performed. */ - default Pair, Optional>> filterRequestLastChunkWithOptionalShortCircuitResponse( - RequestInfo currentRequestInfo, ChannelHandlerContext ctx + default @Nullable Pair, Optional>> filterRequestLastChunkWithOptionalShortCircuitResponse( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx ) { throw new UnsupportedOperationException( "Not implemented - should only be called if isShortCircuitRequestFilter() is true"); diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/ShortCircuitingRequestAndResponseFilter.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/ShortCircuitingRequestAndResponseFilter.java index 7298fd56..cd139ccc 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/ShortCircuitingRequestAndResponseFilter.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/ShortCircuitingRequestAndResponseFilter.java @@ -4,6 +4,9 @@ import com.nike.riposte.server.http.RequestInfo; import com.nike.riposte.server.http.ResponseInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.Optional; import io.netty.channel.ChannelHandlerContext; @@ -20,15 +23,25 @@ public interface ShortCircuitingRequestAndResponseFilter extends RequestAndResponseFilter { @Override - default RequestInfo filterRequestFirstChunkNoPayload(RequestInfo currentRequestInfo, ChannelHandlerContext ctx) { - throw new UnsupportedOperationException("This method should never be called for ShortCircuitingRequestAndResponseFilter classes " - + "(where isShortCircuitRequestFilter() returns true)"); + default @Nullable RequestInfo filterRequestFirstChunkNoPayload( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx + ) { + throw new UnsupportedOperationException( + "This method should never be called for ShortCircuitingRequestAndResponseFilter classes " + + "(where isShortCircuitRequestFilter() returns true)" + ); } @Override - default RequestInfo filterRequestLastChunkWithFullPayload(RequestInfo currentRequestInfo, ChannelHandlerContext ctx) { - throw new UnsupportedOperationException("This method should never be called for ShortCircuitingRequestAndResponseFilter classes " - + "(where isShortCircuitRequestFilter() returns true)"); + default @Nullable RequestInfo filterRequestLastChunkWithFullPayload( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx + ) { + throw new UnsupportedOperationException( + "This method should never be called for ShortCircuitingRequestAndResponseFilter classes " + + "(where isShortCircuitRequestFilter() returns true)" + ); } @Override @@ -37,8 +50,14 @@ default boolean isShortCircuitRequestFilter() { } @Override - Pair, Optional>> filterRequestFirstChunkWithOptionalShortCircuitResponse(RequestInfo currentRequestInfo, ChannelHandlerContext ctx); + @Nullable Pair, Optional>> filterRequestFirstChunkWithOptionalShortCircuitResponse( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx + ); @Override - Pair, Optional>> filterRequestLastChunkWithOptionalShortCircuitResponse(RequestInfo currentRequestInfo, ChannelHandlerContext ctx); + @Nullable Pair, Optional>> filterRequestLastChunkWithOptionalShortCircuitResponse( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx + ); } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/impl/AllowAllTheThingsCORSFilter.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/impl/AllowAllTheThingsCORSFilter.java index 2fe9a4f5..e3a4fc11 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/impl/AllowAllTheThingsCORSFilter.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/filter/impl/AllowAllTheThingsCORSFilter.java @@ -5,6 +5,9 @@ import com.nike.riposte.server.http.ResponseInfo; import com.nike.riposte.server.http.filter.ShortCircuitingRequestAndResponseFilter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.Optional; import io.netty.channel.ChannelHandlerContext; @@ -36,20 +39,23 @@ * * @author Nic Munroe */ -@SuppressWarnings("WeakerAccess") public class AllowAllTheThingsCORSFilter implements ShortCircuitingRequestAndResponseFilter { @Override - public ResponseInfo filterResponse(ResponseInfo currentResponseInfo, RequestInfo requestInfo, - ChannelHandlerContext ctx) { + public @Nullable ResponseInfo filterResponse( + @NotNull ResponseInfo currentResponseInfo, + @NotNull RequestInfo requestInfo, + @NotNull ChannelHandlerContext ctx + ) { // *All* responses get tagged with the "do whatever you want" CORS ACAO header. currentResponseInfo.getHeaders().set("Access-Control-Allow-Origin", "*"); return currentResponseInfo; } @Override - public Pair, Optional>> filterRequestFirstChunkWithOptionalShortCircuitResponse( - RequestInfo currentRequestInfo, ChannelHandlerContext ctx + public @Nullable Pair, Optional>> filterRequestFirstChunkWithOptionalShortCircuitResponse( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx ) { if (HttpMethod.OPTIONS.equals(currentRequestInfo.getMethod())) { // CORS preflight OPTIONS request. Return a blank 200 response, and the filterResponse() method will @@ -62,8 +68,9 @@ public Pair, Optional>> filterRequestFirstChu } @Override - public Pair, Optional>> filterRequestLastChunkWithOptionalShortCircuitResponse( - RequestInfo currentRequestInfo, ChannelHandlerContext ctx + public @Nullable Pair, Optional>> filterRequestLastChunkWithOptionalShortCircuitResponse( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx ) { // If it was a CORS preflight OPTIONS request it would have been handled by // filterRequestFirstChunkWithOptionalShortCircuitResponse() and we would never reach here. diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/BaseResponseInfo.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/BaseResponseInfo.java index ca53ca3e..b116c2eb 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/BaseResponseInfo.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/BaseResponseInfo.java @@ -2,6 +2,9 @@ import com.nike.riposte.server.http.ResponseInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.nio.charset.Charset; import java.util.Set; @@ -18,23 +21,25 @@ @SuppressWarnings("WeakerAccess") public abstract class BaseResponseInfo implements ResponseInfo { - protected Integer httpStatusCode; - protected final HttpHeaders headers; - protected String desiredContentWriterMimeType; - protected Charset desiredContentWriterEncoding; - protected Set cookies; + protected @Nullable Integer httpStatusCode; + protected final @NotNull HttpHeaders headers; + protected @Nullable String desiredContentWriterMimeType; + protected @Nullable Charset desiredContentWriterEncoding; + protected @Nullable Set cookies; protected boolean preventCompressedOutput; - protected Long uncompressedRawContentLength; - protected Long finalContentLength; + protected @Nullable Long uncompressedRawContentLength; + protected @Nullable Long finalContentLength; protected boolean responseSendingStarted; protected boolean responseSendingLastChunkSent; protected boolean forceConnectionCloseAfterResponseSent = false; - protected BaseResponseInfo(Integer httpStatusCode, HttpHeaders headers, - String desiredContentWriterMimeType, - Charset desiredContentWriterEncoding, - Set cookies, - boolean preventCompressedOutput + protected BaseResponseInfo( + @Nullable Integer httpStatusCode, + @Nullable HttpHeaders headers, + @Nullable String desiredContentWriterMimeType, + @Nullable Charset desiredContentWriterEncoding, + @Nullable Set cookies, + boolean preventCompressedOutput ) { if (headers == null) @@ -56,47 +61,47 @@ protected BaseResponseInfo(Integer httpStatusCode, HttpHeaders headers, } @Override - public Integer getHttpStatusCode() { + public @Nullable Integer getHttpStatusCode() { return httpStatusCode; } @Override - public void setHttpStatusCode(Integer httpStatusCode) { + public void setHttpStatusCode(@Nullable Integer httpStatusCode) { this.httpStatusCode = httpStatusCode; } @Override - public HttpHeaders getHeaders() { + public @NotNull HttpHeaders getHeaders() { return headers; } @Override - public String getDesiredContentWriterMimeType() { + public @Nullable String getDesiredContentWriterMimeType() { return desiredContentWriterMimeType; } @Override - public void setDesiredContentWriterMimeType(String desiredContentWriterMimeType) { + public void setDesiredContentWriterMimeType(@Nullable String desiredContentWriterMimeType) { this.desiredContentWriterMimeType = desiredContentWriterMimeType; } @Override - public Charset getDesiredContentWriterEncoding() { + public @Nullable Charset getDesiredContentWriterEncoding() { return desiredContentWriterEncoding; } @Override - public void setDesiredContentWriterEncoding(Charset desiredContentWriterEncoding) { + public void setDesiredContentWriterEncoding(@Nullable Charset desiredContentWriterEncoding) { this.desiredContentWriterEncoding = desiredContentWriterEncoding; } @Override - public Set getCookies() { + public @Nullable Set getCookies() { return cookies; } @Override - public void setCookies(Set cookies) { + public void setCookies(@Nullable Set cookies) { this.cookies = cookies; } @@ -111,22 +116,22 @@ public void setPreventCompressedOutput(boolean preventCompressedOutput) { } @Override - public Long getUncompressedRawContentLength() { + public @Nullable Long getUncompressedRawContentLength() { return uncompressedRawContentLength; } @Override - public void setUncompressedRawContentLength(Long uncompressedRawContentLength) { + public void setUncompressedRawContentLength(@Nullable Long uncompressedRawContentLength) { this.uncompressedRawContentLength = uncompressedRawContentLength; } @Override - public Long getFinalContentLength() { + public @Nullable Long getFinalContentLength() { return finalContentLength; } @Override - public void setFinalContentLength(Long finalContentLength) { + public void setFinalContentLength(@Nullable Long finalContentLength) { this.finalContentLength = finalContentLength; } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/BaseResponseInfoBuilder.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/BaseResponseInfoBuilder.java index d39906d1..ab3825af 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/BaseResponseInfoBuilder.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/BaseResponseInfoBuilder.java @@ -2,10 +2,14 @@ import com.nike.riposte.server.http.ResponseInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.nio.charset.Charset; import java.util.Set; import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.cookie.Cookie; @@ -20,11 +24,11 @@ @SuppressWarnings("WeakerAccess") public abstract class BaseResponseInfoBuilder { - private Integer httpStatusCode; - private HttpHeaders headers; - private String desiredContentWriterMimeType; - private Charset desiredContentWriterEncoding; - private Set cookies; + private @Nullable Integer httpStatusCode; + private @Nullable HttpHeaders headers; + private @Nullable String desiredContentWriterMimeType; + private @Nullable Charset desiredContentWriterEncoding; + private @Nullable Set cookies; private boolean preventCompressedOutput = false; protected BaseResponseInfoBuilder() { @@ -34,7 +38,7 @@ protected BaseResponseInfoBuilder() { * Populates this builder with the given HTTP status code. Can be null - if this is null then the response sender * will use a default value (generally 200, but it's up to the response sender). */ - public BaseResponseInfoBuilder withHttpStatusCode(Integer httpStatusCode) { + public @NotNull BaseResponseInfoBuilder withHttpStatusCode(@Nullable Integer httpStatusCode) { this.httpStatusCode = httpStatusCode; return this; } @@ -45,7 +49,7 @@ public BaseResponseInfoBuilder withHttpStatusCode(Integer httpStatusCode) { * Populates this builder with the given headers. Can be null - if this is null then the a default blank {@link * DefaultHttpHeaders} will be used. */ - public BaseResponseInfoBuilder withHeaders(HttpHeaders headers) { + public @NotNull BaseResponseInfoBuilder withHeaders(@Nullable HttpHeaders headers) { this.headers = headers; return this; } @@ -53,7 +57,7 @@ public BaseResponseInfoBuilder withHeaders(HttpHeaders headers) { /** * Populates this builder with the mime type (e.g. application/json, or text/html) to use when sending {@link * ResponseInfo#getContentForFullResponse()} or any content chunks. This will be used along with {@link - * #withDesiredContentWriterEncoding(Charset)} to populate the outgoing {@link HttpHeaders.Names#CONTENT_TYPE} + * #withDesiredContentWriterEncoding(Charset)} to populate the outgoing {@link HttpHeaderNames#CONTENT_TYPE} * header if non-null. *

    * NOTE: This and {@link #withDesiredContentWriterEncoding(Charset)} will be used to override any Content-Type @@ -64,7 +68,9 @@ public BaseResponseInfoBuilder withHeaders(HttpHeaders headers) { * NOTE: This MUST NOT include a charset in this string. The charset is specified via {@link * #desiredContentWriterEncoding}. */ - public BaseResponseInfoBuilder withDesiredContentWriterMimeType(String desiredContentWriterMimeType) { + public @NotNull BaseResponseInfoBuilder withDesiredContentWriterMimeType( + @Nullable String desiredContentWriterMimeType + ) { this.desiredContentWriterMimeType = desiredContentWriterMimeType; return this; } @@ -73,14 +79,16 @@ public BaseResponseInfoBuilder withDesiredContentWriterMimeType(String desire * Populates this builder with the charset/encoding that should be used when sending {@link * ResponseInfo#getContentForFullResponse()} or any content chunks. This will be used to encode the bytes that are * sent and will be used along with {@link #withDesiredContentWriterMimeType(String)} to populate the outgoing - * {@link HttpHeaders.Names#CONTENT_TYPE} header if non-null. + * {@link HttpHeaderNames#CONTENT_TYPE} header if non-null. *

    * NOTE: This and {@link #withDesiredContentWriterMimeType(String)} will be used to override any Content-Type header * in {@link #getHeaders()} if non-null, so if you want the Content-Type header from {@link #getHeaders()} to be the * one that is sent to the user (e.g. if you're doing a reverse proxy/edge router/domain router style endpoint) then * make sure this is null. */ - public BaseResponseInfoBuilder withDesiredContentWriterEncoding(Charset desiredContentWriterEncoding) { + public @NotNull BaseResponseInfoBuilder withDesiredContentWriterEncoding( + @Nullable Charset desiredContentWriterEncoding + ) { this.desiredContentWriterEncoding = desiredContentWriterEncoding; return this; } @@ -89,7 +97,7 @@ public BaseResponseInfoBuilder withDesiredContentWriterEncoding(Charset desir * Populates this builder with the given cookies. Can be null - if this is null then no cookies will be sent to the * user. */ - public BaseResponseInfoBuilder withCookies(Set cookies) { + public @NotNull BaseResponseInfoBuilder withCookies(@Nullable Set cookies) { this.cookies = cookies; return this; } @@ -101,28 +109,28 @@ public BaseResponseInfoBuilder withCookies(Set cookies) { * whether it meets compression criteria. This defaults to false (allowing normal compression rules to apply) - if * you want to force a full sized response then set this to true. */ - public BaseResponseInfoBuilder withPreventCompressedOutput(boolean preventCompressedOutput) { + public @NotNull BaseResponseInfoBuilder withPreventCompressedOutput(boolean preventCompressedOutput) { this.preventCompressedOutput = preventCompressedOutput; return this; } - protected Integer getHttpStatusCode() { + protected @Nullable Integer getHttpStatusCode() { return httpStatusCode; } - protected HttpHeaders getHeaders() { + protected @Nullable HttpHeaders getHeaders() { return headers; } - protected String getDesiredContentWriterMimeType() { + protected @Nullable String getDesiredContentWriterMimeType() { return desiredContentWriterMimeType; } - protected Charset getDesiredContentWriterEncoding() { + protected @Nullable Charset getDesiredContentWriterEncoding() { return desiredContentWriterEncoding; } - protected Set getCookies() { + protected @Nullable Set getCookies() { return cookies; } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/ChunkedResponseInfo.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/ChunkedResponseInfo.java index d87b0769..1829dc98 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/ChunkedResponseInfo.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/ChunkedResponseInfo.java @@ -1,5 +1,8 @@ package com.nike.riposte.server.http.impl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.nio.charset.Charset; import java.util.Set; @@ -20,12 +23,14 @@ public class ChunkedResponseInfo extends BaseResponseInfo { * The "populate everything" constructor. It's recommended that you use the {@link ChunkedResponseInfoBuilder} * instead. */ - public ChunkedResponseInfo(Integer httpStatusCode, HttpHeaders headers, - String desiredContentWriterMimeType, - Charset desiredContentWriterEncoding, - Set cookies, - boolean preventCompressedOutput) { - + public ChunkedResponseInfo( + @Nullable Integer httpStatusCode, + @Nullable HttpHeaders headers, + @Nullable String desiredContentWriterMimeType, + @Nullable Charset desiredContentWriterEncoding, + @Nullable Set cookies, + boolean preventCompressedOutput + ) { super(httpStatusCode, headers, desiredContentWriterMimeType, desiredContentWriterEncoding, cookies, preventCompressedOutput); } @@ -45,7 +50,7 @@ public boolean isChunkedResponse() { } @Override - public Void getContentForFullResponse() { + public @Nullable Void getContentForFullResponse() { throw new IllegalStateException( "Attempted to call getContentForFullResponse() when isChunkedResponse() is true. Always verify that " + "isChunkedResponse() returns false before calling this method." @@ -53,7 +58,7 @@ public Void getContentForFullResponse() { } @Override - public void setContentForFullResponse(Void contentForFullResponse) { + public void setContentForFullResponse(@Nullable Void contentForFullResponse) { throw new IllegalStateException("isChunkedResponse() is true. You cannot add full response content to a " + "chunked response."); } @@ -68,37 +73,41 @@ public ChunkedResponseInfoBuilder() { } @Override - public ChunkedResponseInfoBuilder withHttpStatusCode(Integer httpStatusCode) { + public @NotNull ChunkedResponseInfoBuilder withHttpStatusCode(@Nullable Integer httpStatusCode) { super.withHttpStatusCode(httpStatusCode); return this; } @Override - public ChunkedResponseInfoBuilder withHeaders(HttpHeaders headers) { + public @NotNull ChunkedResponseInfoBuilder withHeaders(@Nullable HttpHeaders headers) { super.withHeaders(headers); return this; } @Override - public ChunkedResponseInfoBuilder withDesiredContentWriterMimeType(String desiredContentWriterMimeType) { + public @NotNull ChunkedResponseInfoBuilder withDesiredContentWriterMimeType( + @Nullable String desiredContentWriterMimeType + ) { super.withDesiredContentWriterMimeType(desiredContentWriterMimeType); return this; } @Override - public ChunkedResponseInfoBuilder withDesiredContentWriterEncoding(Charset desiredContentWriterEncoding) { + public @NotNull ChunkedResponseInfoBuilder withDesiredContentWriterEncoding( + @Nullable Charset desiredContentWriterEncoding + ) { super.withDesiredContentWriterEncoding(desiredContentWriterEncoding); return this; } @Override - public ChunkedResponseInfoBuilder withCookies(Set cookies) { + public @NotNull ChunkedResponseInfoBuilder withCookies(@Nullable Set cookies) { super.withCookies(cookies); return this; } @Override - public ChunkedResponseInfoBuilder withPreventCompressedOutput(boolean preventCompressedOutput) { + public @NotNull ChunkedResponseInfoBuilder withPreventCompressedOutput(boolean preventCompressedOutput) { super.withPreventCompressedOutput(preventCompressedOutput); return this; } @@ -106,7 +115,7 @@ public ChunkedResponseInfoBuilder withPreventCompressedOutput(boolean preventCom /** * @return A {@link ChunkedResponseInfo} setup with all the values contained in this builder. */ - public ChunkedResponseInfo build() { + public @NotNull ChunkedResponseInfo build() { return new ChunkedResponseInfo( getHttpStatusCode(), getHeaders(), diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/FullResponseInfo.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/FullResponseInfo.java index afe43e32..ba79bb63 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/FullResponseInfo.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/FullResponseInfo.java @@ -1,5 +1,8 @@ package com.nike.riposte.server.http.impl; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.nio.charset.Charset; import java.util.Set; @@ -17,19 +20,20 @@ public class FullResponseInfo extends BaseResponseInfo { @SuppressWarnings("WeakerAccess") - protected T contentForFullResponse; + protected @Nullable T contentForFullResponse; /** * The "populate everything" constructor. It's recommended that you use the {@link FullResponseInfoBuilder} instead. */ - public FullResponseInfo(T contentForFullResponse, - Integer httpStatusCode, - HttpHeaders headers, - String desiredContentWriterMimeType, - Charset desiredContentWriterEncoding, - Set cookies, - boolean preventCompressedOutput) { - + public FullResponseInfo( + @Nullable T contentForFullResponse, + @Nullable Integer httpStatusCode, + @Nullable HttpHeaders headers, + @Nullable String desiredContentWriterMimeType, + @Nullable Charset desiredContentWriterEncoding, + @Nullable Set cookies, + boolean preventCompressedOutput + ) { super(httpStatusCode, headers, desiredContentWriterMimeType, desiredContentWriterEncoding, cookies, preventCompressedOutput); @@ -51,12 +55,12 @@ public boolean isChunkedResponse() { } @Override - public T getContentForFullResponse() { + public @Nullable T getContentForFullResponse() { return contentForFullResponse; } @Override - public void setContentForFullResponse(T contentForFullResponse) { + public void setContentForFullResponse(@Nullable T contentForFullResponse) { if (isResponseSendingLastChunkSent()) { throw new IllegalStateException("isFullResponseSent() is true. You cannot set content for a response that " + "has already been sent to the user."); @@ -74,7 +78,7 @@ public void setContentForFullResponse(T contentForFullResponse) { */ public static final class FullResponseInfoBuilder extends BaseResponseInfoBuilder { - private T contentForFullResponse; + private @Nullable T contentForFullResponse; public FullResponseInfoBuilder() { @@ -84,43 +88,47 @@ public FullResponseInfoBuilder() { * Populates this builder with the given content intended for a full response. Can be null if there is no * response body content to send. */ - public FullResponseInfoBuilder withContentForFullResponse(T content) { + public @NotNull FullResponseInfoBuilder withContentForFullResponse(@Nullable T content) { this.contentForFullResponse = content; return this; } @Override - public FullResponseInfoBuilder withHttpStatusCode(Integer httpStatusCode) { + public @NotNull FullResponseInfoBuilder withHttpStatusCode(@Nullable Integer httpStatusCode) { super.withHttpStatusCode(httpStatusCode); return this; } @Override - public FullResponseInfoBuilder withHeaders(HttpHeaders headers) { + public @NotNull FullResponseInfoBuilder withHeaders(@Nullable HttpHeaders headers) { super.withHeaders(headers); return this; } @Override - public FullResponseInfoBuilder withDesiredContentWriterMimeType(String desiredContentWriterMimeType) { + public @NotNull FullResponseInfoBuilder withDesiredContentWriterMimeType( + @Nullable String desiredContentWriterMimeType + ) { super.withDesiredContentWriterMimeType(desiredContentWriterMimeType); return this; } @Override - public FullResponseInfoBuilder withDesiredContentWriterEncoding(Charset desiredContentWriterEncoding) { + public @NotNull FullResponseInfoBuilder withDesiredContentWriterEncoding( + @Nullable Charset desiredContentWriterEncoding + ) { super.withDesiredContentWriterEncoding(desiredContentWriterEncoding); return this; } @Override - public FullResponseInfoBuilder withCookies(Set cookies) { + public @NotNull FullResponseInfoBuilder withCookies(@Nullable Set cookies) { super.withCookies(cookies); return this; } @Override - public FullResponseInfoBuilder withPreventCompressedOutput(boolean preventCompressedOutput) { + public @NotNull FullResponseInfoBuilder withPreventCompressedOutput(boolean preventCompressedOutput) { super.withPreventCompressedOutput(preventCompressedOutput); return this; } @@ -128,7 +136,7 @@ public FullResponseInfoBuilder withPreventCompressedOutput(boolean preventCom /** * @return A {@link FullResponseInfo} setup with all the values contained in this builder. */ - public FullResponseInfo build() { + public @NotNull FullResponseInfo build() { return new FullResponseInfo<>(contentForFullResponse, getHttpStatusCode(), getHeaders(), diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/RequestInfoImpl.java b/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/RequestInfoImpl.java index 5f77945b..2a81de26 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/RequestInfoImpl.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/http/impl/RequestInfoImpl.java @@ -7,7 +7,8 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.netty.handler.codec.http.HttpUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,6 +30,7 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.QueryStringDecoder; @@ -49,54 +51,67 @@ public class RequestInfoImpl implements RequestInfo, RiposteInternalReques private static final Logger logger = LoggerFactory.getLogger(RequestInfoImpl.class); - protected final String uri; - protected final String path; - protected final HttpMethod method; - protected final HttpHeaders headers; - protected HttpHeaders trailingHeaders; - protected final QueryStringDecoder queryParams; - protected final Set cookies; - protected String pathTemplate; - protected Map pathParams; - protected final Map attributes = new HashMap<>(); + protected final @NotNull String uri; + protected final @NotNull String path; + protected final @Nullable HttpMethod method; + protected final @NotNull HttpHeaders headers; + protected @NotNull HttpHeaders trailingHeaders; + protected @NotNull final QueryStringDecoder queryParams; + protected final @NotNull Set cookies; + protected @Nullable String pathTemplate; + protected @NotNull Map pathParams = Collections.emptyMap(); + protected final @NotNull Map attributes = new HashMap<>(); protected int rawContentLengthInBytes; - protected byte[] rawContentBytes; - protected String rawContent; - protected T content; - protected final Charset contentCharset; - protected final HttpVersion protocolVersion; + protected @Nullable byte[] rawContentBytes; + protected @Nullable String rawContent; + protected @Nullable T content; + protected final @NotNull Charset contentCharset; + protected final @Nullable HttpVersion protocolVersion; protected final boolean keepAliveRequested; - protected final List contentChunks = new ArrayList<>(); + protected final @NotNull List contentChunks = new ArrayList<>(); protected boolean isCompleteRequestWithAllChunks; protected final boolean isMultipart; protected boolean multipartDataIsDestroyed = false; - protected HttpPostMultipartRequestDecoder multipartData; + protected @Nullable HttpPostMultipartRequestDecoder multipartData; - protected ObjectMapper contentDeserializer; - protected TypeReference contentDeserializerTypeReference; + protected @Nullable ObjectMapper contentDeserializer; + protected @Nullable TypeReference contentDeserializerTypeReference; protected boolean contentChunksWillBeReleasedExternally = false; - public RequestInfoImpl(String uri, HttpMethod method, HttpHeaders headers, HttpHeaders trailingHeaders, - QueryStringDecoder queryParams, - Set cookies, Map pathParams, List contentChunks, - HttpVersion protocolVersion, - boolean keepAliveRequested, boolean isCompleteRequestWithAllChunks, boolean isMultipart) { - - if (uri == null) + public RequestInfoImpl( + @Nullable String uri, + @Nullable HttpMethod method, + @Nullable HttpHeaders headers, + @Nullable HttpHeaders trailingHeaders, + @Nullable QueryStringDecoder queryParams, + @Nullable Set cookies, + @Nullable Map pathParams, + @Nullable List<@NotNull HttpContent> contentChunks, + @Nullable HttpVersion protocolVersion, + boolean keepAliveRequested, + boolean isCompleteRequestWithAllChunks, + boolean isMultipart + ) { + if (uri == null) { uri = ""; + } - if (headers == null) + if (headers == null) { headers = new DefaultHttpHeaders(); + } - if (trailingHeaders == null) + if (trailingHeaders == null) { trailingHeaders = new DefaultHttpHeaders(); + } - if (queryParams == null) + if (queryParams == null) { queryParams = new QueryStringDecoder(uri); + } - if (cookies == null) + if (cookies == null) { cookies = new HashSet<>(); + } this.uri = uri; this.path = QueryStringDecoder.decodeComponent(HttpUtils.extractPath(uri)); @@ -116,12 +131,21 @@ public RequestInfoImpl(String uri, HttpMethod method, HttpHeaders headers, HttpH this.isMultipart = isMultipart; } - public RequestInfoImpl(HttpRequest request) { - this(request.uri(), request.method(), request.headers(), - HttpUtils.extractTrailingHeadersIfPossible(request), null, HttpUtils.extractCookies(request), null, - HttpUtils.extractContentChunks(request), request.protocolVersion(), HttpUtil.isKeepAlive(request), - (request instanceof FullHttpRequest), - HttpPostRequestDecoder.isMultipart(request)); + public RequestInfoImpl(@NotNull HttpRequest request) { + this( + request.uri(), + request.method(), + request.headers(), + HttpUtils.extractTrailingHeadersIfPossible(request), + null, + HttpUtils.extractCookies(request), + null, + HttpUtils.extractContentChunks(request), + request.protocolVersion(), + HttpUtil.isKeepAlive(request), + (request instanceof FullHttpRequest), + HttpPostRequestDecoder.isMultipart(request) + ); } /** @@ -141,7 +165,7 @@ public static RequestInfoImpl dummyInstanceForUnknownRequests() { * {@inheritDoc} */ @Override - public String getUri() { + public @NotNull String getUri() { return uri; } @@ -149,7 +173,7 @@ public String getUri() { * {@inheritDoc} */ @Override - public String getPath() { + public @NotNull String getPath() { return path; } @@ -157,7 +181,7 @@ public String getPath() { * {@inheritDoc} */ @Override - public HttpMethod getMethod() { + public @Nullable HttpMethod getMethod() { return method; } @@ -165,7 +189,7 @@ public HttpMethod getMethod() { * {@inheritDoc} */ @Override - public HttpHeaders getHeaders() { + public @NotNull HttpHeaders getHeaders() { return headers; } @@ -173,7 +197,7 @@ public HttpHeaders getHeaders() { * {@inheritDoc} */ @Override - public QueryStringDecoder getQueryParams() { + public @NotNull QueryStringDecoder getQueryParams() { return queryParams; } @@ -181,7 +205,7 @@ public QueryStringDecoder getQueryParams() { * {@inheritDoc} */ @Override - public Set getCookies() { + public @NotNull Set getCookies() { return cookies; } @@ -189,7 +213,7 @@ public Set getCookies() { * {@inheritDoc} */ @Override - public Charset getContentCharset() { + public @NotNull Charset getContentCharset() { return contentCharset; } @@ -197,7 +221,7 @@ public Charset getContentCharset() { * {@inheritDoc} */ @Override - public HttpVersion getProtocolVersion() { + public @Nullable HttpVersion getProtocolVersion() { return protocolVersion; } @@ -224,7 +248,7 @@ public synchronized int getRawContentLengthInBytes() { * {@inheritDoc} */ @Override - public synchronized byte[] getRawContentBytes() { + public synchronized @Nullable byte[] getRawContentBytes() { if (!isCompleteRequestWithAllChunks) return null; @@ -240,7 +264,7 @@ public synchronized byte[] getRawContentBytes() { * {@inheritDoc} */ @Override - public synchronized String getRawContent() { + public synchronized @Nullable String getRawContent() { if (!isCompleteRequestWithAllChunks) return null; @@ -257,7 +281,7 @@ public synchronized String getRawContent() { * {@inheritDoc} */ @Override - public synchronized T getContent() { + public synchronized @Nullable T getContent() { if (!isCompleteRequestWithAllChunks) return null; @@ -279,16 +303,31 @@ public boolean isMultipartRequest() { * {@inheritDoc} */ @Override - public synchronized List getMultipartParts() { + public synchronized @Nullable List getMultipartParts() { if (!isMultipartRequest() || !isCompleteRequestWithAllChunks()) return null; if (multipartData == null) { byte[] contentBytes = getRawContentBytes(); + HttpVersion httpVersion = getProtocolVersion(); + HttpMethod httpMethod = getMethod(); + // HttpVersion and HttpMethod cannot be null because DefaultFullHttpRequest doesn't allow them to be + // null, but our getProtocolVersion() and getMethod() methods might return null (i.e. due to an + // invalid request). They shouldn't be null in practice by the time this getMultipartParts() method + // is called, but since they don't seem to be used by the Netty code we delegate to, we can just + // default them to something if null somehow slips through. + if (httpVersion == null) { + httpVersion = HttpVersion.HTTP_1_0; + } + + if (httpMethod == null) { + httpMethod = HttpMethod.POST; + } + HttpRequest fullHttpRequestForMultipartDecoder = (contentBytes == null) - ? new DefaultFullHttpRequest(getProtocolVersion(), getMethod(), getUri()) - : new DefaultFullHttpRequest(getProtocolVersion(), getMethod(), getUri(), + ? new DefaultFullHttpRequest(httpVersion, httpMethod, getUri()) + : new DefaultFullHttpRequest(httpVersion, httpMethod, getUri(), Unpooled.wrappedBuffer(contentBytes)); fullHttpRequestForMultipartDecoder.headers().add(getHeaders()); @@ -305,10 +344,12 @@ protected T deserializeContent() { // TODO: We could conceivably have a case where contentDeserializerTypeReference is a string/charsequence, // but contentDeserializer is null. In that case we should not return null, because getRawContent() is a // valid return value. Can probably fix this by making isContentDeserializerSetup() smarter. - if (!isContentDeserializerSetup()) + if (!isContentDeserializerSetup()) { return null; + } try { + @SuppressWarnings("ConstantConditions") // isContentDeserializerSetup() verifies contentDeserializerTypeReference is non-null. Type inputType = contentDeserializerTypeReference.getType(); if (inputType instanceof Class) { Class inputTypeClass = (Class) inputType; @@ -326,10 +367,12 @@ protected T deserializeContent() { // Not a String or CharSequence. Do our best to deserialize. byte[] bytes = getRawContentBytes(); + //noinspection ConstantConditions - isContentDeserializerSetup() verifies contentDeserializer is non-null. return (bytes == null) ? null : contentDeserializer.readValue(bytes, contentDeserializerTypeReference); } catch (Throwable e) { // Something went wrong during deserialization. Throw an appropriate error. + //noinspection ConstantConditions - isContentDeserializerSetup() verifies contentDeserializerTypeReference is non-null. logger.info("Unable to deserialize request content to desired object type - {}: {}", contentDeserializerTypeReference.getType().toString(), e.getMessage()); throw new RequestContentDeserializationException( @@ -343,7 +386,10 @@ protected T deserializeContent() { * {@inheritDoc} */ @Override - public RequestInfo setupContentDeserializer(ObjectMapper deserializer, TypeReference typeReference) { + public @NotNull RequestInfo setupContentDeserializer( + @NotNull ObjectMapper deserializer, + @NotNull TypeReference typeReference + ) { this.contentDeserializer = deserializer; this.contentDeserializerTypeReference = typeReference; return this; @@ -361,7 +407,7 @@ public boolean isContentDeserializerSetup() { * {@inheritDoc} */ @Override - public HttpHeaders getTrailingHeaders() { + public @NotNull HttpHeaders getTrailingHeaders() { return trailingHeaders; } @@ -369,7 +415,7 @@ public HttpHeaders getTrailingHeaders() { * {@inheritDoc} */ @Override - public RequestInfo setPathParamsBasedOnPathTemplate(String pathTemplate) { + public @NotNull RequestInfo setPathParamsBasedOnPathTemplate(@NotNull String pathTemplate) { this.pathTemplate = pathTemplate; setPathParams(HttpUtils.decodePathParams(pathTemplate, getPath())); return this; @@ -379,11 +425,11 @@ public RequestInfo setPathParamsBasedOnPathTemplate(String pathTemplate) { * {@inheritDoc} */ @Override - public Map getPathParams() { + public @NotNull Map getPathParams() { return pathParams; } - protected void setPathParams(Map pathParams) { + protected void setPathParams(@Nullable Map pathParams) { if (pathParams == null) pathParams = Collections.emptyMap(); @@ -406,7 +452,7 @@ public void contentChunksWillBeReleasedExternally() { * {@inheritDoc} */ @Override - public int addContentChunk(HttpContent chunk) { + public int addContentChunk(@NotNull HttpContent chunk) { if (isCompleteRequestWithAllChunks) { throw new IllegalStateException("Cannot add new content chunk - this RequestInfo is already marked as " + "representing the complete request with all chunks"); @@ -492,14 +538,14 @@ public void releaseMultipartData() { /** * {@inheritDoc} */ - public void addRequestAttribute(String attributeName, Object attributeValue) { + public void addRequestAttribute(@NotNull String attributeName, @NotNull Object attributeValue) { attributes.put(attributeName, attributeValue); } /** * {@inheritDoc} */ - public Map getRequestAttributes() { + public @NotNull Map getRequestAttributes() { return attributes; } @@ -507,7 +553,7 @@ public Map getRequestAttributes() { * {@inheritDoc} */ @Override - public String getPathTemplate() { + public @NotNull String getPathTemplate() { return this.pathTemplate == null ? "" : this.pathTemplate; } diff --git a/riposte-spi/src/main/java/com/nike/riposte/server/logging/AccessLogger.java b/riposte-spi/src/main/java/com/nike/riposte/server/logging/AccessLogger.java index 2334e135..82cc4948 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/server/logging/AccessLogger.java +++ b/riposte-spi/src/main/java/com/nike/riposte/server/logging/AccessLogger.java @@ -4,6 +4,8 @@ import com.nike.riposte.server.http.RequestInfo; import com.nike.riposte.server.http.ResponseInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,12 +26,12 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponse; -import static io.netty.handler.codec.http.HttpHeaders.Names.ACCEPT; -import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH; -import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; -import static io.netty.handler.codec.http.HttpHeaders.Names.REFERER; -import static io.netty.handler.codec.http.HttpHeaders.Names.TRANSFER_ENCODING; -import static io.netty.handler.codec.http.HttpHeaders.Names.USER_AGENT; +import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpHeaderNames.REFERER; +import static io.netty.handler.codec.http.HttpHeaderNames.TRANSFER_ENCODING; +import static io.netty.handler.codec.http.HttpHeaderNames.USER_AGENT; /** * This class is responsible for logging an access log message for the given request/response to the SLF4J logger with @@ -103,15 +105,16 @@ public class AccessLogger { public static final String SPAN_ID = "X-B3-SpanId"; @SuppressWarnings("unused") public static final String PARENT_ID = "X-B3-ParentSpanId"; + @SuppressWarnings("unused") public static final String SPAN_NAME = "X-B3-SpanName"; public static final String TRACE_ENABLED = "X-B3-Sampled"; - private static String cachedLocalIpAddress; + private static @Nullable String cachedLocalIpAddress; private static long cachedLocalIpAddressLastCheckedTime = 0; private static final long LOCAL_IP_ADDRESS_CACHE_CHECK_FREQUENCY_MILLIS = 60 * 1000; - private final String timezoneString; - private final String[] shortMonthNames; + private final @NotNull String timezoneString; + private final @NotNull String[] shortMonthNames; private final CompletableFuture alreadyCompletedFuture = CompletableFuture.completedFuture(null); public AccessLogger() { @@ -119,10 +122,10 @@ public AccessLogger() { timezoneString = " " + ZonedDateTime.now().format(DateTimeFormatter.ofPattern("Z")); Locale defaultLocale = Locale.getDefault(Locale.Category.FORMAT); Map monthMap = - Calendar.getInstance().getDisplayNames(Calendar.MONTH, Calendar.SHORT_FORMAT, defaultLocale); + Calendar.getInstance().getDisplayNames(Calendar.MONTH, Calendar.SHORT, defaultLocale); int maxMonthIndex = Collections.max(monthMap.values()); shortMonthNames = new String[maxMonthIndex + 1]; - monthMap.entrySet().forEach(entry -> shortMonthNames[entry.getValue()] = entry.getKey()); + monthMap.forEach((key, value) -> shortMonthNames[value] = key); } /** @@ -153,12 +156,16 @@ public AccessLogger() { * @param elapsedTimeMillis * represents time difference from receiving the request to ending the request in milliseconds; may be null */ - public CompletableFuture log(RequestInfo request, - HttpResponse finalResponseObject, - ResponseInfo responseInfo, - Long elapsedTimeMillis) { - if (request == null) + public @NotNull CompletableFuture log( + @NotNull RequestInfo request, + @Nullable HttpResponse finalResponseObject, + @Nullable ResponseInfo responseInfo, + @Nullable Long elapsedTimeMillis + ) { + //noinspection ConstantConditions + if (request == null) { throw new IllegalArgumentException("request cannot be null"); + } if (logAsynchronously()) { return CompletableFuture.runAsync( @@ -207,10 +214,12 @@ protected boolean logAsynchronously() { * * @return The final string that will be logged as the full access log message. */ - protected String generateFinalAccessLogMessage(RequestInfo request, - HttpResponse finalResponseObject, - ResponseInfo responseInfo, - Long elapsedTimeMillis) { + protected @NotNull String generateFinalAccessLogMessage( + @NotNull RequestInfo request, + @Nullable HttpResponse finalResponseObject, + @Nullable ResponseInfo responseInfo, + @Nullable Long elapsedTimeMillis + ) { String combinedLogString = combinedLogFormatPrefix(request, finalResponseObject, responseInfo); List> logMessageAdditions = logMessageAdditions( @@ -238,9 +247,11 @@ protected String generateFinalAccessLogMessage(RequestInfo request, * @return String representing the NCSA Combined log format for access logs plus the referrer and user agent: %h %l * %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}" */ - protected String combinedLogFormatPrefix(RequestInfo request, - HttpResponse finalResponseObject, - ResponseInfo responseInfo) { + protected @NotNull String combinedLogFormatPrefix( + @NotNull RequestInfo request, + @Nullable HttpResponse finalResponseObject, + @Nullable ResponseInfo responseInfo + ) { String ipAddress = ""; try { ipAddress = getLocalIpAddress(); @@ -251,32 +262,41 @@ protected String combinedLogFormatPrefix(RequestInfo request, String method = (request.getMethod() == null) ? "-" : String.valueOf(request.getMethod()); String uriString = request.getUri(); - if (uriString == null) + //noinspection ConstantConditions + if (uriString == null) { uriString = "-"; - String protocolVersion = - (request.getProtocolVersion() == null) ? "-" : String.valueOf(request.getProtocolVersion()); + } + String protocolVersion = (request.getProtocolVersion() == null) + ? "-" + : String.valueOf(request.getProtocolVersion()); String url = method + " " + uriString + " " + protocolVersion; String referer = "-"; String userAgent = "-"; + //noinspection ConstantConditions if (request.getHeaders() != null) { referer = request.getHeaders().get(REFERER); - if (referer == null) + if (referer == null) { referer = "-"; + } userAgent = request.getHeaders().get(USER_AGENT); - if (userAgent == null) + if (userAgent == null) { userAgent = "-"; + } } String httpStatusCode = "-"; - if (finalResponseObject != null && finalResponseObject.status() != null) + if (finalResponseObject != null && finalResponseObject.status() != null) { httpStatusCode = String.valueOf(finalResponseObject.status().code()); - else if (responseInfo != null && responseInfo.getHttpStatusCode() != null) + } + else if (responseInfo != null && responseInfo.getHttpStatusCode() != null) { httpStatusCode = String.valueOf(responseInfo.getHttpStatusCode()); + } String contentLength = "-"; if (responseInfo != null) { - if (responseInfo.getFinalContentLength() != null && responseInfo.getFinalContentLength() > 0) + if (responseInfo.getFinalContentLength() != null && responseInfo.getFinalContentLength() > 0) { contentLength = String.valueOf(responseInfo.getFinalContentLength()); + } } return ipAddress + @@ -308,7 +328,7 @@ else if (responseInfo != null && responseInfo.getHttpStatusCode() != null) * @return The given {@link ZonedDateTime} formatted as if it was run through: {@code * DateTimeFormatter.ofPattern("dd/MMM/YYYY:HH:mm:ss Z")}. */ - protected String getFormattedDateTimeForNcsaCombinedLog(ZonedDateTime dateTime) { + protected @NotNull String getFormattedDateTimeForNcsaCombinedLog(@NotNull ZonedDateTime dateTime) { StringBuilder resultString = new StringBuilder(32); appendTwoDigitFormattedInt(dateTime.getDayOfMonth(), resultString).append("/"); resultString.append(shortMonthNames[dateTime.getMonthValue() - 1]).append("/") @@ -330,16 +350,17 @@ protected String getFormattedDateTimeForNcsaCombinedLog(ZonedDateTime dateTime) * necessary so that the appended value takes up two characters - e.g. passing in an integer value of 3 would cause * "03" to be appended to the given string builder. */ - protected StringBuilder appendTwoDigitFormattedInt(int theInt, StringBuilder sb) { - if (theInt < 10) + protected @NotNull StringBuilder appendTwoDigitFormattedInt(int theInt, @NotNull StringBuilder sb) { + if (theInt < 10) { sb.append("0"); + } return sb.append(theInt); } /** * @return The current local IP address. */ - protected String getLocalIpAddress() throws UnknownHostException { + protected @NotNull String getLocalIpAddress() throws UnknownHostException { // This is an imperfect solution: // http://stackoverflow.com/questions/9481865/how-to-get-ip-address-of-current-machine-using-java long currentTimeMillis = System.currentTimeMillis(); @@ -370,10 +391,12 @@ protected String getLocalIpAddress() throws UnknownHostException { * this method as well as anything returned by {@link #customApplicationLogMessageExtras(RequestInfo, HttpResponse, * ResponseInfo, Long)}. */ - protected List> logMessageAdditions(RequestInfo request, - HttpResponse finalResponseObject, - ResponseInfo responseInfo, - Long elapsedTimeMillis) { + protected @NotNull List> logMessageAdditions( + @NotNull RequestInfo request, + @Nullable HttpResponse finalResponseObject, + @Nullable ResponseInfo responseInfo, + @Nullable Long elapsedTimeMillis + ) { String httpStatusCode = null; String contentLengthResponseHeader = null; String transferEncodingResponseHeader = null; @@ -382,16 +405,20 @@ protected List> logMessageAdditions(RequestInfo request, String uncompressedRawContentLength = null; String finalContentLength = null; - if (finalResponseObject != null && finalResponseObject.status() != null) + if (finalResponseObject != null && finalResponseObject.status() != null) { httpStatusCode = String.valueOf(finalResponseObject.status().code()); - else if (responseInfo != null && responseInfo.getHttpStatusCode() != null) + } + else if (responseInfo != null && responseInfo.getHttpStatusCode() != null) { httpStatusCode = String.valueOf(responseInfo.getHttpStatusCode()); + } HttpHeaders responseHeadersToUse = null; - if (finalResponseObject != null && finalResponseObject.headers() != null) + if (finalResponseObject != null && finalResponseObject.headers() != null) { responseHeadersToUse = finalResponseObject.headers(); - else if (responseInfo != null) + } + else if (responseInfo != null) { responseHeadersToUse = responseInfo.getHeaders(); + } if (responseHeadersToUse != null) { contentLengthResponseHeader = responseHeadersToUse.get(CONTENT_LENGTH); @@ -401,16 +428,16 @@ else if (responseInfo != null) } if (responseInfo != null) { - if (responseInfo.getUncompressedRawContentLength() != null) + if (responseInfo.getUncompressedRawContentLength() != null) { uncompressedRawContentLength = String.valueOf(responseInfo.getUncompressedRawContentLength()); + } - if (responseInfo.getFinalContentLength() != null) + if (responseInfo.getFinalContentLength() != null) { finalContentLength = String.valueOf(responseInfo.getFinalContentLength()); + } } - List> logMessageAdditions = new ArrayList<>(); - - logMessageAdditions.addAll(Arrays.asList( + List> logMessageAdditions = new ArrayList<>(Arrays.asList( Pair.of("accept-Req", request.getHeaders().get(ACCEPT)), Pair.of("content-type-Req", request.getHeaders().get(CONTENT_TYPE)), Pair.of("content-length-Res", contentLengthResponseHeader), @@ -429,8 +456,10 @@ else if (responseInfo != null) List> customApplicationLogMessageExtras = customApplicationLogMessageExtras(request, finalResponseObject, responseInfo, elapsedTimeMillis); - if (customApplicationLogMessageExtras != null) + + if (customApplicationLogMessageExtras != null) { logMessageAdditions.addAll(customApplicationLogMessageExtras); + } return logMessageAdditions; } @@ -459,21 +488,24 @@ else if (responseInfo != null) * Represents time difference from receiving the request to ending the request in milliseconds - may be null in * some circumstances (i.e. an error occurred during request processing). */ - protected List> customApplicationLogMessageExtras(RequestInfo request, - HttpResponse finalResponseObject, - ResponseInfo responseInfo, - Long elapsedTimeMillis) { + protected @Nullable List> customApplicationLogMessageExtras( + @NotNull RequestInfo request, + @Nullable HttpResponse finalResponseObject, + @Nullable ResponseInfo responseInfo, + @Nullable Long elapsedTimeMillis + ) { return null; } /** * Converts the given list of key/value pairs to a single string. By default this uses {@link - * #formatAdditionPairForLogMessage(Pair)} to turn each pair into a string and then joins them all together with a " - * " single space as the delimiter. + * #formatAdditionPairForLogMessage(Pair)} to turn each pair into a string and then joins them all together with a + * " " single space as the delimiter. */ - protected String convertAdditionsToString(List> logMessageAdditions) { - if (logMessageAdditions == null) + protected @NotNull String convertAdditionsToString(@Nullable List> logMessageAdditions) { + if (logMessageAdditions == null) { return "-"; + } return logMessageAdditions.stream().map(this::formatAdditionPairForLogMessage).collect(Collectors.joining(" ")); } @@ -483,18 +515,21 @@ protected String convertAdditionsToString(List> logMessageA * key and value with an equals sign between '='. So for example a key of foo and value of bar would be returned as * foo=bar. A key of foo and null value would be returned as foo=- */ - protected String formatAdditionPairForLogMessage(Pair pair) { - if (pair == null) + protected @NotNull String formatAdditionPairForLogMessage(@Nullable Pair pair) { + if (pair == null) { return "-"; + } String key = pair.getKey(); String value = pair.getValue(); - if (key == null) + if (key == null) { key = "-"; + } - if (value == null) + if (value == null) { value = "-"; + } return key + "=" + value; } @@ -503,8 +538,10 @@ protected String formatAdditionPairForLogMessage(Pair pair) { * Combines the two strings into a single string. By default this just puts a single space " " between the two * strings. */ - protected String concatenateCombinedLogAndAdditionStrings(String combinedLogString, - String logMessageAdditionsAsString) { + protected @NotNull String concatenateCombinedLogAndAdditionStrings( + @NotNull String combinedLogString, + @NotNull String logMessageAdditionsAsString + ) { return combinedLogString + " " + logMessageAdditionsAsString; } } diff --git a/riposte-spi/src/main/java/com/nike/riposte/util/HttpUtils.java b/riposte-spi/src/main/java/com/nike/riposte/util/HttpUtils.java index 6e01621d..565331d2 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/util/HttpUtils.java +++ b/riposte-spi/src/main/java/com/nike/riposte/util/HttpUtils.java @@ -5,6 +5,9 @@ import com.nike.riposte.server.http.Endpoint; import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.nio.charset.Charset; import java.util.Collection; import java.util.Collections; @@ -14,18 +17,18 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufHolder; import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.ServerCookieDecoder; -import static io.netty.handler.codec.http.HttpHeaders.Names.COOKIE; +import static io.netty.handler.codec.http.HttpHeaderNames.COOKIE; /** * Static utility/helper methods for dealing with HTTP stuff (requests, responses, and all the associated tidbits). @@ -50,15 +53,18 @@ protected HttpUtils() { /* do nothing */ } /** * @return The path portion of the given URI (i.e. everything before the '?' of a query string). For example if you * pass in {@code /my/uri?foo=bar} then this method will return {@code /my/uri}. If there is no query string in the - * URI then the URI will be returned unchanged. + * URI then the URI will be returned unchanged. Will never return null - this method will return empty string "" + * if passed null or empty string. */ - public static String extractPath(String uri) { - if (uri == null) + public static @NotNull String extractPath(@Nullable String uri) { + if (uri == null) { return ""; + } int pathEndPos = uri.indexOf('?'); - if (pathEndPos < 0) + if (pathEndPos < 0) { return uri; + } return uri.substring(0, pathEndPos); } @@ -68,16 +74,19 @@ public static String extractPath(String uri) { * you pass in {@code /my/uri?foo=bar} then this method will return {@code foo=bar}. If there is no query string in * the URI then this will return null. */ - public static String extractQueryString(String uri) { - if (uri == null) + public static @Nullable String extractQueryString(@Nullable String uri) { + if (uri == null) { return null; + } int questionMarkPos = uri.indexOf('?'); - if (questionMarkPos < 0) + if (questionMarkPos < 0) { return null; + } - if ((questionMarkPos + 1) >= uri.length()) + if ((questionMarkPos + 1) >= uri.length()) { return null; + } return uri.substring(questionMarkPos + 1); } @@ -90,13 +99,18 @@ public static String extractQueryString(String uri) { * * @return The encoding specified in the header or the default Charset if not specified. **/ - public static Charset determineCharsetFromContentType(HttpHeaders headers, Charset def) { - if (headers == null) + public static @NotNull Charset determineCharsetFromContentType( + @Nullable HttpHeaders headers, + @NotNull Charset def + ) { + if (headers == null) { return def; + } - String contentTypeHeader = headers.get(HttpHeaders.Names.CONTENT_TYPE); - if (contentTypeHeader == null) + String contentTypeHeader = headers.get(HttpHeaderNames.CONTENT_TYPE); + if (contentTypeHeader == null) { return def; + } String charset; Matcher m = CONTENT_TYPE_CHARSET_EXTRACTOR_PATTERN.matcher(contentTypeHeader); @@ -114,22 +128,30 @@ public static Charset determineCharsetFromContentType(HttpHeaders headers, Chars return def; } - public static List extractContentChunks(HttpRequest request) { - if (!(request instanceof HttpContent)) + public static @Nullable List extractContentChunks(@Nullable HttpRequest request) { + if (!(request instanceof HttpContent)) { return null; + } return Collections.singletonList((HttpContent) request); } - public static String convertRawBytesToString(Charset contentCharset, byte[] rawBytes) { - if (contentCharset == null) + public static @Nullable String convertRawBytesToString( + @NotNull Charset contentCharset, + @Nullable byte[] rawBytes + ) { + //noinspection ConstantConditions + if (contentCharset == null) { throw new IllegalArgumentException("contentCharset cannot be null"); + } - if (rawBytes == null) + if (rawBytes == null) { return null; + } - if (rawBytes.length == 0) + if (rawBytes.length == 0) { return ""; + } String rawString = new String(rawBytes, contentCharset); // UTF-16 can insert byte order mark characters when splicing together multiple chunks. Remove them @@ -137,24 +159,30 @@ public static String convertRawBytesToString(Charset contentCharset, byte[] rawB return rawString; } - public static String convertContentChunksToRawString(Charset contentCharset, - Collection contentChunks) { + public static @Nullable String convertContentChunksToRawString( + @NotNull Charset contentCharset, + @Nullable Collection contentChunks + ) { byte[] rawBytes = convertContentChunksToRawBytes(contentChunks); - if (rawBytes == null) + if (rawBytes == null) { return null; + } return convertRawBytesToString(contentCharset, rawBytes); } - public static byte[] convertContentChunksToRawBytes(Collection contentChunks) { - if (contentChunks == null || contentChunks.size() == 0) + public static @Nullable byte[] convertContentChunksToRawBytes( + @Nullable Collection contentChunks + ) { + if (contentChunks == null || contentChunks.size() == 0) { return null; + } ByteBuf[] chunkByteBufs = contentChunks.stream().map(ByteBufHolder::content).toArray(ByteBuf[]::new); - int totalNumBytes = - contentChunks.stream().collect(Collectors.summingInt(chunk -> chunk.content().readableBytes())); - if (totalNumBytes == 0) + int totalNumBytes = contentChunks.stream().mapToInt(chunk -> chunk.content().readableBytes()).sum(); + if (totalNumBytes == 0) { return null; + } byte[] comboBytes = new byte[totalNumBytes]; int bytesWrittenSoFar = 0; @@ -167,35 +195,42 @@ public static byte[] convertContentChunksToRawBytes(Collection cont return comboBytes; } - public static HttpHeaders extractTrailingHeadersIfPossible(HttpRequest request) { - if (!(request instanceof LastHttpContent)) + public static @Nullable HttpHeaders extractTrailingHeadersIfPossible(@Nullable HttpRequest request) { + if (!(request instanceof LastHttpContent)) { return null; + } return ((LastHttpContent) request).trailingHeaders(); } - public static Set extractCookies(HttpRequest request) { - Set cookies = new HashSet<>(); + public static @NotNull Set extractCookies(@Nullable HttpRequest request) { + if (request == null) { + return Collections.emptySet(); + } HttpHeaders trailingHeaders = extractTrailingHeadersIfPossible(request); String cookieString = request.headers().get(COOKIE); - if (cookieString == null && trailingHeaders != null) + if (cookieString == null && trailingHeaders != null) { cookieString = trailingHeaders.get(COOKIE); + } - if (cookieString != null) - cookies.addAll(ServerCookieDecoder.LAX.decode(cookieString)); + if (cookieString != null) { + return new HashSet<>(ServerCookieDecoder.LAX.decode(cookieString)); + } - return cookies; + return Collections.emptySet(); } - public static Map decodePathParams(String pathTemplate, String path) { + public static @NotNull Map decodePathParams(@NotNull String pathTemplate, @NotNull String path) { // Ignore trailing slashes on either the template or path. - if (pathTemplate.endsWith("/")) + if (pathTemplate.endsWith("/")) { pathTemplate = pathTemplate.substring(0, pathTemplate.length() - 1); + } - if (path.endsWith("/")) + if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); + } if (!pathParamExtractor.match(pathTemplate, path)) { throw new PathParameterMatchingException( @@ -206,10 +241,16 @@ public static Map decodePathParams(String pathTemplate, String p } - public static String replaceUriPathVariables(RequestInfo request, String downstreamDestinationUriPath) { - for (String pathParam : request.getPathParams().keySet()) { - downstreamDestinationUriPath = - downstreamDestinationUriPath.replaceAll("\\{" + pathParam + "\\}", request.getPathParam(pathParam)); + public static @NotNull String replaceUriPathVariables( + @NotNull RequestInfo request, + @NotNull String downstreamDestinationUriPath + ) { + for (Map.Entry pathParamKeyValue : request.getPathParams().entrySet()) { + String pathParamKey = pathParamKeyValue.getKey(); + String pathParamValue = pathParamKeyValue.getValue(); + downstreamDestinationUriPath = downstreamDestinationUriPath.replaceAll( + "\\{" + pathParamKey + "}", pathParamValue + ); } return downstreamDestinationUriPath; @@ -219,12 +260,16 @@ public static boolean isMaxRequestSizeValidationDisabled(int configuredMaxReques return configuredMaxRequestSize <= 0; } - public static int getConfiguredMaxRequestSize(Endpoint endpoint, int globalConfiguredMaxRequestSizeInBytes) { + public static int getConfiguredMaxRequestSize( + @Nullable Endpoint endpoint, + int globalConfiguredMaxRequestSizeInBytes + ) { //if the endpoint is null or the endpoint is not overriding, we should return the globally configured value - if (endpoint == null || endpoint.maxRequestSizeInBytesOverride() == null) { + Integer endpointMaxSizeOverride = (endpoint == null) ? null : endpoint.maxRequestSizeInBytesOverride(); + if (endpointMaxSizeOverride == null) { return globalConfiguredMaxRequestSizeInBytes; } - return endpoint.maxRequestSizeInBytesOverride(); + return endpointMaxSizeOverride; } } diff --git a/riposte-spi/src/main/java/com/nike/riposte/util/MainClassUtils.java b/riposte-spi/src/main/java/com/nike/riposte/util/MainClassUtils.java index 684bdaee..743f3c97 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/util/MainClassUtils.java +++ b/riposte-spi/src/main/java/com/nike/riposte/util/MainClassUtils.java @@ -2,6 +2,8 @@ import com.nike.internal.util.Pair; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,10 +88,11 @@ public static void setupJbossLoggingToUseSlf4j() { * link - we're just reusing the concept. If appId or environment cannot be extracted from the System properties * then a {@link IllegalStateException} will be thrown. */ - public static Pair getAppIdAndEnvironmentFromSystemProperties() { + public static @NotNull Pair getAppIdAndEnvironmentFromSystemProperties() { String appIdToUse = System.getProperty("@appId"); - if (appIdToUse == null) + if (appIdToUse == null) { appIdToUse = System.getProperty("archaius.deployment.applicationId"); + } if (appIdToUse == null) { throw new IllegalStateException( @@ -100,8 +103,9 @@ public static Pair getAppIdAndEnvironmentFromSystemProperties() } String environmentToUse = System.getProperty("@environment"); - if (environmentToUse == null) + if (environmentToUse == null) { environmentToUse = System.getProperty("archaius.deployment.environment"); + } if (environmentToUse == null) { throw new IllegalStateException( @@ -142,21 +146,25 @@ public static Pair getAppIdAndEnvironmentFromSystemProperties() * A function that accepts a property key as an argument and returns the property value as a string. The {@code * hasPropertyFunction} arg will be used to guarantee the property exists before this function is called. */ - public static void setupNettyLeakDetectionLevel(Function hasPropertyFunction, - Function propertyExtractionFunction) { + public static void setupNettyLeakDetectionLevel( + @NotNull Function hasPropertyFunction, + @NotNull Function propertyExtractionFunction + ) { String nettyLeakDetectionLevel = System.getProperty(NETTY_LEAK_DETECTION_LEVEL_SYSTEM_PROP_KEY); if (nettyLeakDetectionLevel == null) { // No system property. See if it's specified in the app properties. - if (hasPropertyFunction.apply(NETTY_LEAK_DETECTION_LEVEL_SYSTEM_PROP_KEY)) + if (hasPropertyFunction.apply(NETTY_LEAK_DETECTION_LEVEL_SYSTEM_PROP_KEY)) { nettyLeakDetectionLevel = propertyExtractionFunction.apply(NETTY_LEAK_DETECTION_LEVEL_SYSTEM_PROP_KEY); - else if (hasPropertyFunction.apply(NETTY_LEAK_DETECTION_LEVEL_APP_PROP_KEY)) + } + else if (hasPropertyFunction.apply(NETTY_LEAK_DETECTION_LEVEL_APP_PROP_KEY)) { nettyLeakDetectionLevel = propertyExtractionFunction.apply(NETTY_LEAK_DETECTION_LEVEL_APP_PROP_KEY); + } } if (nettyLeakDetectionLevel == null) { logger.info("No netty leak detection level specified in System or application properties. " + "The default Netty behavior will be used. netty_leak_detection_level_used={}", - String.valueOf(ResourceLeakDetector.getLevel()) + ResourceLeakDetector.getLevel() ); } else { @@ -182,10 +190,12 @@ else if (hasPropertyFunction.apply(NETTY_LEAK_DETECTION_LEVEL_APP_PROP_KEY)) * @param applicationPropertyNames * The collection of property names for all the properties associated with this application. */ - public static void logApplicationPropertiesIfDebugActionsEnabled(Function hasPropertyFunction, - Function propertyExtractionFunction, - Collection applicationPropertyNames, - boolean forceLogging) { + public static void logApplicationPropertiesIfDebugActionsEnabled( + @NotNull Function hasPropertyFunction, + @NotNull Function propertyExtractionFunction, + @NotNull Collection applicationPropertyNames, + boolean forceLogging + ) { boolean debugActionsEnabledInAppProps = (hasPropertyFunction.apply(DEBUG_ACTIONS_ENABLED_PROP_KEY) && Boolean.valueOf(propertyExtractionFunction.apply(DEBUG_ACTIONS_ENABLED_PROP_KEY))); @@ -216,9 +226,10 @@ public static void logApplicationPropertiesIfDebugActionsEnabled(Function * See http://www.slf4j.org/codes.html#replay for more info. */ - public static T executeCallableWithLoggingReplayProtection(Callable callable, - long delayInMillisIfExceptionOccurs) - throws Exception { + public static T executeCallableWithLoggingReplayProtection( + @NotNull Callable callable, + long delayInMillisIfExceptionOccurs + ) throws Exception { try { return callable.call(); } @@ -270,7 +281,7 @@ public static T executeCallableWithLoggingReplayProtection(Callable calla *

    This method will use the {@link #DEFAULT_CRASH_DELAY_MILLIS} as the crash delay value. If you want to specify * a different value you can call {@link #executeCallableWithLoggingReplayProtection(Callable, long)} instead. */ - public static T executeCallableWithLoggingReplayProtection(Callable callable) throws Exception { + public static T executeCallableWithLoggingReplayProtection(@NotNull Callable callable) throws Exception { return executeCallableWithLoggingReplayProtection(callable, DEFAULT_CRASH_DELAY_MILLIS); } @@ -279,10 +290,16 @@ public static T executeCallableWithLoggingReplayProtection(Callable calla * to both {@code System.err} and the given logger. If the given exception is not null then its stack trace will be * included in any output. */ - private static void outputExceptionalShutdownMessage(String message, Throwable ex, Logger logger) { + @SuppressWarnings("SameParameterValue") + private static void outputExceptionalShutdownMessage( + @NotNull String message, + @Nullable Throwable ex, + @NotNull Logger logger + ) { System.err.println("STARTUP ERROR: " + message); - if (ex != null) + if (ex != null) { ex.printStackTrace(); + } logger.error(message, ex); } } diff --git a/riposte-spi/src/main/java/com/nike/riposte/util/Matcher.java b/riposte-spi/src/main/java/com/nike/riposte/util/Matcher.java index 3f9308a3..3ca2ba0a 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/util/Matcher.java +++ b/riposte-spi/src/main/java/com/nike/riposte/util/Matcher.java @@ -2,6 +2,8 @@ import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; + import java.util.Collection; import java.util.Optional; @@ -17,31 +19,31 @@ public interface Matcher { /** * @return The collection of explicitly defined HTTP methods that this instance matches against. NOTE: This is just - * the *explicitly defined* HTTP methods, so it's entirely possible for this to be null/empty and still have {@link + * the *explicitly defined* HTTP methods, so it's entirely possible for this to be empty and still have {@link * #isMatchAllMethods()} return true. This means {@link #isMatchAllMethods()} should be checked first, and this - * method is only relevant if {@link #isMatchAllMethods()} returns false. + * method is only relevant if {@link #isMatchAllMethods()} returns false. This should never return null. */ - Collection matchingMethods(); + @NotNull Collection matchingMethods(); /** * @return The path templates that this instance matches against. This is for informational purposes. Rely on - * matchesPath to find the path that matches your request. + * matchesPath to find the path that matches your request. This should never return null. */ - Collection matchingPathTemplates(); + @NotNull Collection matchingPathTemplates(); /** * @return An optional string of the pattern matched if this instance matches the path from the given request. If * this returns a string then {@link #matchesMethod(RequestInfo)} should be checked next to see if the endpoint * attached to this instance should handle the request. If this returns empty then the endpoint attached to this - * instance should not handle the request. + * instance should not handle the request. This should never return null. */ - Optional matchesPath(RequestInfo request); + @NotNull Optional matchesPath(@NotNull RequestInfo request); /** * @return true if this instance handles the HTTP method from the given request, false otherwise. If this returns * false then the endpoint attached to this instance should not handle the request. */ - boolean matchesMethod(RequestInfo request); + boolean matchesMethod(@NotNull RequestInfo request); /** * @return true if this instance wants to match all HTTP methods, false otherwise. If this returns true then {@link @@ -52,21 +54,27 @@ public interface Matcher { /** * Convenience function to create a SingleMatcher from a pattern */ - static Matcher match(String matchingPathTemplate) { + static @NotNull Matcher match(@NotNull String matchingPathTemplate) { return SingleMatcher.match(matchingPathTemplate); } /** * Convenience function to create a SingleMatcher from a pattern and varargs of HttpMethod */ - static Matcher match(String matchingPathTemplate, HttpMethod... matchingMethods) { + static @NotNull Matcher match( + @NotNull String matchingPathTemplate, + @NotNull HttpMethod... matchingMethods + ) { return SingleMatcher.match(matchingPathTemplate, matchingMethods); } /** * Convenience function to create a SingleMatcher from a pattern and collection of HttpMethods */ - static Matcher match(String matchingPathTemplate, Collection matchingMethods) { + static @NotNull Matcher match( + @NotNull String matchingPathTemplate, + @NotNull Collection matchingMethods + ) { return SingleMatcher.match(matchingPathTemplate, matchingMethods); } @@ -74,7 +82,7 @@ static Matcher match(String matchingPathTemplate, Collection matchin * Convenience function to create a MultiMatcher from a Collection of patterns. First match is used so and ordered * collection may be needed if a path could match a more general pattern. */ - static Matcher multiMatch(Collection matchingPathTemplates) { + static @NotNull Matcher multiMatch(@NotNull Collection matchingPathTemplates) { return MultiMatcher.match(matchingPathTemplates); } @@ -82,7 +90,10 @@ static Matcher multiMatch(Collection matchingPathTemplates) { * Convenience function to create a MultiMatcher from a Collection of patterns and varargs of HttpMethod. * First match is used so and ordered collection may be needed if a path could match a more general pattern. */ - static Matcher multiMatch(Collection matchingPathTemplates, HttpMethod... matchingMethods) { + static @NotNull Matcher multiMatch( + @NotNull Collection matchingPathTemplates, + @NotNull HttpMethod... matchingMethods + ) { return MultiMatcher.match(matchingPathTemplates, matchingMethods); } @@ -90,7 +101,10 @@ static Matcher multiMatch(Collection matchingPathTemplates, HttpMethod.. * Convenience function to create a MultiMatcher from a Collection of patterns and collection of HttpMethods. * First match is used so and ordered collection may be needed if a path could match a more general pattern. */ - static Matcher multiMatch(Collection matchingPathTemplates, Collection matchingMethods) { + static @NotNull Matcher multiMatch( + @NotNull Collection matchingPathTemplates, + @NotNull Collection matchingMethods + ) { return MultiMatcher.match(matchingPathTemplates, matchingMethods); } } diff --git a/riposte-spi/src/main/java/com/nike/riposte/util/MatcherUtil.java b/riposte-spi/src/main/java/com/nike/riposte/util/MatcherUtil.java index 34f2c0bf..5f640f2c 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/util/MatcherUtil.java +++ b/riposte-spi/src/main/java/com/nike/riposte/util/MatcherUtil.java @@ -1,12 +1,14 @@ package com.nike.riposte.util; +import org.jetbrains.annotations.NotNull; + @SuppressWarnings("WeakerAccess") public class MatcherUtil { // Intentionally protected - use the static methods protected MatcherUtil() { /* do nothing */ } - public static String stripEndSlash(String path) { + public static String stripEndSlash(@NotNull String path) { if (path.endsWith("/")) { return path.substring(0, path.length() - 1); } else { diff --git a/riposte-spi/src/main/java/com/nike/riposte/util/MultiMatcher.java b/riposte-spi/src/main/java/com/nike/riposte/util/MultiMatcher.java index 8d2cc42f..f3e0f881 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/util/MultiMatcher.java +++ b/riposte-spi/src/main/java/com/nike/riposte/util/MultiMatcher.java @@ -2,6 +2,8 @@ import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -30,12 +32,16 @@ public class MultiMatcher implements Matcher { protected static final AntPathMatcher pathParamExtractor = new AntPathMatcher(); - protected final Collection matchingPathTemplates; - protected final Collection matchingMethods; + protected final @NotNull Collection matchingPathTemplates; + protected final @NotNull Collection matchingMethods; protected final boolean matchAllMethods; - protected MultiMatcher(Collection matchingPathTemplates, Collection matchingMethods, - boolean matchAllMethods) { + @SuppressWarnings("ConstantConditions") + protected MultiMatcher( + @NotNull Collection matchingPathTemplates, + @NotNull Collection matchingMethods, + boolean matchAllMethods + ) { // If the path template doesn't start with a forward slash it has no hope of ever matching any incoming request. if (matchingPathTemplates == null || matchingPathTemplates.isEmpty() @@ -45,8 +51,9 @@ protected MultiMatcher(Collection matchingPathTemplates, Collection matchingPathTemplates, Collection matchingPathTemplates) { + public static @NotNull MultiMatcher match(@NotNull Collection matchingPathTemplates) { return new MultiMatcher(matchingPathTemplates, Collections.emptyList(), true); } /** * @return A new multi-matcher with the given path templates that match the given HTTP methods. */ - public static MultiMatcher match(Collection matchingPathTemplates, HttpMethod... matchingMethods) { + public static @NotNull MultiMatcher match( + @NotNull Collection matchingPathTemplates, + @NotNull HttpMethod... matchingMethods + ) { + //noinspection ConstantConditions if (matchingMethods == null || matchingMethods.length == 0) { throw new IllegalArgumentException("matchingMethods cannot be null or empty. If you want to match all " + "methods use the single-arg match(Collection) method."); @@ -78,7 +89,11 @@ public static MultiMatcher match(Collection matchingPathTemplates, HttpM /** * @return A new multi-matcher with the given path templates that match the given HTTP methods. */ - public static MultiMatcher match(Collection matchingPathTemplates, Collection matchingMethods) { + public static @NotNull MultiMatcher match( + @NotNull Collection matchingPathTemplates, + @NotNull Collection matchingMethods + ) { + //noinspection ConstantConditions if (matchingMethods == null || matchingMethods.isEmpty()) { throw new IllegalArgumentException("matchingMethods cannot be null or empty. If you want to match all " + "methods use the single-arg match(Collection) method."); @@ -91,14 +106,14 @@ public static MultiMatcher match(Collection matchingPathTemplates, Colle * {@inheritDoc} */ @Override - public Collection matchingMethods() { + public @NotNull Collection matchingMethods() { return matchingMethods; } /** * {@inheritDoc} */ - public Collection matchingPathTemplates() { + public @NotNull Collection matchingPathTemplates() { return matchingPathTemplates; } @@ -109,7 +124,8 @@ public Collection matchingPathTemplates() { * multiple patterns. */ @Override - public Optional matchesPath(RequestInfo request) { + public @NotNull Optional matchesPath(@NotNull RequestInfo request) { + //noinspection ConstantConditions if (request == null || request.getPath() == null) return Optional.empty(); @@ -127,11 +143,11 @@ public Optional matchesPath(RequestInfo request) { * {@inheritDoc} */ @Override - public boolean matchesMethod(RequestInfo request) { + public boolean matchesMethod(@NotNull RequestInfo request) { if (matchAllMethods) return true; - //noinspection SimplifiableIfStatement + //noinspection ConstantConditions if (request == null || request.getMethod() == null) return false; diff --git a/riposte-spi/src/main/java/com/nike/riposte/util/SingleMatcher.java b/riposte-spi/src/main/java/com/nike/riposte/util/SingleMatcher.java index ecee7a3e..79173718 100644 --- a/riposte-spi/src/main/java/com/nike/riposte/util/SingleMatcher.java +++ b/riposte-spi/src/main/java/com/nike/riposte/util/SingleMatcher.java @@ -2,6 +2,8 @@ import com.nike.riposte.server.http.RequestInfo; +import org.jetbrains.annotations.NotNull; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -28,22 +30,28 @@ public class SingleMatcher implements Matcher { protected static final AntPathMatcher pathParamExtractor = new AntPathMatcher(); - protected final Collection matchingMethods; - protected final String matchingPathTemplate; + protected final @NotNull Collection matchingMethods; + protected final @NotNull String matchingPathTemplate; protected final boolean matchAllMethods; - protected final Collection matchingPathTemplates; + protected final @NotNull Collection matchingPathTemplates; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - protected final Optional cachedMatchesPathResponse; - - public SingleMatcher(String matchingPathTemplate, Collection matchingMethods, boolean matchAllMethods) { + protected final @NotNull Optional cachedMatchesPathResponse; + + @SuppressWarnings("ConstantConditions") + public SingleMatcher( + @NotNull String matchingPathTemplate, + @NotNull Collection matchingMethods, + boolean matchAllMethods + ) { // If the path template doesn't start with a forward slash it has no hope of ever matching any incoming request. if (matchingPathTemplate == null || !matchingPathTemplate.startsWith("/")) { throw new IllegalArgumentException("matchingPathTemplate cannot be null and must start with a forward " + "slash '/'"); } - if (matchingMethods == null) + if (matchingMethods == null) { throw new IllegalArgumentException("matchingMethods cannot be null"); + } matchingPathTemplate = MatcherUtil.stripEndSlash(matchingPathTemplate); @@ -58,14 +66,18 @@ public SingleMatcher(String matchingPathTemplate, Collection matchin * @return A new single matcher with the given path template that matches all HTTP methods ({@link * #isMatchAllMethods()} will return true). */ - public static SingleMatcher match(String matchingPathTemplate) { + public static @NotNull SingleMatcher match(@NotNull String matchingPathTemplate) { return new SingleMatcher(matchingPathTemplate, Collections.emptyList(), true); } /** * @return A new single matcher with the given path template that matches the given HTTP methods. */ - public static SingleMatcher match(String matchingPathTemplate, HttpMethod... matchingMethods) { + public static @NotNull SingleMatcher match( + @NotNull String matchingPathTemplate, + @NotNull HttpMethod... matchingMethods + ) { + //noinspection ConstantConditions if (matchingMethods == null || matchingMethods.length == 0) { throw new IllegalArgumentException("matchingMethods cannot be null or empty. If you want to match all " + "methods use the single-arg match(String) method."); @@ -77,7 +89,11 @@ public static SingleMatcher match(String matchingPathTemplate, HttpMethod... mat /** * @return A new single matcher with the given path template that matches the given HTTP methods. */ - public static SingleMatcher match(String matchingPathTemplate, Collection matchingMethods) { + public static @NotNull SingleMatcher match( + @NotNull String matchingPathTemplate, + @NotNull Collection matchingMethods + ) { + //noinspection ConstantConditions if (matchingMethods == null || matchingMethods.isEmpty()) { throw new IllegalArgumentException("matchingMethods cannot be null or empty. If you want to match all " + "methods use the single-arg match(String) method."); @@ -89,14 +105,14 @@ public static SingleMatcher match(String matchingPathTemplate, Collection matchingMethods() { + public @NotNull Collection matchingMethods() { return matchingMethods; } /** * {@inheritDoc} */ - public Collection matchingPathTemplates() { + public @NotNull Collection matchingPathTemplates() { return matchingPathTemplates; } @@ -110,7 +126,8 @@ public boolean isMatchAllMethods() { /** * {@inheritDoc} */ - public Optional matchesPath(RequestInfo request) { + public @NotNull Optional matchesPath(@NotNull RequestInfo request) { + //noinspection ConstantConditions if (request == null || request.getPath() == null) return Optional.empty(); @@ -128,11 +145,11 @@ public Optional matchesPath(RequestInfo request) { /** * {@inheritDoc} */ - public boolean matchesMethod(RequestInfo request) { + public boolean matchesMethod(@NotNull RequestInfo request) { if (matchAllMethods) return true; - //noinspection SimplifiableIfStatement + //noinspection SimplifiableIfStatement,ConstantConditions if (request == null || request.getMethod() == null) return false; diff --git a/riposte-spi/src/test/groovy/com/nike/riposte/server/logging/AccessLoggerSpec.groovy b/riposte-spi/src/test/groovy/com/nike/riposte/server/logging/AccessLoggerSpec.groovy index 198a9209..1e4606b7 100644 --- a/riposte-spi/src/test/groovy/com/nike/riposte/server/logging/AccessLoggerSpec.groovy +++ b/riposte-spi/src/test/groovy/com/nike/riposte/server/logging/AccessLoggerSpec.groovy @@ -5,6 +5,8 @@ import com.nike.riposte.server.http.RequestInfo import com.nike.riposte.server.http.ResponseInfo import com.nike.riposte.server.http.impl.FullResponseInfo import io.netty.handler.codec.http.* +import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.Nullable import spock.lang.Specification import spock.lang.Unroll import uk.org.lidalia.slf4jext.Level @@ -14,8 +16,12 @@ import uk.org.lidalia.slf4jtest.TestLoggerFactory import java.util.concurrent.CompletableFuture import java.util.function.Consumer -import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH -import static io.netty.handler.codec.http.HttpHeaders.Names.TRANSFER_ENCODING +import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE +import static io.netty.handler.codec.http.HttpHeaderNames.REFERER +import static io.netty.handler.codec.http.HttpHeaderNames.USER_AGENT +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH +import static io.netty.handler.codec.http.HttpHeaderNames.TRANSFER_ENCODING class AccessLoggerSpec extends Specification { @@ -27,8 +33,8 @@ class AccessLoggerSpec extends Specification { and: "we've mocked the request object" RequestInfo requestMock = Mock(RequestInfo) requestMock.getHeaders() >> Mock(DefaultHttpHeaders) - requestMock.getHeaders().get("Referer") >> "myReferer" - requestMock.getHeaders().get("User-Agent") >> "myUserAgent" + requestMock.getHeaders().get(REFERER) >> "myReferer" + requestMock.getHeaders().get(USER_AGENT) >> "myUserAgent" requestMock.getMethod() >> HttpMethod.GET requestMock.getUri() >> "/test" requestMock.getProtocolVersion() >> HttpVersion.HTTP_1_1 @@ -64,8 +70,8 @@ class AccessLoggerSpec extends Specification { and: "we've mocked the request object" RequestInfo requestMock = Mock(RequestInfo) requestMock.getHeaders() >> Mock(DefaultHttpHeaders) - requestMock.getHeaders().get("Referer") >> "myReferer" - requestMock.getHeaders().get("User-Agent") >> "myUserAgent" + requestMock.getHeaders().get(REFERER) >> "myReferer" + requestMock.getHeaders().get(USER_AGENT) >> "myUserAgent" requestMock.getMethod() >> HttpMethod.GET requestMock.getUri() >> "/test" requestMock.getProtocolVersion() >> HttpVersion.HTTP_1_1 @@ -83,15 +89,20 @@ class AccessLoggerSpec extends Specification { given: "the AccessLogger object that also returns some custom log message extras" AccessLogger accessLogger = new AccessLogger() { @Override - protected List> customApplicationLogMessageExtras(RequestInfo request, HttpResponse finalResponseObject, ResponseInfo responseInfo, Long elapsedTimeMillis) { + protected @Nullable List> customApplicationLogMessageExtras( + @NotNull RequestInfo request, + @Nullable HttpResponse finalResponseObject, + @Nullable ResponseInfo responseInfo, + @Nullable Long elapsedTimeMillis + ) { return Arrays.asList(Pair.of("foo", "bar"), Pair.of("whee", "yay")) } } and: "we've mocked the request object" HttpHeaders headersMock = Mock(DefaultHttpHeaders) - headersMock.get("Accept") >> "application/json" - headersMock.get("Content-Type") >> "application/json;charset=utf-8" - headersMock.get("Referer") >> "test" + headersMock.get(ACCEPT) >> "application/json" + headersMock.get(CONTENT_TYPE) >> "application/json;charset=utf-8" + headersMock.get(REFERER) >> "test" headersMock.get("X-B3-Sampled") >> "X-B3-SampledMock" headersMock.get("X-B3-SpanId") >> "X-B3-SpanIdMock" headersMock.get("X-B3-SpanName") >> "X-B3-SpanNameMock" @@ -103,8 +114,8 @@ class AccessLoggerSpec extends Specification { requestMock.getRawContentLengthInBytes() >> 19 and: "we've mocked the response object" HttpHeaders responseHeadersMock = Mock(HttpHeaders) - responseHeadersMock.get("Content-Length") >> "Content-LengthMock" - responseHeadersMock.get("Transfer-Encoding") >> "Transfer-EncodingMock" + responseHeadersMock.get(CONTENT_LENGTH) >> "Content-LengthMock" + responseHeadersMock.get(TRANSFER_ENCODING) >> "Transfer-EncodingMock" responseHeadersMock.get("X-B3-TraceId") >> "X-B3-TraceId-ResMock" responseHeadersMock.get("error_uid") >> "error_uidMock" ResponseInfo responseMock = new FullResponseInfo(null, 210, responseHeadersMock, null, null, null, false) @@ -308,8 +319,8 @@ class AccessLoggerSpec extends Specification { and: "we've mocked the request object" RequestInfo requestMock = Mock(RequestInfo) requestMock.getHeaders() >> Mock(DefaultHttpHeaders) - requestMock.getHeaders().get("Referer") >> "myReferer" - requestMock.getHeaders().get("User-Agent") >> "myUserAgent" + requestMock.getHeaders().get(REFERER) >> "myReferer" + requestMock.getHeaders().get(USER_AGENT) >> "myUserAgent" requestMock.getMethod() >> HttpMethod.GET requestMock.getUri() >> "/test" requestMock.getProtocolVersion() >> HttpVersion.HTTP_1_1 @@ -329,8 +340,8 @@ class AccessLoggerSpec extends Specification { and: "we've mocked the request object" RequestInfo requestMock = Mock(RequestInfo) requestMock.getHeaders() >> Mock(DefaultHttpHeaders) - requestMock.getHeaders().get("Referer") >> "myReferer" - requestMock.getHeaders().get("User-Agent") >> "myUserAgent" + requestMock.getHeaders().get(REFERER) >> "myReferer" + requestMock.getHeaders().get(USER_AGENT) >> "myUserAgent" requestMock.getMethod() >> HttpMethod.GET requestMock.getUri() >> "/test" requestMock.getProtocolVersion() >> HttpVersion.HTTP_1_1 @@ -347,8 +358,8 @@ class AccessLoggerSpec extends Specification { and: "we've mocked the request object" RequestInfo requestMock = Mock(RequestInfo) requestMock.getHeaders() >> Mock(DefaultHttpHeaders) - requestMock.getHeaders().get("Referer") >> "myReferer" - requestMock.getHeaders().get("User-Agent") >> "myUserAgent" + requestMock.getHeaders().get(REFERER) >> "myReferer" + requestMock.getHeaders().get(USER_AGENT) >> "myUserAgent" requestMock.getMethod() >> null requestMock.getUri() >> "/test" requestMock.getProtocolVersion() >> HttpVersion.HTTP_1_1 @@ -367,8 +378,8 @@ class AccessLoggerSpec extends Specification { and: "we've mocked the request object" RequestInfo requestMock = Mock(RequestInfo) requestMock.getHeaders() >> Mock(DefaultHttpHeaders) - requestMock.getHeaders().get("Referer") >> "myReferer" - requestMock.getHeaders().get("User-Agent") >> "myUserAgent" + requestMock.getHeaders().get(REFERER) >> "myReferer" + requestMock.getHeaders().get(USER_AGENT) >> "myUserAgent" requestMock.getMethod() >> HttpMethod.GET requestMock.getUri() >> "/test" requestMock.getProtocolVersion() >> null @@ -404,8 +415,8 @@ class AccessLoggerSpec extends Specification { AccessLogger accessLogger = new AccessLogger() RequestInfo requestMock = Mock(RequestInfo) requestMock.getHeaders() >> Mock(DefaultHttpHeaders) - requestMock.getHeaders().get("Referer") >> null - requestMock.getHeaders().get("User-Agent") >> null + requestMock.getHeaders().get(REFERER) >> null + requestMock.getHeaders().get(USER_AGENT) >> null ResponseInfo responseMock = new FullResponseInfo(null, null, null, null, null, null, false) when: String result = accessLogger.combinedLogFormatPrefix(requestMock, null, responseMock) @@ -419,8 +430,8 @@ class AccessLoggerSpec extends Specification { accessLogger.getLocalIpAddress() >> { throw new UnknownHostException() } RequestInfo requestMock = Mock(RequestInfo) requestMock.getHeaders() >> Mock(DefaultHttpHeaders) - requestMock.getHeaders().get("Referer") >> null - requestMock.getHeaders().get("User-Agent") >> null + requestMock.getHeaders().get(REFERER) >> null + requestMock.getHeaders().get(USER_AGENT) >> null ResponseInfo responseMock = new FullResponseInfo(null, null, null, null, null, null, false) when: String result = accessLogger.combinedLogFormatPrefix(requestMock, null, responseMock) diff --git a/riposte-spi/src/test/java/com/nike/backstopper/handler/riposte/RequestInfoForLoggingRiposteAdapterTest.java b/riposte-spi/src/test/java/com/nike/backstopper/handler/riposte/RequestInfoForLoggingRiposteAdapterTest.java index 708857cf..820dab24 100644 --- a/riposte-spi/src/test/java/com/nike/backstopper/handler/riposte/RequestInfoForLoggingRiposteAdapterTest.java +++ b/riposte-spi/src/test/java/com/nike/backstopper/handler/riposte/RequestInfoForLoggingRiposteAdapterTest.java @@ -1,11 +1,12 @@ package com.nike.backstopper.handler.riposte; -import com.nike.backstopper.handler.RequestInfoForLogging; +import com.nike.backstopper.handler.RequestInfoForLogging.GetBodyException; import com.nike.internal.util.MapBuilder; import com.nike.internal.util.Pair; import com.nike.riposte.server.http.RequestInfo; import com.nike.riposte.server.http.impl.RequestInfoImpl; +import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Test; import org.mockito.internal.util.reflection.Whitebox; @@ -21,12 +22,14 @@ import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; +import static org.assertj.core.api.Assertions.catchThrowable; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -148,7 +151,7 @@ public void getAttributeReturnsNull() { } @Test - public void getBody_delegates_to_request_getRawContent() throws RequestInfoForLogging.GetBodyException { + public void getBody_delegates_to_request_getRawContent() throws GetBodyException { // given String content = UUID.randomUUID().toString(); doReturn(content).when(requestInfoSpy).getRawContent(); @@ -160,4 +163,35 @@ public void getBody_delegates_to_request_getRawContent() throws RequestInfoForLo verify(requestInfoSpy).getRawContent(); assertThat(result, is(content)); } + + @Test + public void getBody_returns_empty_string_if_request_getRawContent_returns_null() throws GetBodyException { + // given + doReturn(null).when(requestInfoSpy).getRawContent(); + + // when + String result = adapter.getBody(); + + // then + verify(requestInfoSpy).getRawContent(); + Assertions.assertThat(result) + .isNotNull() + .isEmpty(); + } + + @Test + public void getBody_throws_GetBodyException_if_request_getRawContent_throws_exception() { + // given + RuntimeException expectedCause = new RuntimeException("intentional test exception"); + doThrow(expectedCause).when(requestInfoSpy).getRawContent(); + + // when + Throwable ex = catchThrowable(() -> adapter.getBody()); + + // then + verify(requestInfoSpy).getRawContent(); + Assertions.assertThat(ex) + .isInstanceOf(GetBodyException.class) + .hasCause(expectedCause); + } } \ No newline at end of file diff --git a/riposte-spi/src/test/java/com/nike/backstopper/model/riposte/ErrorResponseBodyImplTest.java b/riposte-spi/src/test/java/com/nike/backstopper/model/riposte/ErrorResponseBodyImplTest.java index 96e1689e..ed15ae71 100644 --- a/riposte-spi/src/test/java/com/nike/backstopper/model/riposte/ErrorResponseBodyImplTest.java +++ b/riposte-spi/src/test/java/com/nike/backstopper/model/riposte/ErrorResponseBodyImplTest.java @@ -5,13 +5,16 @@ import com.nike.backstopper.model.DefaultErrorContractDTO; import com.nike.backstopper.model.DefaultErrorDTO; +import org.assertj.core.api.Assertions; import org.junit.Test; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; +import static org.assertj.core.api.Assertions.catchThrowable; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; @@ -73,4 +76,51 @@ public void errorResponseViewWrapperConstructorWorks() { ErrorResponseBodyImpl adapter = new ErrorResponseBodyImpl(errorContract); verifyAdapter(adapter, errorUuid, errorsList); } + + @Test + public void copy_constructor_throws_IllegalArgumentException_when_passed_null_DTO() { + // when + Throwable ex = catchThrowable(() -> new ErrorResponseBodyImpl(null)); + + // then + Assertions.assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("The DefaultErrorContractDTO copy arg cannot be null."); + } + + @Test + public void copy_constructor_throws_IllegalArgumentException_when_passed_DTO_with_null_errorId() { + // when + Throwable ex = catchThrowable(() -> new ErrorResponseBodyImpl( + new DefaultErrorContractDTO(null, Collections.emptyList()) + )); + + // then + Assertions.assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("The DefaultErrorContractDTO.error_id value cannot be null."); + } + + @Test + public void double_arg_constructor_throws_IllegalArgumentException_when_passed_null_errorId() { + // when + Throwable ex = catchThrowable(() -> new ErrorResponseBodyImpl(null, Collections.emptyList())); + + // then + Assertions.assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("error_id cannot be null."); + } + + @Test + public void triple_arg_constructor_throws_IllegalArgumentException_when_passed_null_errorId() { + // when + Throwable ex = catchThrowable(() -> new ErrorResponseBodyImpl(null, Collections.emptyList(), null)); + + // then + Assertions.assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("error_id cannot be null."); + } + } \ No newline at end of file diff --git a/riposte-spi/src/test/java/com/nike/backstopper/model/riposte/ErrorResponseInfoImplTest.java b/riposte-spi/src/test/java/com/nike/backstopper/model/riposte/ErrorResponseInfoImplTest.java index 6494ce4c..987405b3 100644 --- a/riposte-spi/src/test/java/com/nike/backstopper/model/riposte/ErrorResponseInfoImplTest.java +++ b/riposte-spi/src/test/java/com/nike/backstopper/model/riposte/ErrorResponseInfoImplTest.java @@ -4,12 +4,15 @@ import com.nike.internal.util.MapBuilder; import com.nike.riposte.server.error.handler.ErrorResponseBody; +import org.assertj.core.api.Assertions; import org.junit.Test; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.catchThrowable; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.sameInstance; @@ -46,8 +49,28 @@ public void constructorWithErrorResponseInfoArgsSetsValues() { @Test public void constructorDefaultsToEmptyHeadersMapIfPassedNull() { - ErrorResponseInfoImpl adapter = new ErrorResponseInfoImpl(null, 42, null); + ErrorResponseInfoImpl adapter = new ErrorResponseInfoImpl(mock(ErrorResponseBody.class), 42, null); assertThat(adapter.headersToAddToResponse, notNullValue()); assertThat(adapter.headersToAddToResponse.isEmpty(), is(true)); } + + @Test + public void kitchen_sink_constructor_throws_IllegalArgumentException_when_passed_null_ErrorResponseBody() { + // when + Throwable ex = catchThrowable(() -> new ErrorResponseInfoImpl(null, 400, Collections.emptyMap())); + + // then + Assertions.assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("errorResponseBody cannot be null."); + } + + @Test + public void backstopper_copy_constructor_throws_NullPointerException_when_passed_null_arg() { + // when + Throwable ex = catchThrowable(() -> new ErrorResponseInfoImpl(null)); + + // then + Assertions.assertThat(ex).isInstanceOf(NullPointerException.class); + } } \ No newline at end of file diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/config/ServerConfigTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/config/ServerConfigTest.java index 0254e24c..3f5d29b6 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/config/ServerConfigTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/config/ServerConfigTest.java @@ -2,6 +2,7 @@ import com.nike.riposte.server.http.Endpoint; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import java.security.cert.CertificateException; @@ -22,7 +23,7 @@ public void default_method_implementations_return_expected_values() throws Certi @SuppressWarnings("Convert2Lambda") ServerConfig defaultImpl = new ServerConfig() { @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return null; } }; diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/config/impl/AppInfoImplTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/config/impl/AppInfoImplTest.java index 7a40e7e6..20c673d1 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/config/impl/AppInfoImplTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/config/impl/AppInfoImplTest.java @@ -1,7 +1,5 @@ package com.nike.riposte.server.config.impl; -import com.nike.riposte.server.config.AppInfo; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -10,6 +8,7 @@ import java.net.UnknownHostException; import java.util.UUID; +import static com.nike.riposte.server.config.AppInfo.UNKNOWN_VALUE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.Mockito.doThrow; @@ -78,15 +77,27 @@ public void constructor_sets_expected_fields() { } @Test - public void deserialization_constructor_sets_everything_to_null() { + public void constructor_sets_fields_to_unknown_when_passed_null() { + // when + AppInfoImpl appInfoImpl = new AppInfoImpl(null, null, null, null); + + // then + assertThat(appInfoImpl.appId).isEqualTo(appInfoImpl.appId()).isEqualTo(UNKNOWN_VALUE); + assertThat(appInfoImpl.environment).isEqualTo(appInfoImpl.environment()).isEqualTo(UNKNOWN_VALUE); + assertThat(appInfoImpl.dataCenter).isEqualTo(appInfoImpl.dataCenter()).isEqualTo(UNKNOWN_VALUE); + assertThat(appInfoImpl.instanceId).isEqualTo(appInfoImpl.instanceId()).isEqualTo(UNKNOWN_VALUE); + } + + @Test + public void deserialization_constructor_sets_everything_to_unknown() { // when AppInfoImpl appInfoImpl = new AppInfoImpl(); // then - assertThat(appInfoImpl.appId).isEqualTo(appInfoImpl.appId()).isNull(); - assertThat(appInfoImpl.environment).isEqualTo(appInfoImpl.environment()).isNull(); - assertThat(appInfoImpl.dataCenter).isEqualTo(appInfoImpl.dataCenter()).isNull(); - assertThat(appInfoImpl.instanceId).isEqualTo(appInfoImpl.instanceId()).isNull(); + assertThat(appInfoImpl.appId).isEqualTo(appInfoImpl.appId()).isEqualTo(UNKNOWN_VALUE); + assertThat(appInfoImpl.environment).isEqualTo(appInfoImpl.environment()).isEqualTo(UNKNOWN_VALUE); + assertThat(appInfoImpl.dataCenter).isEqualTo(appInfoImpl.dataCenter()).isEqualTo(UNKNOWN_VALUE); + assertThat(appInfoImpl.instanceId).isEqualTo(appInfoImpl.instanceId()).isEqualTo(UNKNOWN_VALUE); } private void setAppIdSystemProps(String atAppId, String archaiusDeploymentAppId, String eurekaName) { @@ -208,7 +219,7 @@ public void createLocalInstance_with_appId_uses_UNKNOWN_VALUE_for_local_hostname assertThat(localInstance.appId).isEqualTo(appId); assertThat(localInstance.environment).isEqualTo("local"); assertThat(localInstance.dataCenter).isEqualTo("local"); - assertThat(localInstance.instanceId).isEqualTo(AppInfo.UNKNOWN_VALUE); + assertThat(localInstance.instanceId).isEqualTo(UNKNOWN_VALUE); } finally { Whitebox.setInternalState(instanceForStaticVariableReflectionJunk, localHostnameGetterStaticVariableName, existingLocalHostnameGetter); diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/error/exception/MissingRequiredContentExceptionTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/error/exception/MissingRequiredContentExceptionTest.java index 0d4a186b..f2e58b18 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/error/exception/MissingRequiredContentExceptionTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/error/exception/MissingRequiredContentExceptionTest.java @@ -7,6 +7,8 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import io.netty.handler.codec.http.HttpMethod; + +import org.jetbrains.annotations.NotNull; import org.junit.Test; import org.junit.runner.RunWith; @@ -90,7 +92,7 @@ public void two_arg_constructor_works_as_expected(boolean nullMethod, boolean nu class TestEndpoint implements Endpoint { @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } } diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/error/exception/MultipleMatchingEndpointsExceptionTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/error/exception/MultipleMatchingEndpointsExceptionTest.java index 73e8abbc..086f6bc5 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/error/exception/MultipleMatchingEndpointsExceptionTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/error/exception/MultipleMatchingEndpointsExceptionTest.java @@ -3,6 +3,7 @@ import com.nike.riposte.server.http.Endpoint; import com.nike.riposte.util.Matcher; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import java.util.Arrays; @@ -41,14 +42,14 @@ public void should_honor_constructor_params() { public static class EndpointOne implements Endpoint { @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } } public static class EndpointTwo implements Endpoint { @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } } diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/error/handler/impl/DelegatedErrorResponseBodyTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/error/handler/impl/DelegatedErrorResponseBodyTest.java index 3071226c..37ec0876 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/error/handler/impl/DelegatedErrorResponseBodyTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/error/handler/impl/DelegatedErrorResponseBodyTest.java @@ -5,6 +5,7 @@ import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; /** * Tests the functionality of {@link DelegatedErrorResponseBody}. @@ -29,4 +30,15 @@ public void constructor_sets_fields_as_expected() { assertThat(impl.bodyToSerialize()).isSameAs(someObject); } + @Test + public void constructor_throws_IllegalArgumentException_if_passed_null_errorId() { + // when + Throwable ex = catchThrowable(() -> new DelegatedErrorResponseBody(null, new Object())); + + // then + assertThat(ex) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("errorId cannot be null."); + } + } diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/http/EndpointTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/http/EndpointTest.java index dbd6e683..bfcceefd 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/http/EndpointTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/http/EndpointTest.java @@ -1,9 +1,13 @@ package com.nike.riposte.server.http; -import com.fasterxml.jackson.core.type.TypeReference; import com.nike.riposte.util.Matcher; + +import com.fasterxml.jackson.core.type.TypeReference; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; @@ -61,12 +65,12 @@ public void isRequireRequestContent_returnsExpectedValueBasedOnRequestContentTyp private Endpoint getEndpointWithRequestContentType(Class type) { return new Endpoint() { @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } @Override - public TypeReference requestContentType() { + public @Nullable TypeReference requestContentType() { if (type == null) { return null; } else { diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/http/NonblockingEndpointTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/http/NonblockingEndpointTest.java index a6e153e9..7ad682a0 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/http/NonblockingEndpointTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/http/NonblockingEndpointTest.java @@ -2,6 +2,7 @@ import com.nike.riposte.util.Matcher; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import java.util.concurrent.CompletableFuture; @@ -25,14 +26,16 @@ public void default_method_implementations_return_expected_values() { // given NonblockingEndpoint defaultImpl = new NonblockingEndpoint() { @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return null; } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return null; } }; diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/http/RequestInfoTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/http/RequestInfoTest.java index 20fe65b8..57f54cf4 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/http/RequestInfoTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/http/RequestInfoTest.java @@ -3,6 +3,8 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.Test; import java.nio.charset.Charset; @@ -14,6 +16,7 @@ import java.util.Set; import java.util.UUID; +import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; @@ -140,43 +143,43 @@ public void getPathParam_works_as_expected() { private static class RequestInfoForTesting implements RequestInfo { @Override - public String getUri() { - return null; + public @NotNull String getUri() { + return "fooUri"; } @Override - public String getPath() { - return null; + public @NotNull String getPath() { + return "/fooPath"; } @Override - public HttpMethod getMethod() { - return null; + public @Nullable HttpMethod getMethod() { + return HttpMethod.GET; } @Override - public HttpHeaders getHeaders() { - return null; + public @NotNull HttpHeaders getHeaders() { + return new DefaultHttpHeaders(); } @Override - public HttpHeaders getTrailingHeaders() { - return null; + public @NotNull HttpHeaders getTrailingHeaders() { + return new DefaultHttpHeaders(); } @Override - public QueryStringDecoder getQueryParams() { - return null; + public @NotNull QueryStringDecoder getQueryParams() { + return new QueryStringDecoder(getUri()); } @Override - public Map getPathParams() { - return null; + public @NotNull Map getPathParams() { + return Collections.emptyMap(); } @Override - public RequestInfo setPathParamsBasedOnPathTemplate(String pathTemplate) { - return null; + public @NotNull RequestInfo setPathParamsBasedOnPathTemplate(@NotNull String pathTemplate) { + return this; } @Override @@ -185,17 +188,17 @@ public int getRawContentLengthInBytes() { } @Override - public byte[] getRawContentBytes() { + public @Nullable byte[] getRawContentBytes() { return null; } @Override - public String getRawContent() { + public @Nullable String getRawContent() { return null; } @Override - public T getContent() { + public @Nullable T getContent() { return null; } @@ -205,13 +208,15 @@ public boolean isMultipartRequest() { } @Override - public List getMultipartParts() { + public @Nullable List getMultipartParts() { return null; } @Override - public RequestInfo setupContentDeserializer(ObjectMapper deserializer, TypeReference typeReference) { - return null; + public @NotNull RequestInfo setupContentDeserializer( + @NotNull ObjectMapper deserializer, @NotNull TypeReference typeReference + ) { + return this; } @Override @@ -220,17 +225,17 @@ public boolean isContentDeserializerSetup() { } @Override - public Set getCookies() { - return null; + public @NotNull Set getCookies() { + return Collections.emptySet(); } @Override - public Charset getContentCharset() { - return null; + public @NotNull Charset getContentCharset() { + return DEFAULT_CONTENT_CHARSET; } @Override - public HttpVersion getProtocolVersion() { + public @Nullable HttpVersion getProtocolVersion() { return null; } @@ -240,7 +245,7 @@ public boolean isKeepAliveRequested() { } @Override - public int addContentChunk(HttpContent chunk) { + public int addContentChunk(@NotNull HttpContent chunk) { return 0; } @@ -250,33 +255,29 @@ public boolean isCompleteRequestWithAllChunks() { } @Override - public void releaseAllResources() { - + public void addRequestAttribute(@NotNull String attributeName, @NotNull Object attributeValue) { } @Override - public void releaseContentChunks() { - + public @NotNull Map getRequestAttributes() { + return Collections.emptyMap(); } @Override - public void releaseMultipartData() { - + public void releaseAllResources() { } @Override - public String getPathTemplate() { - return null; + public void releaseContentChunks() { } @Override - public void addRequestAttribute(String attributeName, Object attributeValue) { - + public void releaseMultipartData() { } @Override - public Map getRequestAttributes() { - return null; + public @NotNull String getPathTemplate() { + return ""; } } } \ No newline at end of file diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/http/ResponseInfoTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/http/ResponseInfoTest.java index b80aa8c7..26996b1e 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/http/ResponseInfoTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/http/ResponseInfoTest.java @@ -3,12 +3,15 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.charset.Charset; import java.util.Set; +import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.cookie.Cookie; @@ -40,20 +43,18 @@ public void getHttpStatusCodeWithDefault_works_as_expected(Integer httpStatusCod } private static class ResponseInfoForTesting implements ResponseInfo { - @Override - public Integer getHttpStatusCode() { + public @Nullable Integer getHttpStatusCode() { return null; } @Override - public void setHttpStatusCode(Integer httpStatusCode) { - + public void setHttpStatusCode(@Nullable Integer httpStatusCode) { } @Override - public HttpHeaders getHeaders() { - return null; + public @NotNull HttpHeaders getHeaders() { + return new DefaultHttpHeaders(); } @Override @@ -62,43 +63,40 @@ public boolean isChunkedResponse() { } @Override - public T getContentForFullResponse() { + public @Nullable T getContentForFullResponse() { return null; } @Override - public void setContentForFullResponse(T contentForFullResponse) { + public void setContentForFullResponse(@Nullable T contentForFullResponse) { } @Override - public String getDesiredContentWriterMimeType() { + public @Nullable String getDesiredContentWriterMimeType() { return null; } @Override - public void setDesiredContentWriterMimeType(String desiredContentWriterMimeType) { - + public void setDesiredContentWriterMimeType(@Nullable String desiredContentWriterMimeType) { } @Override - public Charset getDesiredContentWriterEncoding() { + public @Nullable Charset getDesiredContentWriterEncoding() { return null; } @Override - public void setDesiredContentWriterEncoding(Charset desiredContentWriterEncoding) { - + public void setDesiredContentWriterEncoding(@Nullable Charset desiredContentWriterEncoding) { } @Override - public Set getCookies() { + public @Nullable Set getCookies() { return null; } @Override - public void setCookies(Set cookies) { - + public void setCookies(@Nullable Set cookies) { } @Override @@ -112,23 +110,21 @@ public void setPreventCompressedOutput(boolean preventCompressedOutput) { } @Override - public Long getUncompressedRawContentLength() { + public @Nullable Long getUncompressedRawContentLength() { return null; } @Override - public void setUncompressedRawContentLength(Long uncompressedRawContentLength) { - + public void setUncompressedRawContentLength(@Nullable Long uncompressedRawContentLength) { } @Override - public Long getFinalContentLength() { + public @Nullable Long getFinalContentLength() { return null; } @Override - public void setFinalContentLength(Long finalContentLength) { - + public void setFinalContentLength(@Nullable Long finalContentLength) { } @Override @@ -138,7 +134,6 @@ public boolean isResponseSendingStarted() { @Override public void setResponseSendingStarted(boolean responseSendingStarted) { - } @Override @@ -148,7 +143,6 @@ public boolean isResponseSendingLastChunkSent() { @Override public void setResponseSendingLastChunkSent(boolean responseSendingLastChunkSent) { - } @Override @@ -158,7 +152,6 @@ public boolean isForceConnectionCloseAfterResponseSent() { @Override public void setForceConnectionCloseAfterResponseSent(boolean forceConnectionCloseAfterResponseSent) { - } } diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/http/filter/RequestAndResponseFilterTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/http/filter/RequestAndResponseFilterTest.java index e4d39171..8d1b61d7 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/http/filter/RequestAndResponseFilterTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/http/filter/RequestAndResponseFilterTest.java @@ -3,12 +3,10 @@ import com.nike.riposte.server.http.RequestInfo; import com.nike.riposte.server.http.ResponseInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.Test; -import java.security.cert.CertificateException; - -import javax.net.ssl.SSLException; - import io.netty.channel.ChannelHandlerContext; import static org.assertj.core.api.Assertions.assertThat; @@ -23,21 +21,29 @@ public class RequestAndResponseFilterTest { @Test - public void default_method_implementations_behave_as_expected() throws CertificateException, SSLException { + public void default_method_implementations_behave_as_expected() { // given RequestAndResponseFilter defaultImpl = new RequestAndResponseFilter() { @Override - public RequestInfo filterRequestFirstChunkNoPayload(RequestInfo currentRequestInfo, ChannelHandlerContext ctx) { + public @Nullable RequestInfo filterRequestFirstChunkNoPayload( + @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx + ) { return null; } @Override - public RequestInfo filterRequestLastChunkWithFullPayload(RequestInfo currentRequestInfo, ChannelHandlerContext ctx) { + public @Nullable RequestInfo filterRequestLastChunkWithFullPayload( + @NotNull RequestInfo currentRequestInfo, @NotNull ChannelHandlerContext ctx + ) { return null; } @Override - public ResponseInfo filterResponse(ResponseInfo currentResponseInfo, RequestInfo requestInfo, ChannelHandlerContext ctx) { + public @Nullable ResponseInfo filterResponse( + @NotNull ResponseInfo currentResponseInfo, + @NotNull RequestInfo requestInfo, + @NotNull ChannelHandlerContext ctx + ) { return null; } }; diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/http/filter/ShortCircuitingRequestAndResponseFilterTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/http/filter/ShortCircuitingRequestAndResponseFilterTest.java index 6a557715..89339cfc 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/http/filter/ShortCircuitingRequestAndResponseFilterTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/http/filter/ShortCircuitingRequestAndResponseFilterTest.java @@ -4,13 +4,12 @@ import com.nike.riposte.server.http.RequestInfo; import com.nike.riposte.server.http.ResponseInfo; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.Test; -import java.security.cert.CertificateException; import java.util.Optional; -import javax.net.ssl.SSLException; - import io.netty.channel.ChannelHandlerContext; import static org.assertj.core.api.Assertions.assertThat; @@ -25,24 +24,31 @@ public class ShortCircuitingRequestAndResponseFilterTest { @Test - public void default_method_implementations_behave_as_expected() throws CertificateException, SSLException { + public void default_method_implementations_behave_as_expected() { // given ShortCircuitingRequestAndResponseFilter defaultImpl = new ShortCircuitingRequestAndResponseFilter() { @Override - public ResponseInfo filterResponse(ResponseInfo currentResponseInfo, RequestInfo requestInfo, ChannelHandlerContext ctx) { + public @Nullable ResponseInfo filterResponse( + @NotNull ResponseInfo currentResponseInfo, + @NotNull RequestInfo requestInfo, + @NotNull ChannelHandlerContext ctx + ) { return null; } @Override - public Pair, Optional>> filterRequestFirstChunkWithOptionalShortCircuitResponse( - RequestInfo currentRequestInfo, - ChannelHandlerContext ctx) { + public @Nullable Pair, Optional>> filterRequestFirstChunkWithOptionalShortCircuitResponse( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx + ) { return null; } @Override - public Pair, Optional>> filterRequestLastChunkWithOptionalShortCircuitResponse(RequestInfo currentRequestInfo, - ChannelHandlerContext ctx) { + public @Nullable Pair, Optional>> filterRequestLastChunkWithOptionalShortCircuitResponse( + @NotNull RequestInfo currentRequestInfo, + @NotNull ChannelHandlerContext ctx + ) { return null; } }; diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/http/impl/BaseResponseInfoTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/http/impl/BaseResponseInfoTest.java index 60d8f581..bbf01e22 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/http/impl/BaseResponseInfoTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/http/impl/BaseResponseInfoTest.java @@ -4,6 +4,7 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.jetbrains.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,12 +45,12 @@ public boolean isChunkedResponse() { } @Override - public T getContentForFullResponse() { + public @Nullable T getContentForFullResponse() { throw new UnsupportedOperationException("not implemented, don't call me during the test"); } @Override - public void setContentForFullResponse(T contentForFullResponse) { + public void setContentForFullResponse(@Nullable T contentForFullResponse) { throw new UnsupportedOperationException("not implemented, don't call me during the test"); } }; diff --git a/riposte-spi/src/test/java/com/nike/riposte/server/http/impl/RequestInfoImplTest.java b/riposte-spi/src/test/java/com/nike/riposte/server/http/impl/RequestInfoImplTest.java index bcb318f9..82179208 100644 --- a/riposte-spi/src/test/java/com/nike/riposte/server/http/impl/RequestInfoImplTest.java +++ b/riposte-spi/src/test/java/com/nike/riposte/server/http/impl/RequestInfoImplTest.java @@ -702,14 +702,22 @@ public void addContentChunk_does_not_set_isCompleteRequestWithAllChunks_to_true_ KNOWN_MULTIPART_DATA_ATTR_UUID + "\n" + "--OnbiRR2K8-ZzW3rj0wLh_r9td9w_XD34jBR--\n"; + @DataProvider(value = { + "false | false", + "false | true", + "true | false", + "true | true", + }, splitBy = "\\|") @Test - public void getMultipartParts_works_as_expected_with_known_valid_data() throws IOException { + public void getMultipartParts_works_as_expected_with_known_valid_data( + boolean httpVersionIsNull, boolean httpMethodIsNull + ) throws IOException { // given RequestInfoImpl requestInfo = RequestInfoImpl.dummyInstanceForUnknownRequests(); Whitebox.setInternalState(requestInfo, "isMultipart", true); Whitebox.setInternalState(requestInfo, "contentCharset", CharsetUtil.UTF_8); - Whitebox.setInternalState(requestInfo, "protocolVersion", HttpVersion.HTTP_1_1); - Whitebox.setInternalState(requestInfo, "method", HttpMethod.POST); + Whitebox.setInternalState(requestInfo, "protocolVersion", (httpVersionIsNull) ? null : HttpVersion.HTTP_1_1); + Whitebox.setInternalState(requestInfo, "method", (httpMethodIsNull) ? null : HttpMethod.POST); requestInfo.isCompleteRequestWithAllChunks = true; requestInfo.rawContentBytes = KNOWN_MULTIPART_DATA_BODY.getBytes(CharsetUtil.UTF_8); requestInfo.getHeaders().set("Content-Type", KNOWN_MULTIPART_DATA_CONTENT_TYPE_HEADER); @@ -729,7 +737,7 @@ public void getMultipartParts_works_as_expected_with_known_valid_data() throws I } @Test - public void getMultipartParts_works_as_expected_with_known_empty_data() throws IOException { + public void getMultipartParts_works_as_expected_with_known_empty_data() { // given RequestInfoImpl requestInfo = RequestInfoImpl.dummyInstanceForUnknownRequests(); Whitebox.setInternalState(requestInfo, "isMultipart", true); @@ -749,7 +757,7 @@ public void getMultipartParts_works_as_expected_with_known_empty_data() throws I } @Test(expected = IllegalStateException.class) - public void getMultipartParts_explodes_if_multipartData_had_been_released() throws IOException { + public void getMultipartParts_explodes_if_multipartData_had_been_released() { // given RequestInfoImpl requestInfo = RequestInfoImpl.dummyInstanceForUnknownRequests(); Whitebox.setInternalState(requestInfo, "isMultipart", true); diff --git a/riposte-typesafe-config/build.gradle b/riposte-typesafe-config/build.gradle index 9ae0e44e..ec439fbf 100644 --- a/riposte-typesafe-config/build.gradle +++ b/riposte-typesafe-config/build.gradle @@ -5,8 +5,11 @@ dependencies { project(":riposte-core"), "com.typesafe:config:$typesafeConfigVersion" ) - + compileOnly( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", + ) testCompile ( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", "junit:junit:$junitVersion", "org.mockito:mockito-core:$mockitoVersion", "io.rest-assured:rest-assured:$restAssuredVersion", diff --git a/riposte-typesafe-config/src/test/java/com/nike/riposte/typesafeconfig/TypesafeConfigServerTest.java b/riposte-typesafe-config/src/test/java/com/nike/riposte/typesafeconfig/TypesafeConfigServerTest.java index 1c587c94..4f0b2fd0 100644 --- a/riposte-typesafe-config/src/test/java/com/nike/riposte/typesafeconfig/TypesafeConfigServerTest.java +++ b/riposte-typesafe-config/src/test/java/com/nike/riposte/typesafeconfig/TypesafeConfigServerTest.java @@ -12,6 +12,7 @@ import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; +import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -53,7 +54,7 @@ private TypesafeConfigServer generateTypesafeConfigServer(int port) { protected ServerConfig getServerConfig(Config appConfig) { return new ServerConfig() { @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return Collections.singleton(new SomeEndpoint(appConfig)); } @@ -151,14 +152,16 @@ private SomeEndpoint(Config appConfig) { } @Override - public Matcher requestMatcher() { + public @NotNull Matcher requestMatcher() { return Matcher.match(MATCHING_PATH); } @Override - public CompletableFuture> execute(RequestInfo request, - Executor longRunningTaskExecutor, - ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { String value = appConfig.getString("typesafeConfigServer.foo"); return CompletableFuture.completedFuture( ResponseInfo.newBuilder(value).build() diff --git a/samples/sample-1-helloworld/build.gradle b/samples/sample-1-helloworld/build.gradle index 588f4bbb..e23cb7a6 100644 --- a/samples/sample-1-helloworld/build.gradle +++ b/samples/sample-1-helloworld/build.gradle @@ -6,8 +6,11 @@ dependencies { "ch.qos.logback:logback-classic:$logbackVersion", "ch.qos.logback:logback-core:$logbackVersion" ) - + compileOnly( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", + ) testCompile ( + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", "junit:junit:$junitVersion", "org.mockito:mockito-core:$mockitoVersion", "io.rest-assured:rest-assured:$restAssuredVersion" diff --git a/samples/sample-1-helloworld/src/main/java/com/nike/Main.java b/samples/sample-1-helloworld/src/main/java/com/nike/Main.java index 39f7db6b..d3a97180 100644 --- a/samples/sample-1-helloworld/src/main/java/com/nike/Main.java +++ b/samples/sample-1-helloworld/src/main/java/com/nike/Main.java @@ -6,6 +6,9 @@ import com.nike.riposte.server.http.Endpoint; import com.nike.riposte.server.logging.AccessLogger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import java.util.Collection; import java.util.Collections; @@ -20,12 +23,12 @@ public static class AppServerConfig implements ServerConfig { private final AccessLogger accessLogger = new AccessLogger(); @Override - public Collection> appEndpoints() { + public @NotNull Collection<@NotNull Endpoint> appEndpoints() { return endpoints; } @Override - public AccessLogger accessLogger() { + public @Nullable AccessLogger accessLogger() { return accessLogger; } } diff --git a/samples/sample-1-helloworld/src/main/java/com/nike/helloworld/HelloWorldEndpoint.java b/samples/sample-1-helloworld/src/main/java/com/nike/helloworld/HelloWorldEndpoint.java index 78c07cb9..1d8866b9 100644 --- a/samples/sample-1-helloworld/src/main/java/com/nike/helloworld/HelloWorldEndpoint.java +++ b/samples/sample-1-helloworld/src/main/java/com/nike/helloworld/HelloWorldEndpoint.java @@ -6,6 +6,7 @@ import com.nike.riposte.util.AsyncNettyHelper; import com.nike.riposte.util.Matcher; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,7 +31,8 @@ public class HelloWorldEndpoint extends StandardEndpoint { /** * @return The {@link Matcher} that maps incoming requests to this endpoint. */ - public Matcher requestMatcher() { + @Override + public @NotNull Matcher requestMatcher() { return matcher; } @@ -50,7 +52,11 @@ public Matcher requestMatcher() { * incoming request. */ @Override - public CompletableFuture> execute(RequestInfo request, Executor longRunningTaskExecutor, ChannelHandlerContext ctx) { + public @NotNull CompletableFuture> execute( + @NotNull RequestInfo request, + @NotNull Executor longRunningTaskExecutor, + @NotNull ChannelHandlerContext ctx + ) { return CompletableFuture.supplyAsync(supplierWithTracingAndMdc( () -> { logger.debug("Processing Request..."); diff --git a/samples/sample-2-kotlin-todoservice/build.gradle b/samples/sample-2-kotlin-todoservice/build.gradle index e747a78a..125b9a15 100644 --- a/samples/sample-2-kotlin-todoservice/build.gradle +++ b/samples/sample-2-kotlin-todoservice/build.gradle @@ -36,13 +36,12 @@ dependencies { "ch.qos.logback:logback-classic:$logbackVersion", "ch.qos.logback:logback-core:$logbackVersion", "com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion", - "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:$jacksonVersion" + "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:$jacksonVersion", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version", + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", ) - - compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - testCompile ( - //"junit:junit:$junitVersion", + "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", "io.rest-assured:rest-assured:$restAssuredVersion", "io.kotlintest:kotlintest:$kotlinTestVersion", "com.google.truth:truth:${googleTruthVersion}" diff --git a/samples/sample-2-kotlin-todoservice/src/main/kotlin/com/nike/todoservice/endpoints/TodoItemsEndpoint.kt b/samples/sample-2-kotlin-todoservice/src/main/kotlin/com/nike/todoservice/endpoints/TodoItemsEndpoint.kt index 1e04499d..448a1c8c 100644 --- a/samples/sample-2-kotlin-todoservice/src/main/kotlin/com/nike/todoservice/endpoints/TodoItemsEndpoint.kt +++ b/samples/sample-2-kotlin-todoservice/src/main/kotlin/com/nike/todoservice/endpoints/TodoItemsEndpoint.kt @@ -53,7 +53,7 @@ object TodoItemsEndpoint { class Post : StandardEndpoint() { - override fun customRequestContentDeserializer(request: RequestInfo<*>?): ObjectMapper { + override fun customRequestContentDeserializer(request: RequestInfo<*>): ObjectMapper? { return mapper } @@ -78,7 +78,7 @@ object TodoItemsEndpoint { class Put : StandardEndpoint() { - override fun customRequestContentDeserializer(request: RequestInfo<*>?): ObjectMapper { + override fun customRequestContentDeserializer(request: RequestInfo<*>): ObjectMapper? { return mapper } diff --git a/samples/sample-2-kotlin-todoservice/src/test/kotlin/com/nike/todoservice/TodoEndpointTest.kt b/samples/sample-2-kotlin-todoservice/src/test/kotlin/com/nike/todoservice/TodoEndpointTest.kt index f5ca4bf5..3276b0bd 100644 --- a/samples/sample-2-kotlin-todoservice/src/test/kotlin/com/nike/todoservice/TodoEndpointTest.kt +++ b/samples/sample-2-kotlin-todoservice/src/test/kotlin/com/nike/todoservice/TodoEndpointTest.kt @@ -118,7 +118,7 @@ class TodoEndpointTest : FeatureSpec() { @Throws(Exception::class) fun setup() { serverConfig = AppServerConfigForTesting(findFreePort()) - server = Server(serverConfig) + server = Server(serverConfig!!) server!!.startup() }