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