From 15257299d5b41a40fe6c16d76e62a71c4d5f76b7 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 13 Dec 2016 14:14:45 -0800 Subject: [PATCH 1/9] Adding support for spring MVC and bug fixes * Added Lambda context to Handlers call * Moved isValidUtf8 to base class for response writers * Added responseBytes methods to the AwsHttpServletResponse objects for binary support * Fixed issues in AwsProxyHttpServletRequest * Added custom context properties in AwsProxyHttpServletRequestReader --- README.md | 27 ++- .../internal/LambdaContainerHandler.java | 9 +- .../proxy/internal/ResponseWriter.java | 44 ++++ .../servlet/AwsHttpServletResponse.java | 9 +- .../servlet/AwsProxyHttpServletRequest.java | 9 +- .../AwsProxyHttpServletRequestReader.java | 6 +- .../AwsProxyHttpServletResponseWriter.java | 15 +- .../servlet/AwsProxyServletContext.java | 10 +- .../testutils/AwsProxyRequestBuilder.java | 4 +- .../jersey/JerseyAwsProxyResponseWriter.java | 49 ----- .../jersey/JerseyLambdaContainerHandler.java | 3 +- .../spark/SparkLambdaContainerHandler.java | 3 +- aws-serverless-java-container-spring/pom.xml | 71 +++++++ .../LambdaSpringApplicationInitializer.java | 178 ++++++++++++++++ .../spring/SpringLambdacontainerHandler.java | 93 +++++++++ .../proxy/spring/SpringAwsProxyTest.java | 190 ++++++++++++++++++ .../proxy/spring/echoapp/EchoResource.java | 86 ++++++++ .../spring/echoapp/EchoSpringAppConfig.java | 9 + .../echoapp/model/MapResponseModel.java | 39 ++++ .../echoapp/model/SingleValueModel.java | 28 +++ pom.xml | 1 + 21 files changed, 813 insertions(+), 70 deletions(-) create mode 100644 aws-serverless-java-container-spring/pom.xml create mode 100644 aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java create mode 100644 aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/MapResponseModel.java create mode 100644 aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/SingleValueModel.java diff --git a/README.md b/README.md index 4abbb9e2d..33a0cdc59 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,10 @@ To include the library in your Maven project, add the desired implementation to The simplest way to run your application serverlessly is to configure [API Gateway](https://aws.amazon.com/api-gateway/) to use the [`AWS_PROXY`](http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-set-up-lambda-proxy-integration-on-proxy-resource) integration type and configure your desired `LambdaContainerHandler` implementation to use `AwsProxyRequest`/`AwsProxyResponse` readers and writers. Both Spark and Jersey implementations provide static helper methods that -pre-configure this for you, so your handler method would look like this: +pre-configure this for you. + +### Jersey support +The library expects to receive a valid Jax RS application object. For the Jersey implementation this is the `ResourceConfig` object. ```java public class LambdaHandler implements RequestHandler { @@ -26,8 +29,28 @@ public class LambdaHandler implements RequestHandler handler = JerseyLambdaContainerHandler.getAwsProxyHandler(jerseyApplication); - // for Spark applications, use the SparkLambdaContainerHandler.getAwsProxyHandler() helper method; + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + return handler.proxy(awsProxyRequest, context); + } +} +``` + +### Spring support +The library supports Spring applications that are configured using annotations (in code) rather than in an XML file. The simplest possible configuration uses the `@ComponentScan` annotation to load all controller classes from a package. For example, our unit test application has the following configuuration class. +```java +@Configuration +@ComponentScan("com.amazonaws.serverless.proxy.spring.echoapp") +public class EchoSpringAppConfig { +} +``` + +Once you have declared a configuration class, you can initialize the library with the class name: +```java +public class LambdaHandler implements RequestHandler { + SpringLambdacontainerHandler handler = + SpringLambdacontainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class) + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { return handler.proxy(awsProxyRequest, context); } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java index 42bac72b8..2b32410ad 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/LambdaContainerHandler.java @@ -62,7 +62,7 @@ protected LambdaContainerHandler(RequestReader { */ protected abstract ResponseType writeResponse(ContainerResponseType containerResponse, Context lambdaContext) throws InvalidResponseObjectException; + + /** + * Checks whether the given byte array contains a UTF-8 encoded string + * @param input The byte[] to check against + * @return true if the contend is valid UTF-8, false otherwise + */ + protected boolean isValidUtf8(final byte[] input) { + int i = 0; + // Check for BOM + if (input.length >= 3 && (input[0] & 0xFF) == 0xEF + && (input[1] & 0xFF) == 0xBB & (input[2] & 0xFF) == 0xBF) { + i = 3; + } + + int end; + for (int j = input.length; i < j; ++i) { + int octet = input[i]; + if ((octet & 0x80) == 0) { + continue; // ASCII + } + + // Check for UTF-8 leading byte + if ((octet & 0xE0) == 0xC0) { + end = i + 1; + } else if ((octet & 0xF0) == 0xE0) { + end = i + 2; + } else if ((octet & 0xF8) == 0xF0) { + end = i + 3; + } else { + // Java only supports BMP so 3 is max + return false; + } + + while (i < end) { + i++; + octet = input[i]; + if ((octet & 0xC0) != 0x80) { + // Not a valid trailing byte + return false; + } + } + } + return true; + } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java index b68ab06e8..a6d1ba07f 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java @@ -364,10 +364,17 @@ public Locale getLocale() { // Methods - Package //------------------------------------------------------------- - String getAwsResponseBody() { + String getAwsResponseBodyString() { return responseBody; } + byte[] getAwsResponseBodyBytes() { + if (bodyOutputStream != null) { + return bodyOutputStream.toByteArray(); + } + return new byte[0]; + } + Map getAwsResponseHeaders() { Map responseHeaders = new HashMap<>(); diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java index 59c2b0acc..44aa9f790 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java @@ -180,6 +180,9 @@ public Enumeration getHeaders(String s) { @Override public Enumeration getHeaderNames() { + if (request.getHeaders() == null) { + return Collections.emptyEnumeration(); + } return Collections.enumeration(request.getHeaders().keySet()); } @@ -220,7 +223,7 @@ public String getPathTranslated() { @Override public String getContextPath() { - return request.getResource(); + return "/"; } @@ -529,7 +532,9 @@ public Map getParameterMap() { } for (Map.Entry> entry : params.entrySet()) { - output.put(entry.getKey(), (String[])entry.getValue().toArray()); + String[] valuesArray = new String[entry.getValue().size()]; + valuesArray = entry.getValue().toArray(valuesArray); + output.put(entry.getKey(), valuesArray); } return output; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java index bdfaa3462..3358e2705 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestReader.java @@ -32,7 +32,11 @@ public class AwsProxyHttpServletRequestReader extends RequestReaderAwsProxyResponse object given an AwsHttpServletResponse object. If the * response is not populated with a status code we infer a default 200 status code. @@ -32,7 +35,17 @@ public class AwsProxyHttpServletResponseWriter extends ResponseWriter()); this.request.setRequestContext(new ApiGatewayRequestContext()); - this.request.getRequestContext().setIdentity(new ApiGatewayRequestIdentity()); + ApiGatewayRequestIdentity identity = new ApiGatewayRequestIdentity(); + identity.setSourceIp("127.0.0.1"); + this.request.getRequestContext().setIdentity(identity); } diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyResponseWriter.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyResponseWriter.java index 347120bf6..e4915d737 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyResponseWriter.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyResponseWriter.java @@ -69,53 +69,4 @@ public AwsProxyResponse writeResponse(JerseyResponseWriter containerResponse, Co throw new InvalidResponseObjectException(ex.getMessage(), ex); } } - - - //------------------------------------------------------------- - // Methods - Private - //------------------------------------------------------------- - - /** - * Checks whether the given byte array contains a UTF-8 encoded string - * @param input The byte[] to check against - * @return true if the contend is valid UTF-8, false otherwise - */ - private static boolean isValidUtf8(final byte[] input) { - int i = 0; - // Check for BOM - if (input.length >= 3 && (input[0] & 0xFF) == 0xEF - && (input[1] & 0xFF) == 0xBB & (input[2] & 0xFF) == 0xBF) { - i = 3; - } - - int end; - for (int j = input.length; i < j; ++i) { - int octet = input[i]; - if ((octet & 0x80) == 0) { - continue; // ASCII - } - - // Check for UTF-8 leading byte - if ((octet & 0xE0) == 0xC0) { - end = i + 1; - } else if ((octet & 0xF0) == 0xE0) { - end = i + 2; - } else if ((octet & 0xF8) == 0xF0) { - end = i + 3; - } else { - // Java only supports BMP so 3 is max - return false; - } - - while (i < end) { - i++; - octet = input[i]; - if ((octet & 0xC0) != 0x80) { - // Not a valid trailing byte - return false; - } - } - } - return true; - } } diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java index d98ae9695..ad3a0a0ca 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java @@ -23,6 +23,7 @@ import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; +import com.amazonaws.services.lambda.runtime.Context; import org.glassfish.jersey.server.ApplicationHandler; import org.glassfish.jersey.server.ContainerRequest; import org.glassfish.jersey.server.ResourceConfig; @@ -181,7 +182,7 @@ protected JerseyResponseWriter getContainerResponse(CountDownLatch latch) { @Override - protected void handleRequest(ContainerRequest containerRequest, JerseyResponseWriter jerseyResponseWriter) { + protected void handleRequest(ContainerRequest containerRequest, JerseyResponseWriter jerseyResponseWriter, Context lambdaContext) { containerRequest.setWriter(jerseyResponseWriter); applicationHandler.handle(containerRequest); diff --git a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java index ec02f6f01..4cc8322b3 100644 --- a/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java +++ b/aws-serverless-java-container-spark/src/main/java/com/amazonaws/serverless/proxy/spark/SparkLambdaContainerHandler.java @@ -23,6 +23,7 @@ import com.amazonaws.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServer; import com.amazonaws.serverless.proxy.spark.embeddedserver.LambdaEmbeddedServerFactory; +import com.amazonaws.services.lambda.runtime.Context; import spark.Service; import spark.Spark; import spark.embeddedserver.EmbeddedServers; @@ -145,7 +146,7 @@ protected AwsHttpServletResponse getContainerResponse(CountDownLatch latch) { @Override - protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse) + protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, Context lambdaContext) throws Exception { if (embeddedServer == null) { embeddedServer = LambdaEmbeddedServerFactory.getServerInstance(); diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml new file mode 100644 index 000000000..d53a1d668 --- /dev/null +++ b/aws-serverless-java-container-spring/pom.xml @@ -0,0 +1,71 @@ + + + + aws-serverless-java-container + com.amazonaws.serverless + 0.1 + + 4.0.0 + + aws-serverless-java-container-spring + + + 4.3.4.RELEASE + 2.8.4 + + + + + + com.amazonaws.serverless + aws-serverless-java-container-core + 0.1 + + + + + org.springframework + spring-webmvc + ${spring.version} + + + + + commons-codec + commons-codec + 1.10 + test + + + + + junit + junit + 4.12 + test + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + test + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + test + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + test + + + + + \ No newline at end of file diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java new file mode 100644 index 000000000..a9549c973 --- /dev/null +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java @@ -0,0 +1,178 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.proxy.spring; + +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.context.support.ServletRequestHandledEvent; +import org.springframework.web.servlet.DispatcherServlet; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + +/** + * Custom implementation of Spring's `WebApplicationInitializer`. Uses internal variables to keep application state + * and creates a DispatcherServlet to handle incoming events. When the first event arrives it extracts the ServletContext + * and starts the Spring application. This class assumes that the implementation of `HttpServletRequest` that is passed + * in correctly implements the `getServletContext` method. + * + * State is kept using the `initialized` boolean variable. Each time a new event is received, the app sets the + * `currentResponse` private property to the value of the new `HttpServletResponse` object. This is used to intercept + * Spring notifications for the `ServletRequestHandledEvent` and call the flush method to release the latch. + */ +public class LambdaSpringApplicationInitializer implements WebApplicationInitializer { + // Configuration variables that can be passed in + private List configurationClasses; + private List contextListeners; + + // Dynamically instantiated properties + private AnnotationConfigWebApplicationContext applicationContext; + private ServletConfig dispatcherConfig; + private DispatcherServlet dispatcherServlet; + + // State vars + private boolean initialized; + private HttpServletResponse currentResponse; + + /** + * Creates a new instance of the WebApplicationInitializer + */ + public LambdaSpringApplicationInitializer() { + configurationClasses = new ArrayList<>(); + contextListeners = new ArrayList<>(); + initialized = false; + } + + /** + * Adds a configuration class to the Spring startup process. This should be called at least once with an annotated + * class that contains the @Configuration annotations. The simplest possible class uses the @ComponentScan + * annocation to load all controllers. + * + *
+     * {@Code
+     * @Configuration
+     * @ComponentScan("com.amazonaws.serverless.proxy.spring.echoapp")
+     * public class EchoSpringAppConfig {
+     * }
+     * }
+     * 
+ * @param configuration A set of configuration classes + */ + public void addConfigurationClasses(Class... configuration) { + for (Class config : configuration) { + configurationClasses.add(config); + } + } + + /** + * Adds a new listener for the servlet context events. At the moment the library only emits events when the application + * is initialized. Because we don't have container lifecycle notifications from Lambda the `contextDestroyed` + * method is never called + * @param listener An implementation of `ServletContextListener` + */ + public void addListener(ServletContextListener listener) { + contextListeners.add(listener); + } + + public void dispatch(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + if (!initialized) { + ServletContext context = request.getServletContext(); + onStartup(context); + initialized = true; + } + currentResponse = response; + dispatcherServlet.service(request, response); + } + + /** + * Returns the application context initialized in the library + * @return + */ + public AnnotationConfigWebApplicationContext getApplicationContext() { + return applicationContext; + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + // Create the 'root' Spring application context + applicationContext = new AnnotationConfigWebApplicationContext(); + for (Class config : configurationClasses) { + applicationContext.register(config); + } + applicationContext.setServletContext(servletContext); + + dispatcherConfig = new ServletConfig() { + @Override + public String getServletName() { + return "aws-servless-java-container"; + } + + @Override + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public String getInitParameter(String s) { + return null; + } + + @Override + public Enumeration getInitParameterNames() { + return Collections.emptyEnumeration(); + } + }; + applicationContext.setServletConfig(dispatcherConfig); + + applicationContext.addApplicationListener(new ApplicationListener() { + + @Override + public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledEvent) { + try { + currentResponse.flushBuffer(); + } catch (IOException e) { + e.printStackTrace(); + // TODO: We just die here?? + } + } + }); + + // Manage the lifecycle of the root application context + this.addListener(new ContextLoaderListener(applicationContext)); + + // Register and map the dispatcher servlet + dispatcherServlet = new DispatcherServlet(applicationContext); + dispatcherServlet.refresh(); + dispatcherServlet.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); + dispatcherServlet.init(dispatcherConfig); + + notifyStartListeners(servletContext); + } + + private void notifyStartListeners(ServletContext context) { + for (ServletContextListener listener : contextListeners) { + listener.contextInitialized(new ServletContextEvent(context)); + } + } + +} diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java new file mode 100644 index 000000000..a34bee381 --- /dev/null +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java @@ -0,0 +1,93 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.*; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +import com.amazonaws.services.lambda.runtime.Context; + +import java.util.concurrent.CountDownLatch; + +/** + * Spring implementation of the `LambdaContainerHandler` abstract class. This class uses the `LambdaSpringApplicationInitializer` + * object behind the scenes to proxy requests. The default implementation leverages the `AwsProxyHttpServletRequest` and + * `AwsHttpServletResponse` implemented in the `aws-serverless-java-container-core` package. + * @param The incoming event type + * @param The expected return type + */ +public class SpringLambdacontainerHandler extends LambdaContainerHandler { + private LambdaSpringApplicationInitializer initializer; + + /** + * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects + * @param config A set of classes annotated with the Spring @Configuration annotation + * @return An initialized instance of the `SpringLambdaContainerHandler` + * @throws ContainerInitializationException + */ + public static SpringLambdacontainerHandler getAwsProxyHandler(Class... config) + throws ContainerInitializationException { + SpringLambdacontainerHandler handler = + new SpringLambdacontainerHandler<>( + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler() + ); + + handler.addConfiguration(config); + + return handler; + } + + /** + * Creates a new container handler with the given reader and writer objects + * + * @param requestReader An implementation of `RequestReader` + * @param responseWriter An implementation of `ResponseWriter` + * @param securityContextWriter An implementation of `SecurityContextWriter` + * @param exceptionHandler An implementation of `ExceptionHandler` + * @throws ContainerInitializationException + */ + public SpringLambdacontainerHandler(RequestReader requestReader, + ResponseWriter responseWriter, + SecurityContextWriter securityContextWriter, + ExceptionHandler exceptionHandler) + throws ContainerInitializationException { + super(requestReader, responseWriter, securityContextWriter, exceptionHandler); + initializer = new LambdaSpringApplicationInitializer(); + } + + /** + * Registers a set of classes with the underlying Spring application context + * @param config Spring annotated classes to be registered with the application context + */ + public void addConfiguration(Class... config) { + initializer.addConfigurationClasses(config); + } + + @Override + protected AwsHttpServletResponse getContainerResponse(CountDownLatch latch) { + return new AwsHttpServletResponse(latch); + } + + @Override + protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + initializer.dispatch(containerRequest, containerResponse); + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java new file mode 100644 index 000000000..607d31521 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java @@ -0,0 +1,190 @@ +package com.amazonaws.serverless.proxy.spring; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.spring.echoapp.EchoSpringAppConfig; +import com.amazonaws.serverless.proxy.spring.echoapp.model.MapResponseModel; +import com.amazonaws.serverless.proxy.spring.echoapp.model.SingleValueModel; +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.binary.Base64; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.util.UUID; + +import static org.junit.Assert.*; + + +public class SpringAwsProxyTest { + + private static final String CUSTOM_HEADER_KEY = "x-custom-header"; + private static final String CUSTOM_HEADER_VALUE = "my-custom-value"; + private static final String AUTHORIZER_PRINCIPAL_ID = "test-principal-" + UUID.randomUUID().toString(); + + + private static ObjectMapper objectMapper = new ObjectMapper(); + private static SpringLambdacontainerHandler handler = null; + + private static Context lambdaContext = new MockLambdaContext(); + + @BeforeClass + public static void init() { + try { + handler = SpringLambdacontainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + fail(); + } + } + + @Test + public void headers_getHeaders_echo() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/headers", "GET") + .json() + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getHeaders().get("Content-Type").split(";")[0]); + + validateMapResponseModel(output); + } + + @Test + public void headers_servletRequest_echo() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/servlet-headers", "GET") + .json() + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getHeaders().get("Content-Type").split(";")[0]); + + validateMapResponseModel(output); + } + + @Test + public void queryString_uriInfo_echo() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/query-string", "GET") + .json() + .queryString(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getHeaders().get("Content-Type").split(";")[0]); + + validateMapResponseModel(output); + } + + @Test + public void authorizer_securityContext_customPrincipalSuccess() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/authorizer-principal", "GET") + .json() + .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getHeaders().get("Content-Type").split(";")[0]); + + validateSingleValueModel(output, AUTHORIZER_PRINCIPAL_ID); + } + + @Test + public void errors_unknownRoute_expect404() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/test33", "GET").build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(404, output.getStatusCode()); + } + + @Test + public void error_contentType_invalidContentType() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/json-body", "POST") + .header("Content-Type", "application/octet-stream") + .body("asdasdasd") + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(415, output.getStatusCode()); + } + + @Test + public void error_statusCode_methodNotAllowed() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/status-code", "POST") + .json() + .queryString("status", "201") + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(405, output.getStatusCode()); + } + + @Test + public void responseBody_responseWriter_validBody() throws JsonProcessingException { + SingleValueModel singleValueModel = new SingleValueModel(); + singleValueModel.setValue(CUSTOM_HEADER_VALUE); + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/json-body", "POST") + .json() + .header("Content-Type", "application/json") + .body(objectMapper.writeValueAsString(singleValueModel)) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertNotNull(output.getBody()); + System.out.println("Output:" + output.getBody()); + validateSingleValueModel(output, CUSTOM_HEADER_VALUE); + } + + @Test + public void statusCode_responseStatusCode_customStatusCode() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/status-code", "GET") + .json() + .queryString("status", "201") + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(201, output.getStatusCode()); + } + + @Test + public void base64_binaryResponse_base64Encoding() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/binary", "GET").build(); + + AwsProxyResponse response = handler.proxy(request, lambdaContext); + assertNotNull(response.getBody()); + assertTrue(Base64.isBase64(response.getBody())); + } + + private void validateMapResponseModel(AwsProxyResponse output) { + try { + MapResponseModel response = objectMapper.readValue(output.getBody(), MapResponseModel.class); + assertNotNull(response.getValues().get(CUSTOM_HEADER_KEY)); + assertEquals(CUSTOM_HEADER_VALUE, response.getValues().get(CUSTOM_HEADER_KEY)); + } catch (IOException e) { + fail("Exception while parsing response body: " + e.getMessage()); + e.printStackTrace(); + } + } + + private void validateSingleValueModel(AwsProxyResponse output, String value) { + try { + SingleValueModel response = objectMapper.readValue(output.getBody(), SingleValueModel.class); + assertNotNull(response.getValue()); + assertEquals(value, response.getValue()); + } catch (IOException e) { + fail("Exception while parsing response body: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java new file mode 100644 index 000000000..27e18e0c4 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java @@ -0,0 +1,86 @@ +package com.amazonaws.serverless.proxy.spring.echoapp; + +import com.amazonaws.serverless.proxy.internal.RequestReader; +import com.amazonaws.serverless.proxy.internal.model.ApiGatewayRequestContext; +import com.amazonaws.serverless.proxy.spring.echoapp.model.MapResponseModel; +import com.amazonaws.serverless.proxy.spring.echoapp.model.SingleValueModel; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; +import java.util.Map; +import java.util.Random; + +@RestController +@EnableWebMvc +@RequestMapping("/echo") +public class EchoResource { + @RequestMapping(path = "/headers", method = RequestMethod.GET) + public MapResponseModel echoHeaders(@RequestHeader Map allHeaders) { + MapResponseModel headers = new MapResponseModel(); + for (String key : allHeaders.keySet()) { + headers.addValue(key, allHeaders.get(key)); + } + + return headers; + } + + @RequestMapping(path = "/servlet-headers", method = RequestMethod.GET) + public MapResponseModel echoServletHeaders(HttpServletRequest context) { + MapResponseModel headers = new MapResponseModel(); + Enumeration headerNames = context.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + headers.addValue(headerName, context.getHeader(headerName)); + } + + return headers; + } + + @RequestMapping(path = "/query-string", method = RequestMethod.GET) + public MapResponseModel echoQueryString(HttpServletRequest request) { + MapResponseModel queryStrings = new MapResponseModel(); + for (String key : request.getParameterMap().keySet()) { + queryStrings.addValue(key, request.getParameterMap().get(key)[0]); + } + + return queryStrings; + } + + @RequestMapping(path = "/authorizer-principal", method = RequestMethod.GET) + public SingleValueModel echoAuthorizerPrincipal(HttpServletRequest context) { + SingleValueModel valueModel = new SingleValueModel(); + ApiGatewayRequestContext apiGatewayRequestContext = + (ApiGatewayRequestContext) context.getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY); + valueModel.setValue(apiGatewayRequestContext.getAuthorizer().getPrincipalId()); + + return valueModel; + } + + @RequestMapping(path = "/json-body", method = RequestMethod.POST, consumes = { "application/json" }) + public SingleValueModel echoJsonValue(@RequestBody SingleValueModel requestValue) { + SingleValueModel output = new SingleValueModel(); + output.setValue(requestValue.getValue()); + + return output; + } + + @RequestMapping(path = "/status-code", method = RequestMethod.GET) + public ResponseEntity echoCustomStatusCode(@RequestParam("status") int statusCode ) { + SingleValueModel output = new SingleValueModel(); + output.setValue("" + statusCode); + + return new ResponseEntity(output, HttpStatus.valueOf(statusCode)); + } + + @RequestMapping(path = "/binary", method = RequestMethod.GET) + public ResponseEntity echoBinaryData() { + byte[] b = new byte[128]; + new Random().nextBytes(b); + + return new ResponseEntity(b, HttpStatus.OK); + } +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java new file mode 100644 index 000000000..a8032a5fa --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java @@ -0,0 +1,9 @@ +package com.amazonaws.serverless.proxy.spring.echoapp; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan("com.amazonaws.serverless.proxy.spring.echoapp") +public class EchoSpringAppConfig { +} diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/MapResponseModel.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/MapResponseModel.java new file mode 100644 index 000000000..326f7138d --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/MapResponseModel.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.proxy.spring.echoapp.model; + +import java.util.HashMap; +import java.util.Map; + +/** + * Request/response model + */ +public class MapResponseModel { + private Map values; + + public MapResponseModel() { + this.values = new HashMap<>(); + } + + public void addValue(String key, String value) { + this.values.put(key, value); + } + + public Map getValues() { + return values; + } + + public void setValues(Map values) { + this.values = values; + } +} \ No newline at end of file diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/SingleValueModel.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/SingleValueModel.java new file mode 100644 index 000000000..0d04f585b --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/model/SingleValueModel.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.proxy.spring.echoapp.model; + +/** + * Request/response model + */ +public class SingleValueModel { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index d272ed668..f93aae29f 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ aws-serverless-java-container-core aws-serverless-java-container-jersey aws-serverless-java-container-spark + aws-serverless-java-container-spring From c044ed79ba9772ee0da251cb839e2ab399069901 Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Wed, 14 Dec 2016 15:26:55 -0800 Subject: [PATCH 2/9] Binary support and fixes to Spring support --- README.md | 2 +- .../model/ApiGatewayAuthorizerContext.java | 19 ++- .../internal/model/AwsProxyResponse.java | 9 ++ .../AwsProxyHttpServletResponseWriter.java | 1 + .../testutils/AwsProxyRequestBuilder.java | 12 +- .../jersey/JerseyAwsProxyResponseWriter.java | 1 + .../proxy/test/jersey/EchoJerseyResource.java | 11 ++ .../proxy/test/jersey/JerseyAwsProxyTest.java | 16 +++ aws-serverless-java-container-spring/pom.xml | 26 +++- .../LambdaSpringApplicationInitializer.java | 69 ++++++----- .../spring/SpringLambdacontainerHandler.java | 10 ++ samples/jersey/pet-store/README.md | 62 ++++++++++ samples/jersey/pet-store/pom.xml | 73 +++++++++++ samples/jersey/pet-store/sam.yaml | 26 ++++ .../sample/jersey/LambdaHandler.java | 34 ++++++ .../sample/jersey/PetsResource.java | 70 +++++++++++ .../serverless/sample/jersey/model/Error.java | 29 +++++ .../serverless/sample/jersey/model/Pet.java | 54 +++++++++ .../sample/jersey/model/PetData.java | 111 +++++++++++++++++ samples/spark/pet-store/README.md | 62 ++++++++++ samples/spark/pet-store/pom.xml | 114 ++++++++++++++++++ samples/spark/pet-store/sam.yaml | 26 ++++ .../sample/spark/JsonTransformer.java | 32 +++++ .../sample/spark/LambdaHandler.java | 102 ++++++++++++++++ .../serverless/sample/spark/model/Error.java | 29 +++++ .../serverless/sample/spark/model/Pet.java | 54 +++++++++ .../sample/spark/model/PetData.java | 111 +++++++++++++++++ samples/spring/pet-store/README.md | 62 ++++++++++ samples/spring/pet-store/pom.xml | 90 ++++++++++++++ samples/spring/pet-store/sam.yaml | 26 ++++ .../sample/spring/LambdaHandler.java | 41 +++++++ .../spring/PetStoreSpringAppConfig.java | 21 ++++ .../sample/spring/PetsController.java | 68 +++++++++++ .../serverless/sample/spring/model/Error.java | 29 +++++ .../serverless/sample/spring/model/Pet.java | 54 +++++++++ .../sample/spring/model/PetData.java | 111 +++++++++++++++++ 36 files changed, 1618 insertions(+), 49 deletions(-) create mode 100644 samples/jersey/pet-store/README.md create mode 100644 samples/jersey/pet-store/pom.xml create mode 100644 samples/jersey/pet-store/sam.yaml create mode 100644 samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/LambdaHandler.java create mode 100644 samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/PetsResource.java create mode 100644 samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/Error.java create mode 100644 samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/Pet.java create mode 100644 samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/PetData.java create mode 100644 samples/spark/pet-store/README.md create mode 100644 samples/spark/pet-store/pom.xml create mode 100644 samples/spark/pet-store/sam.yaml create mode 100644 samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/JsonTransformer.java create mode 100644 samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/LambdaHandler.java create mode 100644 samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Error.java create mode 100644 samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Pet.java create mode 100644 samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/PetData.java create mode 100644 samples/spring/pet-store/README.md create mode 100644 samples/spring/pet-store/pom.xml create mode 100644 samples/spring/pet-store/sam.yaml create mode 100644 samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/LambdaHandler.java create mode 100644 samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetStoreSpringAppConfig.java create mode 100644 samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetsController.java create mode 100644 samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/Error.java create mode 100644 samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/Pet.java create mode 100644 samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/PetData.java diff --git a/README.md b/README.md index 33a0cdc59..d37621e82 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Serverless Java container -The `aws-serverless-java-container` is collection of interfaces and their implementations that let you run Java application written with frameworks such as [Jersey](https://jersey.java.net/) or [Spark]() in [AWS Lambda](https://aws.amazon.com/lambda/). +The `aws-serverless-java-container` is collection of interfaces and their implementations that let you run Java application written with frameworks such as [Jersey](https://jersey.java.net/) or [Spark](http://sparkjava.com/) in [AWS Lambda](https://aws.amazon.com/lambda/). The library contains a core artifact called `aws-serverless-java-container-core` that defines the interfaces and base classes required as well as default implementation of the Java servlet `HttpServletRequest` and `HttpServletResponse`. The library also includes two initial implementations of the interfaces to support Jersey apps (`aws-serverless-java-container-jersey`) and Spark (`aws-serverless-java-container-spark`). diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java index f79a1efde..294e96de8 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/ApiGatewayAuthorizerContext.java @@ -12,28 +12,27 @@ */ package com.amazonaws.serverless.proxy.internal.model; +import java.util.HashMap; + /** * Custom authorizer context object for the API Gateway request context. */ -public class ApiGatewayAuthorizerContext { - - //------------------------------------------------------------- - // Variables - Private - //------------------------------------------------------------- - - private String principalId; - +public class ApiGatewayAuthorizerContext extends HashMap { //------------------------------------------------------------- // Methods - Getter/Setter //------------------------------------------------------------- public String getPrincipalId() { - return principalId; + return get("principalId"); } public void setPrincipalId(String principalId) { - this.principalId = principalId; + put("principalId", principalId); + } + + public String getContextValue(String key) { + return get(key); } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyResponse.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyResponse.java index a72983559..43393fda9 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyResponse.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/model/AwsProxyResponse.java @@ -27,6 +27,7 @@ public class AwsProxyResponse { private int statusCode; private Map headers; private String body; + private boolean isBase64Encoded; //------------------------------------------------------------- @@ -100,4 +101,12 @@ public String getBody() { public void setBody(String body) { this.body = body; } + + public boolean isBase64Encoded() { + return isBase64Encoded; + } + + public void setBase64Encoded(boolean base64Encoded) { + isBase64Encoded = base64Encoded; + } } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java index d42820892..232bd15e4 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletResponseWriter.java @@ -42,6 +42,7 @@ public AwsProxyResponse writeResponse(AwsHttpServletResponse containerResponse, responseString = containerResponse.getAwsResponseBodyString(); } else { responseString = Base64.getMimeEncoder().encodeToString(containerResponse.getAwsResponseBodyBytes()); + awsProxyResponse.setBase64Encoded(true); } awsProxyResponse.setBody(responseString); diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java index fb1cd8f30..31dd0f494 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java @@ -122,11 +122,21 @@ public AwsProxyRequestBuilder body(String body) { public AwsProxyRequestBuilder authorizerPrincipal(String principal) { - this.request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext()); + if (this.request.getRequestContext().getAuthorizer() == null) { + this.request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext()); + } this.request.getRequestContext().getAuthorizer().setPrincipalId(principal); return this; } + public AwsProxyRequestBuilder authorizerContextValue(String key, String value) { + if (this.request.getRequestContext().getAuthorizer() == null) { + this.request.getRequestContext().setAuthorizer(new ApiGatewayAuthorizerContext()); + } + this.request.getRequestContext().getAuthorizer().put(key, value); + return this; + } + public AwsProxyRequestBuilder cognitoUserPool(String identityId) { this.request.getRequestContext().getIdentity().setCognitoAuthenticationType("POOL"); diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyResponseWriter.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyResponseWriter.java index e4915d737..80ea48fac 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyResponseWriter.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyResponseWriter.java @@ -59,6 +59,7 @@ public AwsProxyResponse writeResponse(JerseyResponseWriter containerResponse, Co responseString = new String(containerResponse.getResponseBody().toByteArray()); } else { responseString = Base64.getMimeEncoder().encodeToString(containerResponse.getResponseBody().toByteArray()); + response.setBase64Encoded(true); } response.setBody(responseString); diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/test/jersey/EchoJerseyResource.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/test/jersey/EchoJerseyResource.java index 222a52b98..1715d6b63 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/test/jersey/EchoJerseyResource.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/test/jersey/EchoJerseyResource.java @@ -79,6 +79,17 @@ public SingleValueModel echoAuthorizerPrincipal(@Context ContainerRequestContext return valueModel; } + @Path("/authorizer-context") @GET + @Produces(MediaType.APPLICATION_JSON) + public SingleValueModel echoAuthorizerContext(@Context ContainerRequestContext context, @QueryParam("key") String key) { + SingleValueModel valueModel = new SingleValueModel(); + ApiGatewayRequestContext apiGatewayRequestContext = + (ApiGatewayRequestContext) context.getProperty(RequestReader.API_GATEWAY_CONTEXT_PROPERTY); + valueModel.setValue(apiGatewayRequestContext.getAuthorizer().getContextValue(key)); + + return valueModel; + } + @Path("/json-body") @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/test/jersey/JerseyAwsProxyTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/test/jersey/JerseyAwsProxyTest.java index 1e08c8c61..a467c0662 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/test/jersey/JerseyAwsProxyTest.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/test/jersey/JerseyAwsProxyTest.java @@ -117,6 +117,22 @@ public void authorizer_securityContext_customPrincipalSuccess() { validateSingleValueModel(output, AUTHORIZER_PRINCIPAL_ID); } + @Test + public void authorizer_securityContext_customAuthorizerContextSuccess() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/authorizer-context", "GET") + .json() + .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID) + .authorizerContextValue(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .queryString("key", CUSTOM_HEADER_KEY) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getHeaders().get("Content-Type")); + + validateSingleValueModel(output, CUSTOM_HEADER_VALUE); + } + @Test public void errors_unknownRoute_expect404() { AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/test33", "GET").build(); diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index d53a1d668..6ae41bbff 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -2,16 +2,30 @@ - - aws-serverless-java-container - com.amazonaws.serverless - 0.1 - - 4.0.0 + com.amazonaws.serverless aws-serverless-java-container-spring + AWS Serverless Java container support - Spring implementation + Allows Java applications written for the Spring framework to run in AWS Lambda + https://aws.amazon.com/lambda/ + 0.1 + + + https://github.com/awslabs/aws-serverless-java-container.git + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + 4.0.0 + 1.8 + 1.8 4.3.4.RELEASE 2.8.4 diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java index a9549c973..0c4cfb39a 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java @@ -12,6 +12,7 @@ */ package com.amazonaws.serverless.proxy.spring; +import com.amazonaws.serverless.exceptions.InvalidResponseObjectException; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.web.WebApplicationInitializer; @@ -40,6 +41,8 @@ * Spring notifications for the `ServletRequestHandledEvent` and call the flush method to release the latch. */ public class LambdaSpringApplicationInitializer implements WebApplicationInitializer { + private static final String DEFAULT_SERVLET_NAME = "aws-servless-java-container"; + // Configuration variables that can be passed in private List configurationClasses; private List contextListeners; @@ -49,8 +52,7 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial private ServletConfig dispatcherConfig; private DispatcherServlet dispatcherServlet; - // State vars - private boolean initialized; + // The current response is used to release the latch when Spring emits the request handled event private HttpServletResponse currentResponse; /** @@ -59,7 +61,6 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial public LambdaSpringApplicationInitializer() { configurationClasses = new ArrayList<>(); contextListeners = new ArrayList<>(); - initialized = false; } /** @@ -95,11 +96,6 @@ public void addListener(ServletContextListener listener) { public void dispatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - if (!initialized) { - ServletContext context = request.getServletContext(); - onStartup(context); - initialized = true; - } currentResponse = response; dispatcherServlet.service(request, response); } @@ -121,38 +117,18 @@ public void onStartup(ServletContext servletContext) throws ServletException { } applicationContext.setServletContext(servletContext); - dispatcherConfig = new ServletConfig() { - @Override - public String getServletName() { - return "aws-servless-java-container"; - } - - @Override - public ServletContext getServletContext() { - return servletContext; - } - - @Override - public String getInitParameter(String s) { - return null; - } - - @Override - public Enumeration getInitParameterNames() { - return Collections.emptyEnumeration(); - } - }; + dispatcherConfig = new DefaultDispatcherConfig(servletContext); applicationContext.setServletConfig(dispatcherConfig); + // Configure the listener for the request handled events. All we do here is release the latch applicationContext.addApplicationListener(new ApplicationListener() { - @Override public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledEvent) { try { currentResponse.flushBuffer(); } catch (IOException e) { e.printStackTrace(); - // TODO: We just die here?? + throw new RuntimeException("Could not flush response buffer", e); } } }); @@ -175,4 +151,35 @@ private void notifyStartListeners(ServletContext context) { } } + /** + * Default configuration class for the DispatcherServlet. This just mocks the behaviour of a default + * ServletConfig object with no init parameters + */ + private class DefaultDispatcherConfig implements ServletConfig { + private ServletContext servletContext; + + DefaultDispatcherConfig(ServletContext context) { + servletContext = context; + } + + @Override + public String getServletName() { + return DEFAULT_SERVLET_NAME; + } + + @Override + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public String getInitParameter(String s) { + return null; + } + + @Override + public Enumeration getInitParameterNames() { + return Collections.emptyEnumeration(); + } + } } diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java index a34bee381..2b6f2c9bf 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java @@ -22,6 +22,7 @@ import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; import com.amazonaws.services.lambda.runtime.Context; +import javax.servlet.ServletContext; import java.util.concurrent.CountDownLatch; /** @@ -34,6 +35,9 @@ public class SpringLambdacontainerHandler extends LambdaContainerHandler { private LambdaSpringApplicationInitializer initializer; + // State vars + private boolean initialized; + /** * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects * @param config A set of classes annotated with the Spring @Configuration annotation @@ -88,6 +92,12 @@ protected AwsHttpServletResponse getContainerResponse(CountDownLatch latch) { @Override protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHttpServletResponse containerResponse, Context lambdaContext) throws Exception { + // wire up the application context on the first invocation + if (!initialized) { + ServletContext context = containerRequest.getServletContext(); + initializer.onStartup(context); + initialized = true; + } initializer.dispatch(containerRequest, containerResponse); } } diff --git a/samples/jersey/pet-store/README.md b/samples/jersey/pet-store/README.md new file mode 100644 index 000000000..bfb441648 --- /dev/null +++ b/samples/jersey/pet-store/README.md @@ -0,0 +1,62 @@ +# Serverless Jersey example +A basic pet store written with the [Jersey framework](https://jersey.java.net/). The `LambdaHandler` object is the main entry point for Lambda. + +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition + +## Installation +To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. + +In a shell, navigate to the sample's folder and use maven to build a deployable jar. +``` +$ mvn package +``` + +This command should generate a `serverless-jersey-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. + +You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: + +``` +$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket +Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) +Successfully packaged artifacts and wrote output template to file output-sam.yaml. +Execute the following command to deploy the packaged template +aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name +``` + +As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. + +``` +$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessJerseySample --capabilities CAPABILITY_IAM +``` + +Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `JerseyPetStoreApi` key of the `Outputs` property: + +``` +$ aws cloudformation describe-stacks --stack-name ServerlessJerseySample +{ + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", + "Description": "Example Pet Store API written in jersey with the aws-serverless-java-container library", + "Tags": [], + "Outputs": [ + { + "Description": "URL for application", + "OutputKey": "JerseyPetStoreApi", + "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" + } + ], + "CreationTime": "2016-12-13T22:59:31.552Z", + "Capabilities": [ + "CAPABILITY_IAM" + ], + "StackName": "JerseySample", + "NotificationARNs": [], + "StackStatus": "UPDATE_COMPLETE" + } + ] +} + +``` + +Copy the `OutputValue` into a browser to test a first request. \ No newline at end of file diff --git a/samples/jersey/pet-store/pom.xml b/samples/jersey/pet-store/pom.xml new file mode 100644 index 000000000..edacdd2a2 --- /dev/null +++ b/samples/jersey/pet-store/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + com.amazonaws.serverless.sample + serverless-jersey-example + 1.0-SNAPSHOT + Jersey example for the aws-serverless-java-container library + Simple pet store written in Jersey + https://aws.amazon.com/lambda/ + + + https://github.com/awslabs/aws-serverless-java-container.git + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 1.8 + 1.8 + 2.24 + 2.8.5 + + + + + com.amazonaws.serverless + aws-serverless-java-container-jersey + 0.1 + + + + com.amazonaws + aws-lambda-java-core + 1.1.0 + + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${jersey.version} + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + + package + + shade + + + + + + + \ No newline at end of file diff --git a/samples/jersey/pet-store/sam.yaml b/samples/jersey/pet-store/sam.yaml new file mode 100644 index 000000000..4b973809a --- /dev/null +++ b/samples/jersey/pet-store/sam.yaml @@ -0,0 +1,26 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Example Pet Store API written in jersey with the aws-serverless-java-container library +Resources: + PetStoreFunction: + Type: AWS::Serverless::Function + Properties: + Handler: com.amazonaws.serverless.sample.jersey.LambdaHandler::handleRequest + Runtime: java8 + CodeUri: target/serverless-jersey-example-1.0-SNAPSHOT.jar + MemorySize: 512 + Policies: AWSLambdaBasicExecutionRole + Timeout: 20 + Events: + GetResource: + Type: Api + Properties: + Path: /{proxy+} + Method: any + +Outputs: + JerseyPetStoreApi: + Description: URL for application + Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Export: + Name: JerseyPetStoreApi diff --git a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/LambdaHandler.java b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/LambdaHandler.java new file mode 100644 index 000000000..8d67d2991 --- /dev/null +++ b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/LambdaHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.jersey; + +import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.jersey.JerseyLambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.glassfish.jersey.jackson.JacksonFeature; +import org.glassfish.jersey.server.ResourceConfig; + +public class LambdaHandler implements RequestHandler { + private ResourceConfig jerseyApplication = new ResourceConfig() + .packages("com.amazonaws.serverless.sample.jersey") + .register(JacksonFeature.class); + private JerseyLambdaContainerHandler handler + = JerseyLambdaContainerHandler.getAwsProxyHandler(jerseyApplication); + + @Override + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + return handler.proxy(awsProxyRequest, context); + } +} \ No newline at end of file diff --git a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/PetsResource.java b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/PetsResource.java new file mode 100644 index 000000000..2d92510d7 --- /dev/null +++ b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/PetsResource.java @@ -0,0 +1,70 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.jersey; + +import com.amazonaws.serverless.sample.jersey.model.Pet; +import com.amazonaws.serverless.sample.jersey.model.PetData; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.UUID; + +@Path("/pets") +public class PetsResource { + + @POST + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response createPet(final Pet newPet) { + if (newPet.getName() == null || newPet.getBreed() == null) { + return Response.status(400).entity(new Error("Invalid name or breed")).build(); + } + + Pet dbPet = newPet; + dbPet.setId(UUID.randomUUID().toString()); + return Response.status(200).entity(dbPet).build(); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Pet[] listPets(@QueryParam("limit") int limit) { + if (limit < 1) { + limit = 10; + } + + Pet[] outputPets = new Pet[limit]; + + for (int i = 0; i < limit; i++) { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setName(PetData.getRandomName()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); + outputPets[i] = newPet; + } + + return outputPets; + } + + @Path("/{petId}") @GET + @Produces(MediaType.APPLICATION_JSON) + public Pet getPetDetails() { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); + newPet.setName(PetData.getRandomName()); + return newPet; + } +} \ No newline at end of file diff --git a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/Error.java b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/Error.java new file mode 100644 index 000000000..b6ca51891 --- /dev/null +++ b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/Error.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.jersey.model; + +public class Error { + private String message; + + public Error(String errorMessage) { + message = errorMessage; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/Pet.java b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/Pet.java new file mode 100644 index 000000000..2a1a220f8 --- /dev/null +++ b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/Pet.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.jersey.model; + +import java.util.Date; + +public class Pet { + private String id; + private String breed; + private String name; + private Date dateOfBirth; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBreed() { + return breed; + } + + public void setBreed(String breed) { + this.breed = breed; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getDateOfBirth() { + return dateOfBirth; + } + + public void setDateOfBirth(Date dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } +} diff --git a/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/PetData.java b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/PetData.java new file mode 100644 index 000000000..fb1ee15c7 --- /dev/null +++ b/samples/jersey/pet-store/src/main/java/com/amazonaws/serverless/sample/jersey/model/PetData.java @@ -0,0 +1,111 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.jersey.model; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +public class PetData { + private static List breeds = new ArrayList<>(); + static { + breeds.add("Afghan Hound"); + breeds.add("Beagle"); + breeds.add("Bernese Mountain Dog"); + breeds.add("Bloodhound"); + breeds.add("Dalmatian"); + breeds.add("Jack Russell Terrier"); + breeds.add("Norwegian Elkhound"); + } + + private static List names = new ArrayList<>(); + static { + names.add("Bailey"); + names.add("Bella"); + names.add("Max"); + names.add("Lucy"); + names.add("Charlie"); + names.add("Molly"); + names.add("Buddy"); + names.add("Daisy"); + names.add("Rocky"); + names.add("Maggie"); + names.add("Jake "); + names.add("Sophie"); + names.add("Jack "); + names.add("Sadie"); + names.add("Toby "); + names.add("Chloe"); + names.add("Cody "); + names.add("Bailey"); + names.add("Buster"); + names.add("Lola"); + names.add("Duke "); + names.add("Zoe"); + names.add("Cooper"); + names.add("Abby"); + names.add("Riley"); + names.add("Ginger"); + names.add("Harley"); + names.add("Roxy"); + names.add("Bear"); + names.add("Gracie"); + names.add("Tucker"); + names.add("Coco"); + names.add("Murphy"); + names.add("Sasha"); + names.add("Lucky"); + names.add("Lily"); + names.add("Oliver"); + names.add("Angel"); + names.add("Sam"); + names.add("Princess"); + names.add("Oscar"); + names.add("Emma"); + names.add("Teddy"); + names.add("Annie"); + names.add("Winston"); + names.add("Rosie"); + } + + public static List getBreeds() { + return breeds; + } + + public static List getNames() { + return names; + } + + public static String getRandomBreed() { + return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1)); + } + + public static String getRandomName() { + return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1)); + } + + public static Date getRandomDoB() { + GregorianCalendar gc = new GregorianCalendar(); + + int year = ThreadLocalRandom.current().nextInt( + Calendar.getInstance().get(Calendar.YEAR) - 15, + Calendar.getInstance().get(Calendar.YEAR) + ); + + gc.set(Calendar.YEAR, year); + + int dayOfYear = ThreadLocalRandom.current().nextInt(1, gc.getActualMaximum(Calendar.DAY_OF_YEAR)); + + gc.set(Calendar.DAY_OF_YEAR, dayOfYear); + return gc.getTime(); + } +} diff --git a/samples/spark/pet-store/README.md b/samples/spark/pet-store/README.md new file mode 100644 index 000000000..307e74ece --- /dev/null +++ b/samples/spark/pet-store/README.md @@ -0,0 +1,62 @@ +# Serverless Jersey example +A basic pet store written with the [Spark framework](http://sparkjava.com/). The `LambdaHandler` object is the main entry point for Lambda. + +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition + +## Installation +To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. + +In a shell, navigate to the sample's folder and use maven to build a deployable jar. +``` +$ mvn package +``` + +This command should generate a `serverless-spark-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. + +You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: + +``` +$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket +Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) +Successfully packaged artifacts and wrote output template to file output-sam.yaml. +Execute the following command to deploy the packaged template +aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name +``` + +As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. + +``` +$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSparkSample --capabilities CAPABILITY_IAM +``` + +Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SparkPetStoreApi` key of the `Outputs` property: + +``` +$ aws cloudformation describe-stacks --stack-name ServerlessSparkSample +{ + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", + "Description": "Example Pet Store API written with spark with the aws-serverless-java-container library", + "Tags": [], + "Outputs": [ + { + "Description": "URL for application", + "OutputKey": "PetStoreApi", + "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" + } + ], + "CreationTime": "2016-12-13T22:59:31.552Z", + "Capabilities": [ + "CAPABILITY_IAM" + ], + "StackName": "JerseySample", + "NotificationARNs": [], + "StackStatus": "UPDATE_COMPLETE" + } + ] +} + +``` + +Copy the `OutputValue` into a browser to test a first request. \ No newline at end of file diff --git a/samples/spark/pet-store/pom.xml b/samples/spark/pet-store/pom.xml new file mode 100644 index 000000000..16f0f849e --- /dev/null +++ b/samples/spark/pet-store/pom.xml @@ -0,0 +1,114 @@ + + + 4.0.0 + + com.amazonaws.serverless.sample + serverless-spark-example + 1.0-SNAPSHOT + Spark example for the aws-serverless-java-container library + Simple pet store written with the Spark framework + https://aws.amazon.com/lambda/ + + + https://github.com/awslabs/aws-serverless-java-container.git + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 1.8 + 1.8 + 2.8.5 + 2.5.3 + + + + + com.amazonaws.serverless + aws-serverless-java-container-spark + 0.1 + + + + com.amazonaws + aws-lambda-java-core + 1.1.0 + + + + com.amazonaws + aws-lambda-java-log4j + 1.0.0 + + + + + com.sparkjava + spark-core + ${spark.version} + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + + org.slf4j + slf4j-log4j12 + 1.7.21 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + + package + + shade + + + + + org.eclipse.jetty.websocket:* + + + + + + + + + \ No newline at end of file diff --git a/samples/spark/pet-store/sam.yaml b/samples/spark/pet-store/sam.yaml new file mode 100644 index 000000000..84a853686 --- /dev/null +++ b/samples/spark/pet-store/sam.yaml @@ -0,0 +1,26 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Example Pet Store API written with spark with the aws-serverless-java-container library +Resources: + PetStoreFunction: + Type: AWS::Serverless::Function + Properties: + Handler: com.amazonaws.serverless.sample.spark.LambdaHandler::handleRequest + Runtime: java8 + CodeUri: target/serverless-spark-example-1.0-SNAPSHOT.jar + MemorySize: 512 + Policies: AWSLambdaBasicExecutionRole + Timeout: 20 + Events: + GetResource: + Type: Api + Properties: + Path: /{proxy+} + Method: any + +Outputs: + SparkPetStoreApi: + Description: URL for application + Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Export: + Name: SparkPetStoreApi diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/JsonTransformer.java b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/JsonTransformer.java new file mode 100644 index 000000000..55e5e5bec --- /dev/null +++ b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/JsonTransformer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.spark; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import spark.ResponseTransformer; + +public class JsonTransformer implements ResponseTransformer { + + private ObjectMapper mapper = new ObjectMapper(); + + @Override + public String render(Object model) { + try { + return mapper.writeValueAsString(model); + } catch (JsonProcessingException e) { + return null; + } + } + +} \ No newline at end of file diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/LambdaHandler.java b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/LambdaHandler.java new file mode 100644 index 000000000..08ecc151d --- /dev/null +++ b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/LambdaHandler.java @@ -0,0 +1,102 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.spark; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spark.SparkLambdaContainerHandler; +import com.amazonaws.serverless.sample.spark.model.Pet; +import com.amazonaws.serverless.sample.spark.model.PetData; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.log4j.LambdaAppender; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.log4j.Logger; + +import javax.ws.rs.core.Response; +import java.util.UUID; + +import static spark.Spark.before; +import static spark.Spark.get; +import static spark.Spark.post; + +public class LambdaHandler implements RequestHandler { + private ObjectMapper objectMapper = new ObjectMapper(); + private boolean isInitialized = false; + private SparkLambdaContainerHandler handler; + + @Override + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + if (!isInitialized) { + isInitialized = true; + try { + Logger.getRootLogger().addAppender(new LambdaAppender()); + handler = SparkLambdaContainerHandler.getAwsProxyHandler(); + defineResources(); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + return null; + } + } + return handler.proxy(awsProxyRequest, context); + } + + private void defineResources() { + before((request, response) -> response.type("application/json")); + + post("/pets", (req, res) -> { + Pet newPet = objectMapper.readValue(req.body(), Pet.class); + if (newPet.getName() == null || newPet.getBreed() == null) { + return Response.status(400).entity(new Error("Invalid name or breed")).build(); + } + + Pet dbPet = newPet; + dbPet.setId(UUID.randomUUID().toString()); + + res.status(200); + return dbPet; + }, new JsonTransformer()); + + get("/pets", (req, res) -> { + int limit = 10; + if (req.queryParams("limit") != null) { + limit = Integer.parseInt(req.queryParams("limit")); + } + + Pet[] outputPets = new Pet[limit]; + + for (int i = 0; i < limit; i++) { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setName(PetData.getRandomName()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); + outputPets[i] = newPet; + } + + res.status(200); + return outputPets; + }, new JsonTransformer()); + + get("/pets/:petId", (req, res) -> { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); + newPet.setName(PetData.getRandomName()); + res.status(200); + return newPet; + }, new JsonTransformer()); + } +} \ No newline at end of file diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Error.java b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Error.java new file mode 100644 index 000000000..3577e6a3a --- /dev/null +++ b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Error.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.spark.model; + +public class Error { + private String message; + + public Error(String errorMessage) { + message = errorMessage; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Pet.java b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Pet.java new file mode 100644 index 000000000..a6f569597 --- /dev/null +++ b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/Pet.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.spark.model; + +import java.util.Date; + +public class Pet { + private String id; + private String breed; + private String name; + private Date dateOfBirth; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBreed() { + return breed; + } + + public void setBreed(String breed) { + this.breed = breed; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getDateOfBirth() { + return dateOfBirth; + } + + public void setDateOfBirth(Date dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } +} diff --git a/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/PetData.java b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/PetData.java new file mode 100644 index 000000000..a8c45a53a --- /dev/null +++ b/samples/spark/pet-store/src/main/java/com/amazonaws/serverless/sample/spark/model/PetData.java @@ -0,0 +1,111 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.spark.model; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +public class PetData { + private static List breeds = new ArrayList<>(); + static { + breeds.add("Afghan Hound"); + breeds.add("Beagle"); + breeds.add("Bernese Mountain Dog"); + breeds.add("Bloodhound"); + breeds.add("Dalmatian"); + breeds.add("Jack Russell Terrier"); + breeds.add("Norwegian Elkhound"); + } + + private static List names = new ArrayList<>(); + static { + names.add("Bailey"); + names.add("Bella"); + names.add("Max"); + names.add("Lucy"); + names.add("Charlie"); + names.add("Molly"); + names.add("Buddy"); + names.add("Daisy"); + names.add("Rocky"); + names.add("Maggie"); + names.add("Jake "); + names.add("Sophie"); + names.add("Jack "); + names.add("Sadie"); + names.add("Toby "); + names.add("Chloe"); + names.add("Cody "); + names.add("Bailey"); + names.add("Buster"); + names.add("Lola"); + names.add("Duke "); + names.add("Zoe"); + names.add("Cooper"); + names.add("Abby"); + names.add("Riley"); + names.add("Ginger"); + names.add("Harley"); + names.add("Roxy"); + names.add("Bear"); + names.add("Gracie"); + names.add("Tucker"); + names.add("Coco"); + names.add("Murphy"); + names.add("Sasha"); + names.add("Lucky"); + names.add("Lily"); + names.add("Oliver"); + names.add("Angel"); + names.add("Sam"); + names.add("Princess"); + names.add("Oscar"); + names.add("Emma"); + names.add("Teddy"); + names.add("Annie"); + names.add("Winston"); + names.add("Rosie"); + } + + public static List getBreeds() { + return breeds; + } + + public static List getNames() { + return names; + } + + public static String getRandomBreed() { + return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1)); + } + + public static String getRandomName() { + return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1)); + } + + public static Date getRandomDoB() { + GregorianCalendar gc = new GregorianCalendar(); + + int year = ThreadLocalRandom.current().nextInt( + Calendar.getInstance().get(Calendar.YEAR) - 15, + Calendar.getInstance().get(Calendar.YEAR) + ); + + gc.set(Calendar.YEAR, year); + + int dayOfYear = ThreadLocalRandom.current().nextInt(1, gc.getActualMaximum(Calendar.DAY_OF_YEAR)); + + gc.set(Calendar.DAY_OF_YEAR, dayOfYear); + return gc.getTime(); + } +} diff --git a/samples/spring/pet-store/README.md b/samples/spring/pet-store/README.md new file mode 100644 index 000000000..44db0eacb --- /dev/null +++ b/samples/spring/pet-store/README.md @@ -0,0 +1,62 @@ +# Serverless Jersey example +A basic pet store written with the [Spring framework](https://projects.spring.io/spring-framework/). The `LambdaHandler` object is the main entry point for Lambda. + +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `sam.yaml` file in the root folder contains the application definition + +## Installation +To build and install the sample application you will need [Maven](https://maven.apache.org/) and the [AWS CLI](https://aws.amazon.com/cli/) installed on your computer. + +In a shell, navigate to the sample's folder and use maven to build a deployable jar. +``` +$ mvn package +``` + +This command should generate a `serverless-spring-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the jar file, we can use the AWS CLI to package the template for deployment. + +You will need an S3 bucket to store the artifacts for deployment. Once you have created the S3 bucket, run the following command from the sample's folder: + +``` +$ aws cloudformation package --template-file sam.yaml --output-template-file output-sam.yaml --s3-bucket +Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxx 6464692 / 6464692.0 (100.00%) +Successfully packaged artifacts and wrote output template to file output-sam.yaml. +Execute the following command to deploy the packaged template +aws cloudformation deploy --template-file /your/path/output-sam.yaml --stack-name +``` + +As the command output suggests, you can now use the cli to deploy the application. Choose a stack name and run the `aws cloudformation deploy` command from the output of the package command. + +``` +$ aws cloudformation deploy --template-file output-sam.yaml --stack-name ServerlessSpringSample --capabilities CAPABILITY_IAM +``` + +Once the application is deployed, you can describe the stack to show the API endpoint that was created. The endpoint should be the `SparkPetStoreApi` key of the `Outputs` property: + +``` +$ aws cloudformation describe-stacks --stack-name ServerlessSpringSample +{ + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/JerseySample/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", + "Description": "Example Pet Store API written with spark with the aws-serverless-java-container library", + "Tags": [], + "Outputs": [ + { + "Description": "URL for application", + "OutputKey": "PetStoreApi", + "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/pets" + } + ], + "CreationTime": "2016-12-13T22:59:31.552Z", + "Capabilities": [ + "CAPABILITY_IAM" + ], + "StackName": "JerseySample", + "NotificationARNs": [], + "StackStatus": "UPDATE_COMPLETE" + } + ] +} + +``` + +Copy the `OutputValue` into a browser to test a first request. \ No newline at end of file diff --git a/samples/spring/pet-store/pom.xml b/samples/spring/pet-store/pom.xml new file mode 100644 index 000000000..8d2c8d014 --- /dev/null +++ b/samples/spring/pet-store/pom.xml @@ -0,0 +1,90 @@ + + + 4.0.0 + + com.amazonaws.serverless.sample + serverless-spring-example + 1.0-SNAPSHOT + Spring example for the aws-serverless-java-container library + Simple pet store written with the Spring framework + https://aws.amazon.com/lambda/ + + + https://github.com/awslabs/aws-serverless-java-container.git + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 1.8 + 1.8 + 4.3.4.RELEASE + 2.8.4 + + + + + com.amazonaws.serverless + aws-serverless-java-container-spring + 0.1 + + + + com.amazonaws + aws-lambda-java-core + 1.1.0 + + + + + org.springframework + spring-webmvc + ${spring.version} + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + + package + + shade + + + + + + + \ No newline at end of file diff --git a/samples/spring/pet-store/sam.yaml b/samples/spring/pet-store/sam.yaml new file mode 100644 index 000000000..82d85dbc7 --- /dev/null +++ b/samples/spring/pet-store/sam.yaml @@ -0,0 +1,26 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Example Pet Store API written with spring with the aws-serverless-java-container library +Resources: + PetStoreFunction: + Type: AWS::Serverless::Function + Properties: + Handler: com.amazonaws.serverless.sample.spring.LambdaHandler::handleRequest + Runtime: java8 + CodeUri: target/serverless-spring-example-1.0-SNAPSHOT.jar + MemorySize: 512 + Policies: AWSLambdaBasicExecutionRole + Timeout: 20 + Events: + GetResource: + Type: Api + Properties: + Path: /{proxy+} + Method: any + +Outputs: + SpringPetStoreApi: + Description: URL for application + Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/pets' + Export: + Name: SpringPetStoreApi diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/LambdaHandler.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/LambdaHandler.java new file mode 100644 index 000000000..1d799ed8f --- /dev/null +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/LambdaHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.spring; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringLambdacontainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +/** + * Created by bulianis on 12/13/16. + */ +public class LambdaHandler implements RequestHandler { + SpringLambdacontainerHandler handler; + boolean isinitialized = false; + + public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { + if (!isinitialized) { + isinitialized = true; + try { + handler = SpringLambdacontainerHandler.getAwsProxyHandler(PetStoreSpringAppConfig.class); + } catch (ContainerInitializationException e) { + e.printStackTrace(); + return null; + } + } + return handler.proxy(awsProxyRequest, context); + } +} diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetStoreSpringAppConfig.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetStoreSpringAppConfig.java new file mode 100644 index 000000000..5884d170c --- /dev/null +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetStoreSpringAppConfig.java @@ -0,0 +1,21 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.spring; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan("com.amazonaws.serverless.sample.spring") +public class PetStoreSpringAppConfig { +} diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetsController.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetsController.java new file mode 100644 index 000000000..9d866041d --- /dev/null +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/PetsController.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.spring; + +import com.amazonaws.serverless.sample.spring.model.Pet; +import com.amazonaws.serverless.sample.spring.model.PetData; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import java.util.Optional; +import java.util.UUID; + +@RestController +@EnableWebMvc +public class PetsController { + @RequestMapping(path = "/pets", method = RequestMethod.POST) + public Pet createPet(@RequestBody Pet newPet) { + if (newPet.getName() == null || newPet.getBreed() == null) { + return null; + } + + Pet dbPet = newPet; + dbPet.setId(UUID.randomUUID().toString()); + return dbPet; + } + + @RequestMapping(path = "/pets", method = RequestMethod.GET) + public Pet[] listPets(@RequestParam("limit") Optional limit) { + int queryLimit = 10; + if (limit.isPresent()) { + queryLimit = limit.get(); + } + + Pet[] outputPets = new Pet[queryLimit]; + + for (int i = 0; i < queryLimit; i++) { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setName(PetData.getRandomName()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); + outputPets[i] = newPet; + } + + return outputPets; + } + + @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET) + public Pet listPets() { + Pet newPet = new Pet(); + newPet.setId(UUID.randomUUID().toString()); + newPet.setBreed(PetData.getRandomBreed()); + newPet.setDateOfBirth(PetData.getRandomDoB()); + newPet.setName(PetData.getRandomName()); + return newPet; + } + +} diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/Error.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/Error.java new file mode 100644 index 000000000..1c7a96ce7 --- /dev/null +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/Error.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.spring.model; + +public class Error { + private String message; + + public Error(String errorMessage) { + message = errorMessage; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/Pet.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/Pet.java new file mode 100644 index 000000000..124351cfd --- /dev/null +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/Pet.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.spring.model; + +import java.util.Date; + +public class Pet { + private String id; + private String breed; + private String name; + private Date dateOfBirth; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBreed() { + return breed; + } + + public void setBreed(String breed) { + this.breed = breed; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getDateOfBirth() { + return dateOfBirth; + } + + public void setDateOfBirth(Date dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } +} diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/PetData.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/PetData.java new file mode 100644 index 000000000..b06348d91 --- /dev/null +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/model/PetData.java @@ -0,0 +1,111 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.sample.spring.model; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +public class PetData { + private static List breeds = new ArrayList<>(); + static { + breeds.add("Afghan Hound"); + breeds.add("Beagle"); + breeds.add("Bernese Mountain Dog"); + breeds.add("Bloodhound"); + breeds.add("Dalmatian"); + breeds.add("Jack Russell Terrier"); + breeds.add("Norwegian Elkhound"); + } + + private static List names = new ArrayList<>(); + static { + names.add("Bailey"); + names.add("Bella"); + names.add("Max"); + names.add("Lucy"); + names.add("Charlie"); + names.add("Molly"); + names.add("Buddy"); + names.add("Daisy"); + names.add("Rocky"); + names.add("Maggie"); + names.add("Jake "); + names.add("Sophie"); + names.add("Jack "); + names.add("Sadie"); + names.add("Toby "); + names.add("Chloe"); + names.add("Cody "); + names.add("Bailey"); + names.add("Buster"); + names.add("Lola"); + names.add("Duke "); + names.add("Zoe"); + names.add("Cooper"); + names.add("Abby"); + names.add("Riley"); + names.add("Ginger"); + names.add("Harley"); + names.add("Roxy"); + names.add("Bear"); + names.add("Gracie"); + names.add("Tucker"); + names.add("Coco"); + names.add("Murphy"); + names.add("Sasha"); + names.add("Lucky"); + names.add("Lily"); + names.add("Oliver"); + names.add("Angel"); + names.add("Sam"); + names.add("Princess"); + names.add("Oscar"); + names.add("Emma"); + names.add("Teddy"); + names.add("Annie"); + names.add("Winston"); + names.add("Rosie"); + } + + public static List getBreeds() { + return breeds; + } + + public static List getNames() { + return names; + } + + public static String getRandomBreed() { + return breeds.get(ThreadLocalRandom.current().nextInt(0, breeds.size() - 1)); + } + + public static String getRandomName() { + return names.get(ThreadLocalRandom.current().nextInt(0, names.size() - 1)); + } + + public static Date getRandomDoB() { + GregorianCalendar gc = new GregorianCalendar(); + + int year = ThreadLocalRandom.current().nextInt( + Calendar.getInstance().get(Calendar.YEAR) - 15, + Calendar.getInstance().get(Calendar.YEAR) + ); + + gc.set(Calendar.YEAR, year); + + int dayOfYear = ThreadLocalRandom.current().nextInt(1, gc.getActualMaximum(Calendar.DAY_OF_YEAR)); + + gc.set(Calendar.DAY_OF_YEAR, dayOfYear); + return gc.getTime(); + } +} From 45e3dca441714072ac58ff8eabf3ce4c82123464 Mon Sep 17 00:00:00 2001 From: joeyvmason Date: Tue, 20 Dec 2016 14:07:43 -0800 Subject: [PATCH 3/9] Renamed SpringLambdacontainerHandler to SpringLambdaContainerHandler --- .../proxy/spring/SpringLambdacontainerHandler.java | 10 +++++----- .../serverless/proxy/spring/SpringAwsProxyTest.java | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java index 2b6f2c9bf..44dbcd278 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java @@ -32,7 +32,7 @@ * @param The incoming event type * @param The expected return type */ -public class SpringLambdacontainerHandler extends LambdaContainerHandler { +public class SpringLambdaContainerHandler extends LambdaContainerHandler { private LambdaSpringApplicationInitializer initializer; // State vars @@ -44,10 +44,10 @@ public class SpringLambdacontainerHandler extends Lam * @return An initialized instance of the `SpringLambdaContainerHandler` * @throws ContainerInitializationException */ - public static SpringLambdacontainerHandler getAwsProxyHandler(Class... config) + public static SpringLambdaContainerHandler getAwsProxyHandler(Class... config) throws ContainerInitializationException { - SpringLambdacontainerHandler handler = - new SpringLambdacontainerHandler<>( + SpringLambdaContainerHandler handler = + new SpringLambdaContainerHandler<>( new AwsProxyHttpServletRequestReader(), new AwsProxyHttpServletResponseWriter(), new AwsProxySecurityContextWriter(), @@ -68,7 +68,7 @@ public static SpringLambdacontainerHandler ge * @param exceptionHandler An implementation of `ExceptionHandler` * @throws ContainerInitializationException */ - public SpringLambdacontainerHandler(RequestReader requestReader, + public SpringLambdaContainerHandler(RequestReader requestReader, ResponseWriter responseWriter, SecurityContextWriter securityContextWriter, ExceptionHandler exceptionHandler) diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java index 607d31521..aee11abf8 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java @@ -29,14 +29,14 @@ public class SpringAwsProxyTest { private static ObjectMapper objectMapper = new ObjectMapper(); - private static SpringLambdacontainerHandler handler = null; + private static SpringLambdaContainerHandler handler = null; private static Context lambdaContext = new MockLambdaContext(); @BeforeClass public static void init() { try { - handler = SpringLambdacontainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class); + handler = SpringLambdaContainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class); } catch (ContainerInitializationException e) { e.printStackTrace(); fail(); From 43eac78346d9513c9c47c379ce3f62f795f85a07 Mon Sep 17 00:00:00 2001 From: joeyvmason Date: Mon, 9 Jan 2017 00:16:49 -0800 Subject: [PATCH 4/9] Added ability to provide custom ApplicationContext. For Spring testing, made objects that can potentially be shared by multiple tests classes and methods beans. --- aws-serverless-java-container-spring/pom.xml | 7 +++ .../LambdaSpringApplicationInitializer.java | 48 +++----------- .../spring/SpringLambdacontainerHandler.java | 42 +++++-------- .../proxy/spring/SpringAwsProxyTest.java | 62 ++++++++++++++----- .../spring/echoapp/EchoSpringAppConfig.java | 28 +++++++++ 5 files changed, 105 insertions(+), 82 deletions(-) diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index 6ae41bbff..befa28384 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -45,6 +45,13 @@ ${spring.version} + + + org.springframework + spring-test + ${spring.version} + + commons-codec diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java index 0c4cfb39a..f20a615ab 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java @@ -12,10 +12,10 @@ */ package com.amazonaws.serverless.proxy.spring; -import com.amazonaws.serverless.exceptions.InvalidResponseObjectException; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.ServletRequestHandledEvent; @@ -44,11 +44,10 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial private static final String DEFAULT_SERVLET_NAME = "aws-servless-java-container"; // Configuration variables that can be passed in - private List configurationClasses; + private ConfigurableWebApplicationContext applicationContext; private List contextListeners; // Dynamically instantiated properties - private AnnotationConfigWebApplicationContext applicationContext; private ServletConfig dispatcherConfig; private DispatcherServlet dispatcherServlet; @@ -57,31 +56,11 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial /** * Creates a new instance of the WebApplicationInitializer + * @param applicationContext A custom ConfigurableWebApplicationContext to be used */ - public LambdaSpringApplicationInitializer() { - configurationClasses = new ArrayList<>(); - contextListeners = new ArrayList<>(); - } - - /** - * Adds a configuration class to the Spring startup process. This should be called at least once with an annotated - * class that contains the @Configuration annotations. The simplest possible class uses the @ComponentScan - * annocation to load all controllers. - * - *
-     * {@Code
-     * @Configuration
-     * @ComponentScan("com.amazonaws.serverless.proxy.spring.echoapp")
-     * public class EchoSpringAppConfig {
-     * }
-     * }
-     * 
- * @param configuration A set of configuration classes - */ - public void addConfigurationClasses(Class... configuration) { - for (Class config : configuration) { - configurationClasses.add(config); - } + public LambdaSpringApplicationInitializer(ConfigurableWebApplicationContext applicationContext) { + this.contextListeners = new ArrayList<>(); + this.applicationContext = applicationContext; } /** @@ -100,21 +79,8 @@ public void dispatch(HttpServletRequest request, HttpServletResponse response) dispatcherServlet.service(request, response); } - /** - * Returns the application context initialized in the library - * @return - */ - public AnnotationConfigWebApplicationContext getApplicationContext() { - return applicationContext; - } - @Override public void onStartup(ServletContext servletContext) throws ServletException { - // Create the 'root' Spring application context - applicationContext = new AnnotationConfigWebApplicationContext(); - for (Class config : configurationClasses) { - applicationContext.register(config); - } applicationContext.setServletContext(servletContext); dispatcherConfig = new DefaultDispatcherConfig(servletContext); @@ -138,7 +104,7 @@ public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledE // Register and map the dispatcher servlet dispatcherServlet = new DispatcherServlet(applicationContext); - dispatcherServlet.refresh(); +// dispatcherServlet.refresh(); dispatcherServlet.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); dispatcherServlet.init(dispatcherConfig); diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java index 44dbcd278..17c97f64b 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java @@ -21,6 +21,7 @@ import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; import com.amazonaws.services.lambda.runtime.Context; +import org.springframework.web.context.ConfigurableWebApplicationContext; import javax.servlet.ServletContext; import java.util.concurrent.CountDownLatch; @@ -38,25 +39,22 @@ public class SpringLambdaContainerHandler extends Lam // State vars private boolean initialized; + /** * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects - * @param config A set of classes annotated with the Spring @Configuration annotation + * @param applicationContext A custom ConfigurableWebApplicationContext to be used * @return An initialized instance of the `SpringLambdaContainerHandler` * @throws ContainerInitializationException */ - public static SpringLambdaContainerHandler getAwsProxyHandler(Class... config) + public static SpringLambdaContainerHandler getAwsProxyHandler(ConfigurableWebApplicationContext applicationContext) throws ContainerInitializationException { - SpringLambdaContainerHandler handler = - new SpringLambdaContainerHandler<>( - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler() - ); - - handler.addConfiguration(config); - - return handler; + return new SpringLambdaContainerHandler<>( + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + applicationContext + ); } /** @@ -69,20 +67,13 @@ public static SpringLambdaContainerHandler ge * @throws ContainerInitializationException */ public SpringLambdaContainerHandler(RequestReader requestReader, - ResponseWriter responseWriter, - SecurityContextWriter securityContextWriter, - ExceptionHandler exceptionHandler) + ResponseWriter responseWriter, + SecurityContextWriter securityContextWriter, + ExceptionHandler exceptionHandler, + ConfigurableWebApplicationContext applicationContext) throws ContainerInitializationException { super(requestReader, responseWriter, securityContextWriter, exceptionHandler); - initializer = new LambdaSpringApplicationInitializer(); - } - - /** - * Registers a set of classes with the underlying Spring application context - * @param config Spring annotated classes to be registered with the application context - */ - public void addConfiguration(Class... config) { - initializer.addConfigurationClasses(config); + initializer = new LambdaSpringApplicationInitializer(applicationContext); } @Override @@ -98,6 +89,7 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt initializer.onStartup(context); initialized = true; } + initializer.dispatch(containerRequest, containerResponse); } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java index aee11abf8..0d19e5884 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java @@ -1,6 +1,5 @@ package com.amazonaws.serverless.proxy.spring; -import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; @@ -12,36 +11,66 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; -import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.context.ConfigurableWebApplicationContext; import java.io.IOException; import java.util.UUID; import static org.junit.Assert.*; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.spring.echoapp.EchoSpringAppConfig; +import com.amazonaws.serverless.proxy.spring.echoapp.model.MapResponseModel; +import com.amazonaws.serverless.proxy.spring.echoapp.model.SingleValueModel; +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.binary.Base64; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.context.ConfigurableWebApplicationContext; -public class SpringAwsProxyTest { +import java.io.IOException; +import java.util.UUID; +import static org.junit.Assert.*; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {EchoSpringAppConfig.class}) +@WebAppConfiguration +@TestExecutionListeners(inheritListeners = false, listeners = {DependencyInjectionTestExecutionListener.class}) +public class SpringAwsProxyTest { private static final String CUSTOM_HEADER_KEY = "x-custom-header"; private static final String CUSTOM_HEADER_VALUE = "my-custom-value"; private static final String AUTHORIZER_PRINCIPAL_ID = "test-principal-" + UUID.randomUUID().toString(); + @Autowired + private ObjectMapper objectMapper; - private static ObjectMapper objectMapper = new ObjectMapper(); - private static SpringLambdaContainerHandler handler = null; + @Autowired + private MockLambdaContext lambdaContext; - private static Context lambdaContext = new MockLambdaContext(); - - @BeforeClass - public static void init() { - try { - handler = SpringLambdaContainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class); - } catch (ContainerInitializationException e) { - e.printStackTrace(); - fail(); - } - } + @Autowired + private SpringLambdaContainerHandler handler; @Test public void headers_getHeaders_echo() { @@ -188,3 +217,4 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) { } } } + diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java index a8032a5fa..024f45ae3 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java @@ -1,9 +1,37 @@ package com.amazonaws.serverless.proxy.spring.echoapp; +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.spring.LambdaSpringApplicationInitializer; +import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.web.context.ConfigurableWebApplicationContext; @Configuration @ComponentScan("com.amazonaws.serverless.proxy.spring.echoapp") public class EchoSpringAppConfig { + + @Autowired + private ConfigurableWebApplicationContext applicationContext; + + @Bean + public SpringLambdaContainerHandler springLambdaContainerHandler() throws ContainerInitializationException { + return SpringLambdaContainerHandler.getAwsProxyHandler(applicationContext); + } + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + @Bean + public MockLambdaContext lambdaContext() { + return new MockLambdaContext(); + } + } From 9d926bc4c65d5b9222963154a83db6a30271d15e Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Mon, 16 Jan 2017 15:33:10 -0800 Subject: [PATCH 5/9] New constructor & fixe file name bug Added a new constructor to accept an initialized implementation of WebApplicationContext and renamed the SpringLambdaContainerHandler file. --- .../LambdaSpringApplicationInitializer.java | 96 +++++++++++-------- ...java => SpringLambdaContainerHandler.java} | 12 ++- 2 files changed, 65 insertions(+), 43 deletions(-) rename aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/{SpringLambdacontainerHandler.java => SpringLambdaContainerHandler.java} (91%) diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java index 0c4cfb39a..90e3ceedf 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java @@ -12,11 +12,12 @@ */ package com.amazonaws.serverless.proxy.spring; -import com.amazonaws.serverless.exceptions.InvalidResponseObjectException; +import com.amazonaws.serverless.exceptions.ContainerInitializationException; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.ServletRequestHandledEvent; import org.springframework.web.servlet.DispatcherServlet; @@ -25,10 +26,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; +import java.util.*; /** * Custom implementation of Spring's `WebApplicationInitializer`. Uses internal variables to keep application state @@ -41,6 +39,8 @@ * Spring notifications for the `ServletRequestHandledEvent` and call the flush method to release the latch. */ public class LambdaSpringApplicationInitializer implements WebApplicationInitializer { + public static final String ERROR_NO_CONTEXT = "No application context or configuration classes provided"; + private static final String DEFAULT_SERVLET_NAME = "aws-servless-java-container"; // Configuration variables that can be passed in @@ -48,7 +48,7 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial private List contextListeners; // Dynamically instantiated properties - private AnnotationConfigWebApplicationContext applicationContext; + private WebApplicationContext applicationContext; private ServletConfig dispatcherConfig; private DispatcherServlet dispatcherServlet; @@ -56,32 +56,26 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial private HttpServletResponse currentResponse; /** - * Creates a new instance of the WebApplicationInitializer + * Starts the application initializer with a set of annotated configuration classes. At the first request, this will + * create a new instance of AnnotationWebApplicationContext using the array of configuration classes. + * + * @param configuration A set of configuration classes */ - public LambdaSpringApplicationInitializer() { - configurationClasses = new ArrayList<>(); + public LambdaSpringApplicationInitializer(Class... configuration) { + configurationClasses = new ArrayList<>(Arrays.asList(configuration)); contextListeners = new ArrayList<>(); } /** - * Adds a configuration class to the Spring startup process. This should be called at least once with an annotated - * class that contains the @Configuration annotations. The simplest possible class uses the @ComponentScan - * annocation to load all controllers. + * Creates a new instance of LambdaSpringApplicationInitializer with an instance of applicationContext. When created + * this way, annotated configuration classes have no effect and the library will only use the application context. * - *
-     * {@Code
-     * @Configuration
-     * @ComponentScan("com.amazonaws.serverless.proxy.spring.echoapp")
-     * public class EchoSpringAppConfig {
-     * }
-     * }
-     * 
- * @param configuration A set of configuration classes + * @param applicationContext An initialized implementation of WebApplicationContext */ - public void addConfigurationClasses(Class... configuration) { - for (Class config : configuration) { - configurationClasses.add(config); - } + public LambdaSpringApplicationInitializer(WebApplicationContext applicationContext) { + configurationClasses = new ArrayList<>(); + this.applicationContext = applicationContext; + contextListeners = new ArrayList<>(); } /** @@ -104,24 +98,53 @@ public void dispatch(HttpServletRequest request, HttpServletResponse response) * Returns the application context initialized in the library * @return */ - public AnnotationConfigWebApplicationContext getApplicationContext() { + public WebApplicationContext getApplicationContext() { return applicationContext; } @Override public void onStartup(ServletContext servletContext) throws ServletException { + if (applicationContext == null) { + if (configurationClasses.size() > 0) { + applicationContext = initializeAnnotationApplicationContext(servletContext); + } else { + throw new ServletException( + new ContainerInitializationException(ERROR_NO_CONTEXT, null) + ); + } + } + // Manage the lifecycle of the root application context + this.addListener(new ContextLoaderListener(applicationContext)); + + // Register and map the dispatcher servlet + dispatcherServlet = new DispatcherServlet(applicationContext); + dispatcherServlet.refresh(); + dispatcherServlet.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); + dispatcherServlet.init(dispatcherConfig); + + notifyStartListeners(servletContext); + } + + /** + * Initializes a new AnnotationConfigWebApplicationContext using the list of annotated configuration classes + * passed to the constructor + * + * @param servletContext The context from the servlet container + * @return A new instance of WebApplicationContext using the config classes passed to the constructor + */ + private AnnotationConfigWebApplicationContext initializeAnnotationApplicationContext(ServletContext servletContext) { // Create the 'root' Spring application context - applicationContext = new AnnotationConfigWebApplicationContext(); + AnnotationConfigWebApplicationContext newApplicationContext = new AnnotationConfigWebApplicationContext(); for (Class config : configurationClasses) { - applicationContext.register(config); + newApplicationContext.register(config); } - applicationContext.setServletContext(servletContext); + newApplicationContext.setServletContext(servletContext); dispatcherConfig = new DefaultDispatcherConfig(servletContext); - applicationContext.setServletConfig(dispatcherConfig); + newApplicationContext.setServletConfig(dispatcherConfig); // Configure the listener for the request handled events. All we do here is release the latch - applicationContext.addApplicationListener(new ApplicationListener() { + newApplicationContext.addApplicationListener(new ApplicationListener() { @Override public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledEvent) { try { @@ -133,16 +156,7 @@ public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledE } }); - // Manage the lifecycle of the root application context - this.addListener(new ContextLoaderListener(applicationContext)); - - // Register and map the dispatcher servlet - dispatcherServlet = new DispatcherServlet(applicationContext); - dispatcherServlet.refresh(); - dispatcherServlet.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); - dispatcherServlet.init(dispatcherConfig); - - notifyStartListeners(servletContext); + return newApplicationContext; } private void notifyStartListeners(ServletContext context) { diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java similarity index 91% rename from aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java rename to aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java index 44dbcd278..3da73fe26 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java @@ -21,6 +21,7 @@ import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; import com.amazonaws.services.lambda.runtime.Context; +import org.springframework.web.context.WebApplicationContext; import javax.servlet.ServletContext; import java.util.concurrent.CountDownLatch; @@ -74,7 +75,6 @@ public SpringLambdaContainerHandler(RequestReader exceptionHandler) throws ContainerInitializationException { super(requestReader, responseWriter, securityContextWriter, exceptionHandler); - initializer = new LambdaSpringApplicationInitializer(); } /** @@ -82,7 +82,11 @@ public SpringLambdaContainerHandler(RequestReader Date: Mon, 16 Jan 2017 15:35:58 -0800 Subject: [PATCH 6/9] Added static initializer that receives WebApplicationContext --- .../spring/SpringLambdaContainerHandler.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java index 3da73fe26..d7683f1d5 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java @@ -60,6 +60,27 @@ public static SpringLambdaContainerHandler ge return handler; } + /** + * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects + * @param appContext An initialized implementation of WebApplicationContext + * @return a new instance of SpringLambdaContainerHandler + * @throws ContainerInitializationException + */ + public static SpringLambdaContainerHandler getAwsProxyHandler(WebApplicationContext appContext) + throws ContainerInitializationException { + SpringLambdaContainerHandler handler = + new SpringLambdaContainerHandler<>( + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler() + ); + + handler.setApplicationContext(appContext); + + return handler; + } + /** * Creates a new container handler with the given reader and writer objects * From 709ad2d56202c5e5882c361306a6b10f8505b67d Mon Sep 17 00:00:00 2001 From: joeyvmason Date: Mon, 16 Jan 2017 23:21:09 -0800 Subject: [PATCH 7/9] Added the ability to provide configuration classes, as well as applicationContext. Added the ability to specify whether or not applicationContext should be refreshed during initialization. This is necessary because if you are using the application context that is automatically generated when running Spring tests, calling refresh() on the application context is not allowed. --- .../LambdaSpringApplicationInitializer.java | 11 ++++- .../spring/SpringLambdacontainerHandler.java | 49 +++++++++++++++++-- .../spring/echoapp/EchoSpringAppConfig.java | 2 +- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java index f20a615ab..efb4882d3 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java @@ -45,6 +45,7 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial // Configuration variables that can be passed in private ConfigurableWebApplicationContext applicationContext; + private boolean refreshContext; private List contextListeners; // Dynamically instantiated properties @@ -57,10 +58,12 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial /** * Creates a new instance of the WebApplicationInitializer * @param applicationContext A custom ConfigurableWebApplicationContext to be used + * @param applicationContext Whether or not to refresh the applicationContext */ - public LambdaSpringApplicationInitializer(ConfigurableWebApplicationContext applicationContext) { + public LambdaSpringApplicationInitializer(ConfigurableWebApplicationContext applicationContext, boolean refreshContext) { this.contextListeners = new ArrayList<>(); this.applicationContext = applicationContext; + this.refreshContext = refreshContext; } /** @@ -104,7 +107,11 @@ public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledE // Register and map the dispatcher servlet dispatcherServlet = new DispatcherServlet(applicationContext); -// dispatcherServlet.refresh(); + + if (refreshContext) { + dispatcherServlet.refresh(); + } + dispatcherServlet.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); dispatcherServlet.init(dispatcherConfig); diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java index 17c97f64b..51e6c717d 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java @@ -22,6 +22,7 @@ import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; import com.amazonaws.services.lambda.runtime.Context; import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import javax.servlet.ServletContext; import java.util.concurrent.CountDownLatch; @@ -39,6 +40,25 @@ public class SpringLambdaContainerHandler extends Lam // State vars private boolean initialized; + /** + * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects + * @param config A set of classes annotated with the Spring @Configuration annotation + * @return An initialized instance of the `SpringLambdaContainerHandler` + * @throws ContainerInitializationException + */ + public static SpringLambdaContainerHandler getAwsProxyHandler(Class... config) throws ContainerInitializationException { + AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); + applicationContext.register(config); + + return new SpringLambdaContainerHandler<>( + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + applicationContext, + true + ); + } /** * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects @@ -46,14 +66,15 @@ public class SpringLambdaContainerHandler extends Lam * @return An initialized instance of the `SpringLambdaContainerHandler` * @throws ContainerInitializationException */ - public static SpringLambdaContainerHandler getAwsProxyHandler(ConfigurableWebApplicationContext applicationContext) + public static SpringLambdaContainerHandler getAwsProxyHandler(ConfigurableWebApplicationContext applicationContext, boolean refresh) throws ContainerInitializationException { return new SpringLambdaContainerHandler<>( new AwsProxyHttpServletRequestReader(), new AwsProxyHttpServletResponseWriter(), new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler(), - applicationContext + applicationContext, + refresh ); } @@ -73,7 +94,29 @@ public SpringLambdaContainerHandler(RequestReader requestReader, + ResponseWriter responseWriter, + SecurityContextWriter securityContextWriter, + ExceptionHandler exceptionHandler, + ConfigurableWebApplicationContext applicationContext, + boolean refreshContext) + throws ContainerInitializationException { + super(requestReader, responseWriter, securityContextWriter, exceptionHandler); + initializer = new LambdaSpringApplicationInitializer(applicationContext, refreshContext); } @Override diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java index 024f45ae3..2c9fa85c4 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java @@ -21,7 +21,7 @@ public class EchoSpringAppConfig { @Bean public SpringLambdaContainerHandler springLambdaContainerHandler() throws ContainerInitializationException { - return SpringLambdaContainerHandler.getAwsProxyHandler(applicationContext); + return SpringLambdaContainerHandler.getAwsProxyHandler(applicationContext, false); } @Bean From ef2f14560ebed7d657ce20b8c250802eb49641e1 Mon Sep 17 00:00:00 2001 From: joeyvmason Date: Tue, 17 Jan 2017 11:08:05 -0800 Subject: [PATCH 8/9] Do not require refreshContext variable in constructor of LambdaSpringApplicationInitializer and SpringLambdacontainerHandler. Added setter for refreshContext. Added Spring test dependencies to test scope --- aws-serverless-java-container-spring/pom.xml | 3 +- .../LambdaSpringApplicationInitializer.java | 10 +++--- .../spring/SpringLambdacontainerHandler.java | 32 ++++--------------- .../spring/echoapp/EchoSpringAppConfig.java | 4 ++- 4 files changed, 17 insertions(+), 32 deletions(-) diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index befa28384..dbcb5b891 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -45,11 +45,12 @@ ${spring.version}
- + org.springframework spring-test ${spring.version} + test diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java index efb4882d3..3e0e6bd46 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java @@ -45,7 +45,7 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial // Configuration variables that can be passed in private ConfigurableWebApplicationContext applicationContext; - private boolean refreshContext; + private boolean refreshContext = true; private List contextListeners; // Dynamically instantiated properties @@ -58,12 +58,10 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial /** * Creates a new instance of the WebApplicationInitializer * @param applicationContext A custom ConfigurableWebApplicationContext to be used - * @param applicationContext Whether or not to refresh the applicationContext */ - public LambdaSpringApplicationInitializer(ConfigurableWebApplicationContext applicationContext, boolean refreshContext) { + public LambdaSpringApplicationInitializer(ConfigurableWebApplicationContext applicationContext) { this.contextListeners = new ArrayList<>(); this.applicationContext = applicationContext; - this.refreshContext = refreshContext; } /** @@ -76,6 +74,10 @@ public void addListener(ServletContextListener listener) { contextListeners.add(listener); } + public void setRefreshContext(boolean refreshContext) { + this.refreshContext = refreshContext; + } + public void dispatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { currentResponse = response; diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java index 51e6c717d..29fa9a223 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdacontainerHandler.java @@ -55,8 +55,7 @@ public static SpringLambdaContainerHandler ge new AwsProxyHttpServletResponseWriter(), new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler(), - applicationContext, - true + applicationContext ); } @@ -66,15 +65,14 @@ public static SpringLambdaContainerHandler ge * @return An initialized instance of the `SpringLambdaContainerHandler` * @throws ContainerInitializationException */ - public static SpringLambdaContainerHandler getAwsProxyHandler(ConfigurableWebApplicationContext applicationContext, boolean refresh) + public static SpringLambdaContainerHandler getAwsProxyHandler(ConfigurableWebApplicationContext applicationContext) throws ContainerInitializationException { return new SpringLambdaContainerHandler<>( new AwsProxyHttpServletRequestReader(), new AwsProxyHttpServletResponseWriter(), new AwsProxySecurityContextWriter(), new AwsProxyExceptionHandler(), - applicationContext, - refresh + applicationContext ); } @@ -94,29 +92,11 @@ public SpringLambdaContainerHandler(RequestReader requestReader, - ResponseWriter responseWriter, - SecurityContextWriter securityContextWriter, - ExceptionHandler exceptionHandler, - ConfigurableWebApplicationContext applicationContext, - boolean refreshContext) - throws ContainerInitializationException { - super(requestReader, responseWriter, securityContextWriter, exceptionHandler); - initializer = new LambdaSpringApplicationInitializer(applicationContext, refreshContext); + public void setRefreshContext(boolean refreshContext) { + this.initializer.setRefreshContext(refreshContext); } @Override diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java index 2c9fa85c4..53ace4766 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java @@ -21,7 +21,9 @@ public class EchoSpringAppConfig { @Bean public SpringLambdaContainerHandler springLambdaContainerHandler() throws ContainerInitializationException { - return SpringLambdaContainerHandler.getAwsProxyHandler(applicationContext, false); + SpringLambdaContainerHandler handler = SpringLambdaContainerHandler.getAwsProxyHandler(applicationContext); + handler.setRefreshContext(false); + return handler; } @Bean From af27cf709af3f34ece267c61bb8dd18ecb498c5a Mon Sep 17 00:00:00 2001 From: Stefano Buliani Date: Tue, 17 Jan 2017 14:16:06 -0800 Subject: [PATCH 9/9] Merge of pull request with conflicts Fixed issue with Spring sample app --- aws-serverless-java-container-spring/pom.xml | 9 ++ .../LambdaSpringApplicationInitializer.java | 95 ++++++------------- .../spring/SpringLambdaContainerHandler.java | 72 ++++++-------- .../proxy/spring/SpringAwsProxyTest.java | 36 +++---- .../spring/echoapp/EchoSpringAppConfig.java | 30 ++++++ .../sample/spring/LambdaHandler.java | 6 +- 6 files changed, 119 insertions(+), 129 deletions(-) diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index 6ae41bbff..96f432fdc 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -45,6 +45,14 @@ ${spring.version} + + + org.springframework + spring-test + ${spring.version} + test + + commons-codec @@ -67,6 +75,7 @@ ${jackson.version} test + com.fasterxml.jackson.core jackson-core diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java index 90e3ceedf..82b82c294 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/LambdaSpringApplicationInitializer.java @@ -12,10 +12,10 @@ */ package com.amazonaws.serverless.proxy.spring; -import com.amazonaws.serverless.exceptions.ContainerInitializationException; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; @@ -44,11 +44,11 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial private static final String DEFAULT_SERVLET_NAME = "aws-servless-java-container"; // Configuration variables that can be passed in - private List configurationClasses; + private ConfigurableWebApplicationContext applicationContext; + private boolean refreshContext = true; private List contextListeners; // Dynamically instantiated properties - private WebApplicationContext applicationContext; private ServletConfig dispatcherConfig; private DispatcherServlet dispatcherServlet; @@ -56,26 +56,12 @@ public class LambdaSpringApplicationInitializer implements WebApplicationInitial private HttpServletResponse currentResponse; /** - * Starts the application initializer with a set of annotated configuration classes. At the first request, this will - * create a new instance of AnnotationWebApplicationContext using the array of configuration classes. - * - * @param configuration A set of configuration classes + * Creates a new instance of the WebApplicationInitializer + * @param applicationContext A custom ConfigurableWebApplicationContext to be used */ - public LambdaSpringApplicationInitializer(Class... configuration) { - configurationClasses = new ArrayList<>(Arrays.asList(configuration)); - contextListeners = new ArrayList<>(); - } - - /** - * Creates a new instance of LambdaSpringApplicationInitializer with an instance of applicationContext. When created - * this way, annotated configuration classes have no effect and the library will only use the application context. - * - * @param applicationContext An initialized implementation of WebApplicationContext - */ - public LambdaSpringApplicationInitializer(WebApplicationContext applicationContext) { - configurationClasses = new ArrayList<>(); + public LambdaSpringApplicationInitializer(ConfigurableWebApplicationContext applicationContext) { + this.contextListeners = new ArrayList<>(); this.applicationContext = applicationContext; - contextListeners = new ArrayList<>(); } /** @@ -88,63 +74,25 @@ public void addListener(ServletContextListener listener) { contextListeners.add(listener); } + public void setRefreshContext(boolean refreshContext) { + this.refreshContext = refreshContext; + } + public void dispatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { currentResponse = response; dispatcherServlet.service(request, response); } - /** - * Returns the application context initialized in the library - * @return - */ - public WebApplicationContext getApplicationContext() { - return applicationContext; - } - @Override public void onStartup(ServletContext servletContext) throws ServletException { - if (applicationContext == null) { - if (configurationClasses.size() > 0) { - applicationContext = initializeAnnotationApplicationContext(servletContext); - } else { - throw new ServletException( - new ContainerInitializationException(ERROR_NO_CONTEXT, null) - ); - } - } - // Manage the lifecycle of the root application context - this.addListener(new ContextLoaderListener(applicationContext)); - - // Register and map the dispatcher servlet - dispatcherServlet = new DispatcherServlet(applicationContext); - dispatcherServlet.refresh(); - dispatcherServlet.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); - dispatcherServlet.init(dispatcherConfig); - - notifyStartListeners(servletContext); - } - - /** - * Initializes a new AnnotationConfigWebApplicationContext using the list of annotated configuration classes - * passed to the constructor - * - * @param servletContext The context from the servlet container - * @return A new instance of WebApplicationContext using the config classes passed to the constructor - */ - private AnnotationConfigWebApplicationContext initializeAnnotationApplicationContext(ServletContext servletContext) { - // Create the 'root' Spring application context - AnnotationConfigWebApplicationContext newApplicationContext = new AnnotationConfigWebApplicationContext(); - for (Class config : configurationClasses) { - newApplicationContext.register(config); - } - newApplicationContext.setServletContext(servletContext); + applicationContext.setServletContext(servletContext); dispatcherConfig = new DefaultDispatcherConfig(servletContext); - newApplicationContext.setServletConfig(dispatcherConfig); + applicationContext.setServletConfig(dispatcherConfig); // Configure the listener for the request handled events. All we do here is release the latch - newApplicationContext.addApplicationListener(new ApplicationListener() { + applicationContext.addApplicationListener(new ApplicationListener() { @Override public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledEvent) { try { @@ -156,7 +104,20 @@ public void onApplicationEvent(ServletRequestHandledEvent servletRequestHandledE } }); - return newApplicationContext; + // Manage the lifecycle of the root application context + this.addListener(new ContextLoaderListener(applicationContext)); + + // Register and map the dispatcher servlet + dispatcherServlet = new DispatcherServlet(applicationContext); + + if (refreshContext) { + dispatcherServlet.refresh(); + } + + dispatcherServlet.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); + dispatcherServlet.init(dispatcherConfig); + + notifyStartListeners(servletContext); } private void notifyStartListeners(ServletContext context) { diff --git a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java index d7683f1d5..ed3a1ba6e 100644 --- a/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java +++ b/aws-serverless-java-container-spring/src/main/java/com/amazonaws/serverless/proxy/spring/SpringLambdaContainerHandler.java @@ -21,7 +21,8 @@ import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; import com.amazonaws.services.lambda.runtime.Context; -import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import javax.servlet.ServletContext; import java.util.concurrent.CountDownLatch; @@ -45,40 +46,34 @@ public class SpringLambdaContainerHandler extends Lam * @return An initialized instance of the `SpringLambdaContainerHandler` * @throws ContainerInitializationException */ - public static SpringLambdaContainerHandler getAwsProxyHandler(Class... config) - throws ContainerInitializationException { - SpringLambdaContainerHandler handler = - new SpringLambdaContainerHandler<>( - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler() - ); - - handler.addConfiguration(config); - - return handler; + public static SpringLambdaContainerHandler getAwsProxyHandler(Class... config) throws ContainerInitializationException { + AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); + applicationContext.register(config); + + return new SpringLambdaContainerHandler<>( + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + applicationContext + ); } /** * Creates a default SpringLambdaContainerHandler initialized with the `AwsProxyRequest` and `AwsProxyResponse` objects - * @param appContext An initialized implementation of WebApplicationContext - * @return a new instance of SpringLambdaContainerHandler + * @param applicationContext A custom ConfigurableWebApplicationContext to be used + * @return An initialized instance of the `SpringLambdaContainerHandler` * @throws ContainerInitializationException */ - public static SpringLambdaContainerHandler getAwsProxyHandler(WebApplicationContext appContext) + public static SpringLambdaContainerHandler getAwsProxyHandler(ConfigurableWebApplicationContext applicationContext) throws ContainerInitializationException { - SpringLambdaContainerHandler handler = - new SpringLambdaContainerHandler<>( - new AwsProxyHttpServletRequestReader(), - new AwsProxyHttpServletResponseWriter(), - new AwsProxySecurityContextWriter(), - new AwsProxyExceptionHandler() - ); - - handler.setApplicationContext(appContext); - - return handler; + return new SpringLambdaContainerHandler<>( + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + applicationContext + ); } /** @@ -91,23 +86,17 @@ public static SpringLambdaContainerHandler ge * @throws ContainerInitializationException */ public SpringLambdaContainerHandler(RequestReader requestReader, - ResponseWriter responseWriter, - SecurityContextWriter securityContextWriter, - ExceptionHandler exceptionHandler) + ResponseWriter responseWriter, + SecurityContextWriter securityContextWriter, + ExceptionHandler exceptionHandler, + ConfigurableWebApplicationContext applicationContext) throws ContainerInitializationException { super(requestReader, responseWriter, securityContextWriter, exceptionHandler); + initializer = new LambdaSpringApplicationInitializer(applicationContext); } - /** - * Registers a set of classes with the underlying Spring application context - * @param config Spring annotated classes to be registered with the application context - */ - public void addConfiguration(Class... config) { - initializer = new LambdaSpringApplicationInitializer(config); - } - - public void setApplicationContext(WebApplicationContext context) { - initializer = new LambdaSpringApplicationInitializer(context); + public void setRefreshContext(boolean refreshContext) { + this.initializer.setRefreshContext(refreshContext); } @Override @@ -127,6 +116,7 @@ protected void handleRequest(AwsProxyHttpServletRequest containerRequest, AwsHtt initializer.onStartup(context); initialized = true; } + initializer.dispatch(containerRequest, containerResponse); } } diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java index aee11abf8..5683dec2f 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java @@ -1,6 +1,5 @@ package com.amazonaws.serverless.proxy.spring; -import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; @@ -8,40 +7,40 @@ import com.amazonaws.serverless.proxy.spring.echoapp.EchoSpringAppConfig; import com.amazonaws.serverless.proxy.spring.echoapp.model.MapResponseModel; import com.amazonaws.serverless.proxy.spring.echoapp.model.SingleValueModel; -import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.codec.binary.Base64; -import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; +import org.springframework.test.context.web.WebAppConfiguration; import java.io.IOException; import java.util.UUID; import static org.junit.Assert.*; - +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = {EchoSpringAppConfig.class}) +@WebAppConfiguration +@TestExecutionListeners(inheritListeners = false, listeners = {DependencyInjectionTestExecutionListener.class}) public class SpringAwsProxyTest { - private static final String CUSTOM_HEADER_KEY = "x-custom-header"; private static final String CUSTOM_HEADER_VALUE = "my-custom-value"; private static final String AUTHORIZER_PRINCIPAL_ID = "test-principal-" + UUID.randomUUID().toString(); + @Autowired + private ObjectMapper objectMapper; - private static ObjectMapper objectMapper = new ObjectMapper(); - private static SpringLambdaContainerHandler handler = null; + @Autowired + private MockLambdaContext lambdaContext; - private static Context lambdaContext = new MockLambdaContext(); - - @BeforeClass - public static void init() { - try { - handler = SpringLambdaContainerHandler.getAwsProxyHandler(EchoSpringAppConfig.class); - } catch (ContainerInitializationException e) { - e.printStackTrace(); - fail(); - } - } + @Autowired + private SpringLambdaContainerHandler handler; @Test public void headers_getHeaders_echo() { @@ -188,3 +187,4 @@ private void validateSingleValueModel(AwsProxyResponse output, String value) { } } } + diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java index a8032a5fa..53ace4766 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoSpringAppConfig.java @@ -1,9 +1,39 @@ package com.amazonaws.serverless.proxy.spring.echoapp; +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.spring.LambdaSpringApplicationInitializer; +import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler; +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.web.context.ConfigurableWebApplicationContext; @Configuration @ComponentScan("com.amazonaws.serverless.proxy.spring.echoapp") public class EchoSpringAppConfig { + + @Autowired + private ConfigurableWebApplicationContext applicationContext; + + @Bean + public SpringLambdaContainerHandler springLambdaContainerHandler() throws ContainerInitializationException { + SpringLambdaContainerHandler handler = SpringLambdaContainerHandler.getAwsProxyHandler(applicationContext); + handler.setRefreshContext(false); + return handler; + } + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + @Bean + public MockLambdaContext lambdaContext() { + return new MockLambdaContext(); + } + } diff --git a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/LambdaHandler.java b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/LambdaHandler.java index 1d799ed8f..d52fc8a5d 100644 --- a/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/LambdaHandler.java +++ b/samples/spring/pet-store/src/main/java/com/amazonaws/serverless/sample/spring/LambdaHandler.java @@ -15,7 +15,7 @@ import com.amazonaws.serverless.exceptions.ContainerInitializationException; import com.amazonaws.serverless.proxy.internal.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.internal.model.AwsProxyResponse; -import com.amazonaws.serverless.proxy.spring.SpringLambdacontainerHandler; +import com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; @@ -23,14 +23,14 @@ * Created by bulianis on 12/13/16. */ public class LambdaHandler implements RequestHandler { - SpringLambdacontainerHandler handler; + SpringLambdaContainerHandler handler; boolean isinitialized = false; public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context context) { if (!isinitialized) { isinitialized = true; try { - handler = SpringLambdacontainerHandler.getAwsProxyHandler(PetStoreSpringAppConfig.class); + handler = SpringLambdaContainerHandler.getAwsProxyHandler(PetStoreSpringAppConfig.class); } catch (ContainerInitializationException e) { e.printStackTrace(); return null;