From 42353cb3d3e36b47363715f8b1feab78d3a42888 Mon Sep 17 00:00:00 2001 From: mbfreder Date: Tue, 23 Jan 2024 17:32:02 -0800 Subject: [PATCH 1/2] Added SpringBoot3-GraphQl sample --- .../springboot3/graphql-pet-store/README.md | 38 ++++ samples/springboot3/graphql-pet-store/pom.xml | 162 ++++++++++++++++++ .../graphql-pet-store/src/assembly/bin.xml | 27 +++ .../sample/springboot3/Application.java | 49 ++++++ .../springboot3/StreamLambdaHandler.java | 50 ++++++ .../controller/PetsController.java | 21 +++ .../filter/CognitoIdentityFilter.java | 69 ++++++++ .../sample/springboot3/model/Owner.java | 20 +++ .../sample/springboot3/model/Pet.java | 20 +++ .../main/resources/graphql/schema.graphqls | 16 ++ .../src/main/resources/logback.xml | 5 + .../graphql-pet-store/template.yml | 32 ++++ 12 files changed, 509 insertions(+) create mode 100644 samples/springboot3/graphql-pet-store/README.md create mode 100644 samples/springboot3/graphql-pet-store/pom.xml create mode 100644 samples/springboot3/graphql-pet-store/src/assembly/bin.xml create mode 100644 samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java create mode 100644 samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java create mode 100644 samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java create mode 100644 samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java create mode 100644 samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Owner.java create mode 100644 samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java create mode 100644 samples/springboot3/graphql-pet-store/src/main/resources/graphql/schema.graphqls create mode 100644 samples/springboot3/graphql-pet-store/src/main/resources/logback.xml create mode 100644 samples/springboot3/graphql-pet-store/template.yml diff --git a/samples/springboot3/graphql-pet-store/README.md b/samples/springboot3/graphql-pet-store/README.md new file mode 100644 index 000000000..7d18a0dd7 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/README.md @@ -0,0 +1,38 @@ +# Serverless Spring Boot 3 and Spring-graphQl example +A basic pet store written with the [Spring Boot 3 framework](https://projects.spring.io/spring-boot/). Unlike older examples, this example uses the [Spring for GraphQl](https://docs.spring.io/spring-graphql/reference/) library. + + +The application can be deployed in an AWS account using the [Serverless Application Model](https://github.com/awslabs/serverless-application-model). The `template.yml` file in the root folder contains the application definition. + +## Pre-requisites +* [AWS CLI](https://aws.amazon.com/cli/) +* [SAM CLI](https://github.com/awslabs/aws-sam-cli) +* [Gradle](https://gradle.org/) or [Maven](https://maven.apache.org/) + +## Deployment +In a shell, navigate to the sample's folder and use the SAM CLI to build a deployable package +``` +$ sam build +``` + +This command compiles the application and prepares a deployment package in the `.aws-sam` sub-directory. + +To deploy the application in your AWS account, you can use the SAM CLI's guided deployment process and follow the instructions on the screen + +``` +$ sam deploy --guided +``` + +Once the deployment is completed, the SAM CLI will print out the stack's outputs, including the new application URL. You can use `curl` to make a call to the URL + +``` +... +--------------------------------------------------------------------------------------------------------- +OutputKey-Description OutputValue +--------------------------------------------------------------------------------------------------------- +PetStoreApi - URL for application https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/graphQl +--------------------------------------------------------------------------------------------------------- + +$ curl -X POST https://xxxxxxxxxx.execute-api.us-west-2.amazonaws.com/graphQl -d '{"query":"query petDetails {\n petById(id: \"pet-1\") {\n id\n name\n breed\n owner {\n id\n firstName\n lastName\n }\n }\n}","operationName":"petDetails"}' -H "Content-Type: application/json" + +``` \ No newline at end of file diff --git a/samples/springboot3/graphql-pet-store/pom.xml b/samples/springboot3/graphql-pet-store/pom.xml new file mode 100644 index 000000000..35535e2a4 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/pom.xml @@ -0,0 +1,162 @@ + + + 4.0.0 + + com.amazonaws.serverless.sample + serverless-springboot3-example + 2.0-SNAPSHOT + Spring Boot example for the aws-serverless-java-container library + Simple pet store written with the Spring framework and Spring Boot + https://aws.amazon.com/lambda/ + + + org.springframework.boot + spring-boot-starter-parent + 3.2.2 + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 21 + + + + + org.springframework.boot + spring-boot-starter-graphql + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.graphql + spring-graphql-test + test + + + com.amazonaws.serverless + aws-serverless-java-container-springboot3 + 2.0.0-SNAPSHOT + + + + + + shaded-jar + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.1 + + false + + + + package + + shade + + + + + org.apache.tomcat.embed:* + + + + + + + + + + + assembly-zip + + true + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + default-jar + none + + + + + org.apache.maven.plugins + maven-install-plugin + 3.1.1 + + true + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.6.1 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + runtime + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.6.0 + + + zip-assembly + package + + single + + + ${project.artifactId}-${project.version} + + src${file.separator}assembly${file.separator}bin.xml + + false + + + + + + + + + + + diff --git a/samples/springboot3/graphql-pet-store/src/assembly/bin.xml b/samples/springboot3/graphql-pet-store/src/assembly/bin.xml new file mode 100644 index 000000000..efc312c25 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/assembly/bin.xml @@ -0,0 +1,27 @@ + + lambda-package + + zip + + false + + + + ${project.build.directory}${file.separator}lib + lib + + tomcat-embed* + + + + + ${project.build.directory}${file.separator}classes + + ** + + ${file.separator} + + + diff --git a/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java new file mode 100644 index 000000000..678477a27 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java @@ -0,0 +1,49 @@ +package com.amazonaws.serverless.sample.springboot3; + +import com.amazonaws.serverless.sample.springboot3.controller.PetsController; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + + +@SpringBootApplication +@Import({ PetsController.class }) +public class Application { + + // silence console logging + @Value("${logging.level.root:OFF}") + String message = ""; + + /* + * Create required HandlerMapping, to avoid several default HandlerMapping instances being created + */ + @Bean + public HandlerMapping handlerMapping() { + return new RequestMappingHandlerMapping(); + } + + /* + * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created + */ + @Bean + public HandlerAdapter handlerAdapter() { + return new RequestMappingHandlerAdapter(); + } + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java new file mode 100644 index 000000000..a65c6f1ec --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/StreamLambdaHandler.java @@ -0,0 +1,50 @@ +package com.amazonaws.serverless.sample.springboot3; + + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; +import com.amazonaws.serverless.sample.springboot3.filter.CognitoIdentityFilter; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; + +import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterRegistration; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.EnumSet; + + +public class StreamLambdaHandler implements RequestStreamHandler { + private static SpringBootLambdaContainerHandler handler; + static { + try { + handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class); + + // we use the onStartup method of the handler to register our custom filter + handler.onStartup(servletContext -> { + FilterRegistration.Dynamic registration = servletContext.addFilter("CognitoIdentityFilter", CognitoIdentityFilter.class); + registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); + }); + } catch (ContainerInitializationException e) { + // if we fail here. We re-throw the exception to force another cold start + e.printStackTrace(); + throw new RuntimeException("Could not initialize Spring Boot application", e); + } + } + + public StreamLambdaHandler() { + // we enable the timer for debugging. This SHOULD NOT be enabled in production. + Timer.enable(); + } + + @Override + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) + throws IOException { + handler.proxyStream(inputStream, outputStream, context); + } +} diff --git a/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java new file mode 100644 index 000000000..93eb999bd --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/controller/PetsController.java @@ -0,0 +1,21 @@ +package com.amazonaws.serverless.sample.springboot3.controller; + +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.graphql.data.method.annotation.SchemaMapping; +import org.springframework.stereotype.Controller; +import com.amazonaws.serverless.sample.springboot3.model.Owner; +import com.amazonaws.serverless.sample.springboot3.model.Pet; + +@Controller +public class PetsController { + @QueryMapping + public Pet petById(@Argument String id) { + return Pet.getById(id); + } + + @SchemaMapping + public Owner owner(Pet pet) { + return Owner.getById(pet.ownerId()); + } +} diff --git a/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java new file mode 100644 index 000000000..d6ccae765 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/filter/CognitoIdentityFilter.java @@ -0,0 +1,69 @@ +package com.amazonaws.serverless.sample.springboot3.filter; + + +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +import java.io.IOException; + + +/** + * Simple Filter implementation that looks for a Cognito identity id in the API Gateway request context + * and stores the value in a request attribute. The filter is registered with aws-serverless-java-container + * in the onStartup method from the {@link com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler} class. + */ +public class CognitoIdentityFilter implements Filter { + public static final String COGNITO_IDENTITY_ATTRIBUTE = "com.amazonaws.serverless.cognitoId"; + + private static Logger log = LoggerFactory.getLogger(CognitoIdentityFilter.class); + + @Override + public void init(FilterConfig filterConfig) + throws ServletException { + // nothing to do in init + } + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + Object apiGwContext = servletRequest.getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY); + if (apiGwContext == null) { + log.warn("API Gateway context is null"); + filterChain.doFilter(servletRequest, servletResponse); + return; + } + if (!AwsProxyRequestContext.class.isAssignableFrom(apiGwContext.getClass())) { + log.warn("API Gateway context object is not of valid type"); + filterChain.doFilter(servletRequest, servletResponse); + } + + AwsProxyRequestContext ctx = (AwsProxyRequestContext)apiGwContext; + if (ctx.getIdentity() == null) { + log.warn("Identity context is null"); + filterChain.doFilter(servletRequest, servletResponse); + } + String cognitoIdentityId = ctx.getIdentity().getCognitoIdentityId(); + if (cognitoIdentityId == null || "".equals(cognitoIdentityId.trim())) { + log.warn("Cognito identity id in request is null"); + } + servletRequest.setAttribute(COGNITO_IDENTITY_ATTRIBUTE, cognitoIdentityId); + filterChain.doFilter(servletRequest, servletResponse); + } + + + @Override + public void destroy() { + // nothing to do in destroy + } +} diff --git a/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Owner.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Owner.java new file mode 100644 index 000000000..f048e6906 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Owner.java @@ -0,0 +1,20 @@ +package com.amazonaws.serverless.sample.springboot3.model; + +import java.util.Arrays; +import java.util.List; + +public record Owner (String id, String firstName, String lastName) { + + private static List owners = Arrays.asList( + new Owner("owner-1", "Joshua", "Bloch"), + new Owner("owner-2", "Douglas", "Adams"), + new Owner("owner-3", "Bill", "Bryson") + ); + + public static Owner getById(String id) { + return owners.stream() + .filter(owner -> owner.id().equals(id)) + .findFirst() + .orElse(null); + } +} diff --git a/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java new file mode 100644 index 000000000..f97b973d0 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/model/Pet.java @@ -0,0 +1,20 @@ +package com.amazonaws.serverless.sample.springboot3.model; + +import java.util.Arrays; +import java.util.List; + +public record Pet (String id, String name, String breed, String ownerId) { + + private static List pets = Arrays.asList( + new Pet("pet-1", "Alpha", "Bulldog", "owner-1"), + new Pet("pet-2", "Max", "German Shepherd", "owner-2"), + new Pet("pet-3", "Rockie", "Golden Retriever", "owner-3") + ); + + public static Pet getById(String id) { + return pets.stream() + .filter(pet -> pet.id().equals(id)) + .findFirst() + .orElse(null); + } +} diff --git a/samples/springboot3/graphql-pet-store/src/main/resources/graphql/schema.graphqls b/samples/springboot3/graphql-pet-store/src/main/resources/graphql/schema.graphqls new file mode 100644 index 000000000..293cdcc40 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/resources/graphql/schema.graphqls @@ -0,0 +1,16 @@ +type Query { + petById(id: ID): Pet +} + +type Pet { + id: ID + name: String + breed: String + owner: Owner +} + +type Owner { + id: ID + firstName: String + lastName: String +} diff --git a/samples/springboot3/graphql-pet-store/src/main/resources/logback.xml b/samples/springboot3/graphql-pet-store/src/main/resources/logback.xml new file mode 100644 index 000000000..8ff988992 --- /dev/null +++ b/samples/springboot3/graphql-pet-store/src/main/resources/logback.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/samples/springboot3/graphql-pet-store/template.yml b/samples/springboot3/graphql-pet-store/template.yml new file mode 100644 index 000000000..c6543b15f --- /dev/null +++ b/samples/springboot3/graphql-pet-store/template.yml @@ -0,0 +1,32 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Example Pet Store API written with SpringBoot3, Spring for GraphQl and the aws-serverless-java-container library + +Globals: + Api: + # API Gateway regional endpoints + EndpointConfiguration: REGIONAL + +Resources: + PetStoreFunction: + Type: AWS::Serverless::Function + Properties: + Handler: com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler::handleRequest + Runtime: java21 + CodeUri: . + MemorySize: 1512 + Policies: AWSLambdaBasicExecutionRole + Timeout: 60 + Events: + HttpApiEvent: + Type: HttpApi + Properties: + TimeoutInMillis: 20000 + PayloadFormatVersion: '1.0' + +Outputs: + SpringBootPetStoreApi: + Description: URL for application + Value: !Sub 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/graphql' + Export: + Name: SpringBootPetStoreApi From cc462ff952ef8d6bb3dc617f0bfb5bbda57ab6c5 Mon Sep 17 00:00:00 2001 From: mbfreder Date: Wed, 24 Jan 2024 13:53:20 -0800 Subject: [PATCH 2/2] Deleted unused imports --- samples/springboot3/graphql-pet-store/README.md | 2 +- .../serverless/sample/springboot3/Application.java | 6 ------ samples/springboot3/graphql-pet-store/template.yml | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/samples/springboot3/graphql-pet-store/README.md b/samples/springboot3/graphql-pet-store/README.md index 7d18a0dd7..e5bfad120 100644 --- a/samples/springboot3/graphql-pet-store/README.md +++ b/samples/springboot3/graphql-pet-store/README.md @@ -1,4 +1,4 @@ -# Serverless Spring Boot 3 and Spring-graphQl example +# Serverless Spring Boot 3 with GraphQL example A basic pet store written with the [Spring Boot 3 framework](https://projects.spring.io/spring-boot/). Unlike older examples, this example uses the [Spring for GraphQl](https://docs.spring.io/spring-graphql/reference/) library. diff --git a/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java index 678477a27..9cf0ea610 100644 --- a/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java +++ b/samples/springboot3/graphql-pet-store/src/main/java/com/amazonaws/serverless/sample/springboot3/Application.java @@ -5,19 +5,13 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.web.servlet.HandlerAdapter; -import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - @SpringBootApplication @Import({ PetsController.class }) diff --git a/samples/springboot3/graphql-pet-store/template.yml b/samples/springboot3/graphql-pet-store/template.yml index c6543b15f..ce5dcc6b1 100644 --- a/samples/springboot3/graphql-pet-store/template.yml +++ b/samples/springboot3/graphql-pet-store/template.yml @@ -14,7 +14,7 @@ Resources: Handler: com.amazonaws.serverless.sample.springboot3.StreamLambdaHandler::handleRequest Runtime: java21 CodeUri: . - MemorySize: 1512 + MemorySize: 1024 Policies: AWSLambdaBasicExecutionRole Timeout: 60 Events: