diff --git a/README.md b/README.md index 752dca60f..3823b65ba 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # Serverless Java container [![Build Status](https://travis-ci.org/awslabs/aws-serverless-java-container.svg?branch=master)](https://travis-ci.org/awslabs/aws-serverless-java-container) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.amazonaws.serverless/aws-serverless-java-container/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.amazonaws.serverless/aws-serverless-java-container) [![Help](http://img.shields.io/badge/help-gitter-E91E63.svg?style=flat-square)](https://gitter.im/awslabs/aws-serverless-java-container) -The `aws-serverless-java-container` makes it easy to run Java applications written with frameworks such as [Spring](https://spring.io/), [Spring Boot](https://projects.spring.io/spring-boot/), [Jersey](https://jersey.java.net/), or [Spark](http://sparkjava.com/) in [AWS Lambda](https://aws.amazon.com/lambda/). +The `aws-serverless-java-container` makes it easy to run Java applications written with frameworks such as [Spring](https://spring.io/), [Spring Boot](https://projects.spring.io/spring-boot/), [Apache Struts](http://struts.apache.org/), [Jersey](https://jersey.java.net/), or [Spark](http://sparkjava.com/) in [AWS Lambda](https://aws.amazon.com/lambda/). Serverless Java Container natively supports API Gateway's proxy integration models for requests and responses, you can create and inject custom models for methods that use custom mappings. Follow the quick start guides in [our wiki](https://github.com/awslabs/aws-serverless-java-container/wiki) to integrate Serverless Java Container with your project: * [Spring quick start](https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Spring) * [Spring Boot quick start](https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Spring-Boot) +* [Apache Struts quick start](https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start-Struts) * [Jersey quick start](https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Jersey) * [Spark quick start](https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Spark) diff --git a/archetypes/struts/pom.xml b/archetypes/struts/pom.xml new file mode 100644 index 000000000..9175a40d2 --- /dev/null +++ b/archetypes/struts/pom.xml @@ -0,0 +1,47 @@ + + 4.0.0 + + + com.amazonaws.serverless + aws-serverless-java-container + 1.2-SNAPSHOT + + + com.amazonaws.serverless.archetypes + aws-serverless-struts-archetype + 1.2-SNAPSHOT + maven-archetype + + + 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 + + + + + + + org.apache.maven.archetype + archetype-packaging + 3.0.1 + + + + + + + org.apache.maven.plugins + maven-archetype-plugin + 3.0.1 + + + + + \ No newline at end of file diff --git a/archetypes/struts/src/main/resources/META-INF/maven/archetype-metadata.xml b/archetypes/struts/src/main/resources/META-INF/maven/archetype-metadata.xml new file mode 100644 index 000000000..7aa1c89bd --- /dev/null +++ b/archetypes/struts/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -0,0 +1,33 @@ + + + + src/main/java + + **/*.java + + + + src/main/resources + + **/*.properties + **/*.xml + + + + src/main/assembly + + **/*.xml + + + + + + sam.yaml + README.md + + + + \ No newline at end of file diff --git a/archetypes/struts/src/main/resources/archetype-resources/README.md b/archetypes/struts/src/main/resources/archetype-resources/README.md new file mode 100644 index 000000000..4b646a46e --- /dev/null +++ b/archetypes/struts/src/main/resources/archetype-resources/README.md @@ -0,0 +1,129 @@ +#set($resourceName = $artifactId) +#macro(replaceChar $originalName, $char) + #if($originalName.contains($char)) + #set($tokens = $originalName.split($char)) + #set($newResourceName = "") + #foreach($token in $tokens) + #set($newResourceName = $newResourceName + $token.substring(0,1).toUpperCase() + $token.substring(1).toLowerCase()) + #end + ${newResourceName} + #else + #set($newResourceName = $originalName.substring(0,1).toUpperCase() + $originalName.substring(1)) + ${newResourceName} + #end +#end +#set($resourceName = "#replaceChar($resourceName, '-')") +#set($resourceName = "#replaceChar($resourceName, '.')") +#set($resourceName = $resourceName.replaceAll("\n", "").trim()) +# ${artifactId} serverless API +The ${artifactId} project, created with [`aws-serverless-java-container`](https://github.com/awslabs/aws-serverless-java-container). + +The starter project defines a simple `/ping` resource that can accept `GET` requests with its tests. + +The project folder also includes a `sam.yaml` file. You can use this [SAM](https://github.com/awslabs/serverless-application-model) file to deploy the project to AWS Lambda and Amazon API Gateway or test in local with [SAM Local](https://github.com/awslabs/aws-sam-local). + +## Building the project +Using [Maven](https://maven.apache.org/), you can create an AWS Lambda-compatible jar file simply by running the maven package command from the projct folder. + +```bash +$ mvn archetype:generate -DartifactId=my-spring-api -DarchetypeGroupId=com.amazonaws.serverless.archetypes -DarchetypeArtifactId=aws-serverless-spring-archetype -DarchetypeVersion=1.0-SNAPSHOT -DgroupId=com.sapessi.spring -Dversion=0.1 -Dinteractive=false +$ cd my-spring-api +$ mvn clean package + +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 6.546 s +[INFO] Finished at: 2018-02-15T08:39:33-08:00 +[INFO] Final Memory: XXM/XXXM +[INFO] ------------------------------------------------------------------------ +``` + +## Testing locally with SAM local +You can use [AWS SAM Local](https://github.com/awslabs/aws-sam-local) to start your project. + +First, install SAM local: + +```bash +$ npm install -g aws-sam-local +``` + +Next, from the project root folder - where the `sam.yaml` file is located - start the API with the SAM Local CLI. + +```bash +$ sam local start-api --template sam.yaml + +... +Mounting ${groupId}.StreamLambdaHandler::handleRequest (java8) at http://127.0.0.1:3000/{proxy+} [OPTIONS GET HEAD POST PUT DELETE PATCH] +... +``` + +Using a new shell, you can send a test ping request to your API: + +```bash +$ curl -s http://127.0.0.1:3000/ping | python -m json.tool + +{ + "pong": "Hello, World!" +} +``` + +## Deploying to AWS +You can use the [AWS CLI](https://aws.amazon.com/cli/) to quickly deploy your application to AWS Lambda and Amazon API Gateway with your SAM template. + +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 project's root folder - where the `sam.yaml` file is located: + +``` +$ 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 ServerlessSpringApi --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 `ServerlessSpringApi` key of the `Outputs` property: + +``` +$ aws cloudformation describe-stacks --stack-name ServerlessSpringApi +{ + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:xxxxxxxx:stack/ServerlessSpringApi/xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", + "Description": "AWS Serverless Spring API - ${groupId}::${artifactId}", + "Tags": [], + "Outputs": [ + { + "Description": "URL for application", + "ExportName": "${resourceName}Api", + "OutputKey": "${resourceName}Api", + "OutputValue": "https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping" + } + ], + "CreationTime": "2016-12-13T22:59:31.552Z", + "Capabilities": [ + "CAPABILITY_IAM" + ], + "StackName": "ServerlessSpringApi", + "NotificationARNs": [], + "StackStatus": "UPDATE_COMPLETE" + } + ] +} + +``` + +Copy the `OutputValue` into a browser or use curl to test your first request: + +```bash +$ curl -s https://xxxxxxx.execute-api.us-west-2.amazonaws.com/Prod/ping | python -m json.tool + +{ + "pong": "Hello, World!" +} +``` \ No newline at end of file diff --git a/archetypes/struts/src/main/resources/archetype-resources/pom.xml b/archetypes/struts/src/main/resources/archetype-resources/pom.xml new file mode 100644 index 000000000..bb05d5edf --- /dev/null +++ b/archetypes/struts/src/main/resources/archetype-resources/pom.xml @@ -0,0 +1,153 @@ + + + 4.0.0 + + ${groupId} + ${artifactId} + ${version} + jar + + Serverless Spring Boot API + https://github.com/awslabs/aws-serverless-java-container + + + org.springframework.boot + spring-boot-starter-parent + 1.5.10.RELEASE + + + + 1.8 + 1.8 + 2.5.17 + 2.9.5 + 4.12 + 2.8.2 + + + + + com.amazonaws.serverless + aws-serverless-java-container-struts2 + 1.2.0 + + + + com.amazonaws + aws-lambda-java-core + 1.2.0 + + + + org.apache.struts + struts2-convention-plugin + ${struts2.version} + + + + org.apache.struts + struts2-rest-plugin + ${struts2.version} + + + + org.apache.struts + struts2-bean-validation-plugin + ${struts2.version} + + + + org.apache.struts + struts2-junit-plugin + ${struts2.version} + test + + + + + com.jgeppert.struts2 + struts2-aws-lambda-support-plugin + 1.0.0 + + + + + org.hibernate + hibernate-validator + 4.3.2.Final + + + + com.fasterxml.jackson.core + jackson-core + 2.9.4 + + + com.fasterxml.jackson.core + jackson-annotations + 2.9.4 + + + com.fasterxml.jackson.core + jackson-databind + 2.9.4 + + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j.version} + + + + com.amazonaws + aws-lambda-java-log4j2 + 1.1.0 + + + + junit + junit + ${junit.version} + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.0 + + + src/main/assembly/dist.xml + + + + + lambda + package + + single + + + + + + + diff --git a/archetypes/struts/src/main/resources/archetype-resources/sam.yaml b/archetypes/struts/src/main/resources/archetype-resources/sam.yaml new file mode 100644 index 000000000..d17e69de7 --- /dev/null +++ b/archetypes/struts/src/main/resources/archetype-resources/sam.yaml @@ -0,0 +1,48 @@ +#set($resourceName = $artifactId) +#macro(replaceChar $originalName, $char) + #if($originalName.contains($char)) + #set($tokens = $originalName.split($char)) + #set($newResourceName = "") + #foreach($token in $tokens) + #set($newResourceName = $newResourceName + $token.substring(0,1).toUpperCase() + $token.substring(1).toLowerCase()) + #end + ${newResourceName} + #else + #set($newResourceName = $originalName.substring(0,1).toUpperCase() + $originalName.substring(1)) + ${newResourceName} + #end +#end +#set($resourceName = "#replaceChar($resourceName, '-')") +#set($resourceName = "#replaceChar($resourceName, '.')") +#set($resourceName = $resourceName.replaceAll("\n", "").trim()) +#macro(regionVar) + AWS::Region +#end +#set($awsRegion = "#regionVar()") +#set($awsRegion = $awsRegion.replaceAll("\n", "").trim()) +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: AWS Serverless Apache Struts2 API - ${groupId}::${artifactId} +Resources: + ${resourceName}Function: + Type: AWS::Serverless::Function + Properties: + Handler: com.amazonaws.serverless.proxy.struts2.Struts2LambdaHandler::handleRequest + Runtime: java8 + CodeUri: target/${artifactId}-${version}-lambda.zip + MemorySize: 512 + Policies: AWSLambdaBasicExecutionRole + Timeout: 30 + Events: + GetResource: + Type: Api + Properties: + Path: /{proxy+} + Method: any + +Outputs: + ${resourceName}Api: + Description: URL for application + Value: !Sub 'https://${ServerlessRestApi}.execute-api.${${awsRegion}}.amazonaws.com/Prod/ping' + Export: + Name: ${resourceName}Api diff --git a/archetypes/struts/src/main/resources/archetype-resources/src/main/assembly/dist.xml b/archetypes/struts/src/main/resources/archetype-resources/src/main/assembly/dist.xml new file mode 100644 index 000000000..029ec01c7 --- /dev/null +++ b/archetypes/struts/src/main/resources/archetype-resources/src/main/assembly/dist.xml @@ -0,0 +1,31 @@ + + lambda + + zip + + false + + + lib + false + + + + + ${basedir}/src/main/resources + / + + * + + + + ${project.build.directory}/classes + / + + **/*.class + + + + \ No newline at end of file diff --git a/archetypes/struts/src/main/resources/archetype-resources/src/main/java/actions/PingController.java b/archetypes/struts/src/main/resources/archetype-resources/src/main/java/actions/PingController.java new file mode 100644 index 000000000..b8a67e00a --- /dev/null +++ b/archetypes/struts/src/main/resources/archetype-resources/src/main/java/actions/PingController.java @@ -0,0 +1,74 @@ +package ${groupId}.controller; + + +import com.opensymphony.xwork2.ModelDriven; +import org.apache.struts2.rest.DefaultHttpHeaders; +import org.apache.struts2.rest.HttpHeaders; +import org.apache.struts2.rest.RestActionSupport; + + +import java.util.Collection; +import java.util.UUID; + + +public class PingController extends RestActionSupport implements ModelDriven { + + private String model = new String(); + private String id; + private Collection list = null; + + + // GET /ping/1 + public HttpHeaders show() { + return new DefaultHttpHeaders("show"); + } + + // GET /ping + public HttpHeaders index() { + this.model = "Hello, World!"; + return new DefaultHttpHeaders("index") + .disableCaching(); + } + + // POST /ping + public HttpHeaders create() { + if (model.getName() == null || model.getBreed() == null) { + return null; + } + + this.model = UUID.randomUUID().toString(); + return new DefaultHttpHeaders("success") + .setLocationId(model.getId()); + + } + + // PUT /ping/1 + public String update() { + //TODO: UPDATE LOGIC + return SUCCESS; + } + + // DELETE /ping/1 + public String destroy() { + //TODO: DELETE LOGIC + return SUCCESS; + } + + public void setId(String id) { + if (id != null) { + this.model = "New model instance"; + } + this.id = id; + } + + public Object getModel() { + if (list != null) { + return list; + } else { + if (model == null) { + model = "Pong"; + } + return model; + } + } +} diff --git a/archetypes/struts/src/main/resources/archetype-resources/src/main/resources/application.properties b/archetypes/struts/src/main/resources/archetype-resources/src/main/resources/application.properties new file mode 100644 index 000000000..ec1cb9792 --- /dev/null +++ b/archetypes/struts/src/main/resources/archetype-resources/src/main/resources/application.properties @@ -0,0 +1,3 @@ +# Reduce logging level to make sure the application works with SAM local +# https://github.com/awslabs/aws-serverless-java-container/issues/134 +logging.level.root=WARN \ No newline at end of file diff --git a/archetypes/struts/src/main/resources/archetype-resources/src/main/resources/log4j2.xml b/archetypes/struts/src/main/resources/archetype-resources/src/main/resources/log4j2.xml new file mode 100644 index 000000000..55ed0d21c --- /dev/null +++ b/archetypes/struts/src/main/resources/archetype-resources/src/main/resources/log4j2.xml @@ -0,0 +1,17 @@ + + + + + + %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n + + + + + + + + + + + \ No newline at end of file diff --git a/archetypes/struts/src/main/resources/archetype-resources/src/main/resources/struts.xml b/archetypes/struts/src/main/resources/archetype-resources/src/main/resources/struts.xml new file mode 100644 index 000000000..d0a1c31d2 --- /dev/null +++ b/archetypes/struts/src/main/resources/archetype-resources/src/main/resources/struts.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aws-serverless-java-container-struts2/pom.xml b/aws-serverless-java-container-struts2/pom.xml new file mode 100644 index 000000000..13401b81c --- /dev/null +++ b/aws-serverless-java-container-struts2/pom.xml @@ -0,0 +1,170 @@ + + + 4.0.0 + + aws-serverless-java-container-struts2 + AWS Serverless Java container support - Struts2 implementation + Allows Java applications written for the Struts2 framework to run in AWS Lambda + https://aws.amazon.com/lambda + 1.2-SNAPSHOT + + + com.amazonaws.serverless + aws-serverless-java-container + 1.2-SNAPSHOT + + + + 2.5.18 + 2.9.5 + + + + + + com.amazonaws.serverless + aws-serverless-java-container-core + 1.2-SNAPSHOT + + + + org.apache.struts + struts2-core + ${struts2.version} + + + commons-io + commons-io + + + + + + org.apache.struts + struts2-json-plugin + ${struts2.version} + test + + + + org.apache.struts + struts2-junit-plugin + ${struts2.version} + test + + + + + commons-codec + commons-codec + 1.10 + test + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + org.apache.logging.log4j + log4j-to-slf4j + 2.10.0 + + + + javax.el + javax.el-api + 2.2.4 + test + + + + javax.servlet + jsp-api + 2.0 + test + + + + org.glassfish.web + javax.el + 2.2.4 + test + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.9 + + always + + + + com.github.spotbugs + spotbugs-maven-plugin + 3.1.1 + + + Max + + Low + + true + + ${project.build.directory}/spotbugs + + + + com.h3xstream.findsecbugs + findsecbugs-plugin + 1.7.1 + + + + + + + analyze-compile + compile + + check + + + + + + org.owasp + dependency-check-maven + 3.1.1 + + true + + ${project.basedir}/../owasp-suppression.xml + + 7 + + + + + check + + + + + + + diff --git a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java new file mode 100644 index 000000000..003390ec0 --- /dev/null +++ b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaContainerHandler.java @@ -0,0 +1,112 @@ +package com.amazonaws.serverless.proxy.struts2; + +import com.amazonaws.serverless.exceptions.ContainerInitializationException; +import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; +import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; +import com.amazonaws.serverless.proxy.ExceptionHandler; +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.ResponseWriter; +import com.amazonaws.serverless.proxy.SecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; +import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; +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.serverless.proxy.internal.testutils.Timer; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.services.lambda.runtime.Context; +import org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.DispatcherType; +import javax.servlet.FilterRegistration; +import java.util.EnumSet; +import java.util.concurrent.CountDownLatch; + +/** + * A Lambda handler to initialize the Struts2 filter and proxy the requests. + * + * @param request type + * @param response type + */ +public class Struts2LambdaContainerHandler extends AwsLambdaServletContainerHandler { + + private static final Logger log = LoggerFactory.getLogger(Struts2LambdaContainerHandler.class); + + public static final String HEADER_STRUTS_STATUS_CODE = "X-Struts-StatusCode"; + + private static final String TIMER_STRUTS_2_CONTAINER_CONSTRUCTOR = "STRUTS2_CONTAINER_CONSTRUCTOR"; + private static final String TIMER_STRUTS_2_HANDLE_REQUEST = "STRUTS2_HANDLE_REQUEST"; + private static final String TIMER_STRUTS_2_COLD_START_INIT = "STRUTS2_COLD_START_INIT"; + private static final String STRUTS_FILTER_NAME = "Struts2Filter"; + + private boolean initialized; + + public static Struts2LambdaContainerHandler getAwsProxyHandler() { + return new Struts2LambdaContainerHandler( + AwsProxyRequest.class, + AwsProxyResponse.class, + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler()); + } + + public Struts2LambdaContainerHandler(Class requestTypeClass, + Class responseTypeClass, + RequestReader requestReader, + ResponseWriter responseWriter, + SecurityContextWriter securityContextWriter, + ExceptionHandler exceptionHandler) { + + super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); + Timer.start(TIMER_STRUTS_2_CONTAINER_CONSTRUCTOR); + this.initialized = false; + Timer.stop(TIMER_STRUTS_2_CONTAINER_CONSTRUCTOR); + } + + protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + return new AwsHttpServletResponse(request, latch); + } + + @Override + protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, + AwsHttpServletResponse httpServletResponse, + Context lambdaContext) throws Exception { + Timer.start(TIMER_STRUTS_2_HANDLE_REQUEST); + if (!this.initialized) { + initialize(); + } + + httpServletRequest.setServletContext(this.getServletContext()); + this.doFilter(httpServletRequest, httpServletResponse, null); + String responseStatusCode = httpServletResponse.getHeader(HEADER_STRUTS_STATUS_CODE); + if (responseStatusCode != null) { + httpServletResponse.setStatus(Integer.parseInt(responseStatusCode)); + } + Timer.stop(TIMER_STRUTS_2_HANDLE_REQUEST); + } + + @Override + public void initialize() throws ContainerInitializationException { + log.info("Initialize Struts2 Lambda Application ..."); + Timer.start(TIMER_STRUTS_2_COLD_START_INIT); + try { + if (this.startupHandler != null) { + this.startupHandler.onStartup(this.getServletContext()); + } + StrutsPrepareAndExecuteFilter filter = new StrutsPrepareAndExecuteFilter(); + FilterRegistration.Dynamic filterRegistration = this.getServletContext() + .addFilter(STRUTS_FILTER_NAME, filter); + filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); + } catch (Exception e) { + throw new ContainerInitializationException("Could not initialize Struts2", e); + } + + this.initialized = true; + Timer.stop(TIMER_STRUTS_2_COLD_START_INIT); + log.info("... initialize of Struts2 Lambda Application completed!"); + } +} diff --git a/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaHandler.java b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaHandler.java new file mode 100644 index 000000000..e2afdfb47 --- /dev/null +++ b/aws-serverless-java-container-struts2/src/main/java/com/amazonaws/serverless/proxy/struts2/Struts2LambdaHandler.java @@ -0,0 +1,45 @@ +package com.amazonaws.serverless.proxy.struts2; + +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The lambda handler to handle the requests. + *

+ * + * com.amazonaws.serverless.proxy.struts2.Struts2LambdaHandler::handleRequest + * + */ +public class Struts2LambdaHandler implements RequestStreamHandler { + + private static final Logger log = LoggerFactory.getLogger(Struts2LambdaHandler.class); + + private final Struts2LambdaContainerHandler handler = Struts2LambdaContainerHandler + .getAwsProxyHandler(); + + @Override + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) { + + try { + AwsProxyRequest request = LambdaContainerHandler.getObjectMapper() + .readValue(inputStream, AwsProxyRequest.class); + + AwsProxyResponse response = handler.proxy(request, context); + LambdaContainerHandler.getObjectMapper().writeValue(outputStream, response); + + // just in case it wasn't closed by the mapper + outputStream.close(); + } catch (IOException e) { + log.error("An unexpected exception happened while handling request", e); + } + } +} diff --git a/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java new file mode 100644 index 000000000..f1e4b4b8f --- /dev/null +++ b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/Struts2AwsProxyTest.java @@ -0,0 +1,326 @@ +/* + * 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.struts2; + + +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.serverless.proxy.struts2.echoapp.EchoAction; +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.codec.binary.Base64; +import org.apache.struts2.StrutsJUnit4TestCase; +import org.junit.Test; + +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Unit test class for the Struts2 AWS_PROXY default implementation + */ +public class Struts2AwsProxyTest extends StrutsJUnit4TestCase { + 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 final String QUERY_STRING_KEY = "message"; + private static final String QUERY_STRING_ENCODED_VALUE = "Hello Struts2"; + private static final String USER_PRINCIPAL = "user1"; + private static final String CONTENT_TYPE_APPLICATION_JSON = "application/json;charset=UTF-8"; + + + private static ObjectMapper objectMapper = new ObjectMapper(); + private final Struts2LambdaContainerHandler handler = Struts2LambdaContainerHandler + .getAwsProxyHandler(); + private static Context lambdaContext = new MockLambdaContext(); + + @Test + public void headers_getHeaders_echo() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + .queryString("mode", "headers") + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .json() + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getHeaders().get("Content-Type")); + + validateMapResponseModel(output); + } + + @Test + public void context_servletResponse_setCustomHeader() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET") + .queryString("customHeader", "true") + .json() + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertTrue(output.getHeaders().containsKey("XX")); + } + + @Test + public void context_serverInfo_correctContext() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET") + .queryString(QUERY_STRING_KEY, "Hello Struts2") + .header("Content-Type", "application/json") + .queryString("contentType", "true") + .build(); + AwsProxyResponse output = handler.proxy(request, lambdaContext); + for (String header : output.getHeaders().keySet()) { + System.out.println(header + ": " + output.getHeaders().get(header)); + } + assertEquals(200, output.getStatusCode()); + assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getHeaders().get("Content-Type")); + System.out.println("Body: " + output.getBody()); + + validateSingleValueModel(output, "Hello Struts2"); + } + + @Test + public void queryString_uriInfo_echo() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + .queryString("mode", "query-string") + .queryString(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .json() + .build(); + + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getHeaders().get("Content-Type")); + + validateMapResponseModel(output); + } + + @Test + public void requestScheme_valid_expectHttps() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + .queryString("mode", "scheme") + .queryString(QUERY_STRING_KEY, QUERY_STRING_ENCODED_VALUE) + .json() + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getHeaders().get("Content-Type")); + + validateSingleValueModel(output, "https"); + } + + @Test + public void authorizer_securityContext_customPrincipalSuccess() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + .queryString("mode", "principal") + .json() + .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals(CONTENT_TYPE_APPLICATION_JSON, output.getHeaders().get("Content-Type")); + + validateSingleValueModel(output, AUTHORIZER_PRINCIPAL_ID); + } + + @Test + public void errors_unknownRoute_expect404() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/unknown", "GET").build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(404, output.getStatusCode()); + } + + @Test + public void error_contentType_invalidContentType() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "POST") + .queryString("mode", "content-type") + .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-request-info", "POST") + .queryString("mode", "not-allowed") + .json() + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(405, output.getStatusCode()); + } + + + @Test + public void responseBody_responseWriter_validBody() throws JsonProcessingException { + Map value = new HashMap<>(); + value.put(QUERY_STRING_KEY, CUSTOM_HEADER_VALUE); + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "POST") + .json() + .body(objectMapper.writeValueAsString(value)) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertNotNull(output.getBody()); + + validateSingleValueModel(output, "{\"message\":\"my-custom-value\"}"); + } + + @Test + public void statusCode_responseStatusCode_customStatusCode() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + .queryString("mode", "custom-status-code") + .queryString("status", "201") + .json() + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(201, output.getStatusCode()); + } + + @Test + public void base64_binaryResponse_base64Encoding() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET").build(); + + AwsProxyResponse response = handler.proxy(request, lambdaContext); + assertNotNull(response.getBody()); + assertTrue(Base64.isBase64(response.getBody())); + } + + @Test + public void exception_mapException_mapToNotImplemented() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "POST") + .queryString("mode", "not-implemented") + .build(); + + AwsProxyResponse response = handler.proxy(request, lambdaContext); + assertNotNull(response.getBody()); + assertEquals("null", response.getBody()); + assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), response.getStatusCode()); + } + + @Test + public void stripBasePath_route_shouldRouteCorrectly() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/custompath/echo", "GET") + .json() + .queryString(QUERY_STRING_KEY, "stripped") + .build(); + handler.stripBasePath("/custompath"); + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + validateSingleValueModel(output, "stripped"); + handler.stripBasePath(""); + } + + @Test + public void stripBasePath_route_shouldReturn404() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/custompath/echo/status-code", "GET") + .json() + .queryString("status", "201") + .build(); + handler.stripBasePath("/custom"); + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(404, output.getStatusCode()); + handler.stripBasePath(""); + } + + @Test + public void securityContext_injectPrincipal_expectPrincipalName() { + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo-request-info", "GET") + .queryString("mode", "principal") + .authorizerPrincipal(USER_PRINCIPAL).build(); + + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, USER_PRINCIPAL); + } + + @Test + public void queryParam_encoding_expectUnencodedParam() { + String paramValue = "p%2Fz%2B3"; + String decodedParam = ""; + try { + decodedParam = URLDecoder.decode(paramValue, "UTF-8"); + System.out.println(decodedParam); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + fail("Could not decode parameter"); + } + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET").queryString(QUERY_STRING_KEY, decodedParam) + .build(); + + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, decodedParam); + } + + @Test + public void queryParam_encoding_expectEncodedParam() { + String paramValue = "p%2Fz%2B3"; + AwsProxyRequest request = new AwsProxyRequestBuilder("/echo", "GET").queryString(QUERY_STRING_KEY, paramValue) + .build(); + + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, paramValue); + } + + + private void validateMapResponseModel(AwsProxyResponse output) { + validateMapResponseModel(output, CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); + } + + private void validateMapResponseModel(AwsProxyResponse output, String key, String value) { + try { + TypeReference> typeRef + = new TypeReference>() { + }; + HashMap response = objectMapper.readValue(output.getBody(), typeRef); + assertNotNull(response.get(key)); + assertEquals(value, response.get(key)); + } catch (IOException e) { + fail("Exception while parsing response body: " + e.getMessage()); + e.printStackTrace(); + } + } + + private void validateSingleValueModel(AwsProxyResponse output, String value) { + try { + assertNotNull(output.getBody()); + assertEquals(value, objectMapper.readerFor(String.class).readValue(output.getBody())); + } catch (Exception e) { + fail("Exception while parsing response body: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoAction.java b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoAction.java new file mode 100644 index 000000000..bfaa0a69f --- /dev/null +++ b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoAction.java @@ -0,0 +1,53 @@ +package com.amazonaws.serverless.proxy.struts2.echoapp; + +import com.opensymphony.xwork2.ActionSupport; +import org.apache.commons.io.IOUtils; +import org.apache.struts2.ServletActionContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + + +public class EchoAction extends ActionSupport { + + private String message; + + public String execute() throws IOException { + HttpServletRequest request = ServletActionContext.getRequest(); + + if (message == null && requestHasBody(request)) { + message = IOUtils.toString(request.getReader()); + } + + return SUCCESS; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public void setCustomHeader(boolean customHeader) { + if (customHeader) { + HttpServletResponse response = ServletActionContext.getResponse(); + response.setHeader("XX", "FOO"); + } + } + + + public void setContentType(boolean contentType) { + if (contentType) { + HttpServletResponse response = ServletActionContext.getResponse(); + response.setContentType("application/json"); + } + } + + private boolean requestHasBody(HttpServletRequest request) throws IOException { + return ("POST".equalsIgnoreCase(request.getMethod()) || "PUT".equalsIgnoreCase(request.getMethod())) && request.getReader() != null; + } + +} diff --git a/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoRequestInfoAction.java b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoRequestInfoAction.java new file mode 100644 index 000000000..42f7f3c15 --- /dev/null +++ b/aws-serverless-java-container-struts2/src/test/java/com/amazonaws/serverless/proxy/struts2/echoapp/EchoRequestInfoAction.java @@ -0,0 +1,85 @@ +package com.amazonaws.serverless.proxy.struts2.echoapp; + +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.model.ApiGatewayRequestContext; +import com.opensymphony.xwork2.ActionSupport; +import org.apache.struts2.ServletActionContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + + +public class EchoRequestInfoAction extends ActionSupport { + + private String mode = "principal"; + private Object result = null; + + public String execute() { + + HttpServletRequest request = ServletActionContext.getRequest(); + ApiGatewayRequestContext apiGatewayRequestContext = + (ApiGatewayRequestContext) request + .getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY); + + switch (mode) { + case "principal": + result = apiGatewayRequestContext.getAuthorizer().getPrincipalId(); + break; + case "scheme": + result = request.getScheme(); + break; + case "content-type": + if (request.getContentType().contains("application/octet-stream")) { + ServletActionContext.getResponse().setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE); + } + result = request.getContentType(); + break; + case "not-allowed": + ServletActionContext.getResponse().setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + break; + case "custom-status-code": + ServletActionContext.getResponse().setStatus(HttpServletResponse.SC_CREATED); + break; + case "not-implemented": + ServletActionContext.getResponse().setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); + break; + case "headers": + Map headers = new HashMap<>(); + + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + headers.put(headerName, request.getHeader(headerName)); + } + + result = headers; + break; + case "query-string": + Map params = new HashMap<>(); + + Enumeration parameterNames = request.getParameterNames(); + while (parameterNames.hasMoreElements()) { + String parameterName = parameterNames.nextElement(); + params.put(parameterName, request.getParameter(parameterName)); + } + + result = params; + break; + default: + throw new IllegalArgumentException("Invalid mode requested: " + mode); + } + + return SUCCESS; + } + + public Object getResult() { + return result; + } + + public void setMode(String mode) { + this.mode = mode; + } +} diff --git a/aws-serverless-java-container-struts2/src/test/resources/struts.xml b/aws-serverless-java-container-struts2/src/test/resources/struts.xml new file mode 100644 index 000000000..0c5dd8328 --- /dev/null +++ b/aws-serverless-java-container-struts2/src/test/resources/struts.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + message + + + + + + + + result + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9e57634da..ec032e28a 100644 --- a/pom.xml +++ b/pom.xml @@ -19,10 +19,12 @@ aws-serverless-java-container-jersey aws-serverless-java-container-spark aws-serverless-java-container-spring + aws-serverless-java-container-struts2 archetypes/jersey archetypes/spark archetypes/spring archetypes/springboot + archetypes/struts diff --git a/samples/struts/pet-store/README.md b/samples/struts/pet-store/README.md new file mode 100644 index 000000000..9203dd896 --- /dev/null +++ b/samples/struts/pet-store/README.md @@ -0,0 +1,62 @@ +# Serverless Struts2 example +A basic pet store written with the [Apache Struts framework](https://struts.apache.org). The `Struts2LambdaHandler` object provided by the `aws-serverless-java-container-struts2` 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-struts-example-1.0-SNAPSHOT.jar` in the `target` folder. Now that we have generated the zip 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 Apache Struts 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": "StrutsSample", + "NotificationARNs": [], + "StackStatus": "UPDATE_COMPLETE" + } + ] +} + +``` + +Copy the `OutputValue` into a browser to test a first request. diff --git a/samples/struts/pet-store/pom.xml b/samples/struts/pet-store/pom.xml new file mode 100644 index 000000000..9aab925d0 --- /dev/null +++ b/samples/struts/pet-store/pom.xml @@ -0,0 +1,167 @@ + + + 4.0.0 + + com.amazonaws.serverless.sample + serverless-struts-example + 1.0-SNAPSHOT + Struts2 example for the aws-serverless-java-container library + Simple pet store written with the Apache Struts 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.5.17 + 2.9.5 + 4.12 + 2.8.2 + + + + + com.amazonaws.serverless + aws-serverless-java-container-struts2 + [0.1,) + + + + com.amazonaws + aws-lambda-java-core + 1.2.0 + + + + org.apache.struts + struts2-convention-plugin + ${struts2.version} + + + + org.apache.struts + struts2-rest-plugin + ${struts2.version} + + + + org.apache.struts + struts2-bean-validation-plugin + ${struts2.version} + + + + org.apache.struts + struts2-junit-plugin + ${struts2.version} + test + + + + + com.jgeppert.struts2 + struts2-aws-lambda-support-plugin + 1.0.0 + + + + + org.hibernate + hibernate-validator + 4.3.2.Final + + + + com.fasterxml.jackson.core + jackson-core + 2.9.4 + + + com.fasterxml.jackson.core + jackson-annotations + 2.9.4 + + + com.fasterxml.jackson.core + jackson-databind + 2.9.4 + + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j.version} + + + + com.amazonaws + aws-lambda-java-log4j2 + 1.1.0 + + + + junit + junit + ${junit.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.0 + + + src/main/assembly/dist.xml + + + + + lambda + package + + single + + + + + + + + diff --git a/samples/struts/pet-store/sam.yaml b/samples/struts/pet-store/sam.yaml new file mode 100644 index 000000000..e91f9d597 --- /dev/null +++ b/samples/struts/pet-store/sam.yaml @@ -0,0 +1,32 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Example Pet Store API written with Apache Struts based on 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.proxy.struts2.Struts2LambdaHandler::handleRequest + Runtime: java8 + CodeUri: serverless-struts-example-1.0-SNAPSHOT-lambda.zip + MemorySize: 256 + Policies: AWSLambdaBasicExecutionRole + Timeout: 30 + 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: Struts2PetStoreApi diff --git a/samples/struts/pet-store/src/main/assembly/dist.xml b/samples/struts/pet-store/src/main/assembly/dist.xml new file mode 100644 index 000000000..029ec01c7 --- /dev/null +++ b/samples/struts/pet-store/src/main/assembly/dist.xml @@ -0,0 +1,31 @@ + + lambda + + zip + + false + + + lib + false + + + + + ${basedir}/src/main/resources + / + + * + + + + ${project.build.directory}/classes + / + + **/*.class + + + + \ No newline at end of file diff --git a/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/actions/PetsController.java b/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/actions/PetsController.java new file mode 100644 index 000000000..bc2ad8bbe --- /dev/null +++ b/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/actions/PetsController.java @@ -0,0 +1,92 @@ +/* + * 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.struts.actions; + +import com.amazonaws.serverless.sample.struts.model.Pet; +import com.amazonaws.serverless.sample.struts.model.PetData; +import com.opensymphony.xwork2.ModelDriven; +import org.apache.struts2.rest.DefaultHttpHeaders; +import org.apache.struts2.rest.HttpHeaders; +import org.apache.struts2.rest.RestActionSupport; + +import java.util.Collection; +import java.util.UUID; +import java.util.stream.Collectors; + + +public class PetsController extends RestActionSupport implements ModelDriven { + + private Pet model = new Pet(); + private String id; + private Collection list = null; + + // GET /pets/1 + public HttpHeaders show() { + return new DefaultHttpHeaders("show"); + } + + // GET /pets + public HttpHeaders index() { + list = PetData.getNames() + .stream() + .map(petName -> new Pet( + UUID.randomUUID() + .toString(), PetData.getRandomBreed(), petName, PetData.getRandomDoB())) + .collect(Collectors.toList()); + return new DefaultHttpHeaders("index") + .disableCaching(); + } + + // POST /pets + public HttpHeaders create() { + if (model.getName() == null || model.getBreed() == null) { + return null; + } + + Pet dbPet = model; + dbPet.setId(UUID.randomUUID().toString()); + return new DefaultHttpHeaders("success") + .setLocationId(model.getId()); + + } + + // PUT /pets/1 + public String update() { + //TODO: UPDATE LOGIC + return SUCCESS; + } + + // DELETE /petsr/1 + public String destroy() { + //TODO: DELETE LOGIC + return SUCCESS; + } + + public void setId(String id) { + if (id != null) { + this.model = new Pet(id, PetData.getRandomBreed(), PetData.getRandomName(), PetData.getRandomDoB()); + } + this.id = id; + } + + public Object getModel() { + if (list != null) { + return list; + } else { + if (model == null) { + model = new Pet(); + } + return model; + } + } +} diff --git a/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/model/Pet.java b/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/model/Pet.java new file mode 100644 index 000000000..c9d420ca8 --- /dev/null +++ b/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/model/Pet.java @@ -0,0 +1,69 @@ +/* + * 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.struts.model; + +import org.hibernate.validator.constraints.NotBlank; + +import java.util.Date; + +public class Pet { + + private String id; + private String breed; + + @NotBlank + private String name; + private Date dateOfBirth; + + public Pet() { + } + + public Pet(String id, String breed, String name, Date dateOfBirth) { + this.id = id; + this.breed = breed; + this.name = name; + this.dateOfBirth = 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/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/model/PetData.java b/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/model/PetData.java new file mode 100644 index 000000000..84be64eab --- /dev/null +++ b/samples/struts/pet-store/src/main/java/com/amazonaws/serverless/sample/struts/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.struts.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/struts/pet-store/src/main/resources/log4j2.xml b/samples/struts/pet-store/src/main/resources/log4j2.xml new file mode 100644 index 000000000..55ed0d21c --- /dev/null +++ b/samples/struts/pet-store/src/main/resources/log4j2.xml @@ -0,0 +1,17 @@ + + + + + + %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n + + + + + + + + + + + \ No newline at end of file diff --git a/samples/struts/pet-store/src/main/resources/struts.xml b/samples/struts/pet-store/src/main/resources/struts.xml new file mode 100644 index 000000000..038370d3d --- /dev/null +++ b/samples/struts/pet-store/src/main/resources/struts.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +