forked from aws-powertools/powertools-lambda-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add base RequestHandler class for custom resources
Provides abstract methods for generating the Response to be sent, represented as its own type. Use Objects::nonNull instead of custom method. Addresses aws-powertools#558
- Loading branch information
Joe Wolf
committed
Oct 18, 2021
1 parent
b087302
commit 81ce911
Showing
7 changed files
with
757 additions
and
68 deletions.
There are no files selected for viewing
156 changes: 156 additions & 0 deletions
156
.../java/software/amazon/lambda/powertools/cloudformation/AbstractCustomResourceHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package software.amazon.lambda.powertools.cloudformation; | ||
|
||
import com.amazonaws.services.lambda.runtime.Context; | ||
import com.amazonaws.services.lambda.runtime.RequestHandler; | ||
import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import software.amazon.awssdk.http.SdkHttpClient; | ||
import software.amazon.lambda.powertools.cloudformation.CloudFormationResponse.ResponseStatus; | ||
|
||
import java.io.IOException; | ||
import java.util.Objects; | ||
|
||
/** | ||
* Handler base class providing core functionality for sending responses to custom CloudFormation resources after | ||
* receiving some event. Depending on the type of event, this class either invokes the crete, update, or delete method | ||
* and sends the returned Response object to the custom resource. | ||
*/ | ||
public abstract class AbstractCustomResourceHandler | ||
implements RequestHandler<CloudFormationCustomResourceEvent, Response> { | ||
|
||
private static final Logger LOG = LoggerFactory.getLogger(AbstractCustomResourceHandler.class); | ||
|
||
private final SdkHttpClient client; | ||
|
||
/** | ||
* Creates a new Handler that uses the provided HTTP client for communicating with custom CloudFormation resources. | ||
* | ||
* @param client cannot be null | ||
*/ | ||
public AbstractCustomResourceHandler(SdkHttpClient client) { | ||
this.client = Objects.requireNonNull(client, "SdkHttpClient cannot be null."); | ||
} | ||
|
||
/** | ||
* Generates the appropriate response object based on the event type and sends it as a response to the custom | ||
* cloud formation resource using the URL provided within the event. | ||
* | ||
* @param event custom resources create/update/delete event | ||
* @param context lambda execution context | ||
* @return potentially null response object sent to the custom resource | ||
*/ | ||
@Override | ||
public final Response handleRequest(CloudFormationCustomResourceEvent event, Context context) { | ||
String responseUrl = Objects.requireNonNull(event.getResponseUrl(), | ||
"Event must have a non-null responseUrl to be able to send the response."); | ||
|
||
CloudFormationResponse client = buildResponseClient(); | ||
|
||
Response response = null; | ||
try { | ||
response = getResponse(event, context); | ||
LOG.debug("Preparing to send response {} to {}.", response, responseUrl); | ||
client.send(event, context, ResponseStatus.SUCCESS, response); | ||
} catch (IOException ioe) { | ||
LOG.error("Unable to send {} success to {}.", responseUrl, ioe); | ||
onSendFailure(event, context, response, ioe); | ||
} catch (ResponseException rse) { | ||
LOG.error("Unable to create/serialize Response. Sending empty failure to {}", responseUrl, rse); | ||
// send a failure with a null response on account of response serialization issues | ||
try { | ||
client.send(event, context, ResponseStatus.FAILED); | ||
} catch (Exception e) { | ||
// unable to serialize response AND send an empty response | ||
LOG.error("Unable to send failure to {}.", responseUrl, e); | ||
onSendFailure(event, context, null, e); | ||
} | ||
} | ||
return response; | ||
} | ||
|
||
private Response getResponse(CloudFormationCustomResourceEvent event, Context context) | ||
throws ResponseException { | ||
try { | ||
switch (event.getRequestType()) { | ||
case "Create": | ||
return create(event, context); | ||
case "Update": | ||
return update(event, context); | ||
case "Delete": | ||
return delete(event, context); | ||
default: | ||
LOG.warn("Unexpected request type \"" + event.getRequestType() + "\" for event " + event); | ||
return null; | ||
} | ||
} catch (RuntimeException e) { | ||
throw new ResponseException("Unable to get Response", e); | ||
} | ||
} | ||
|
||
/** | ||
* Builds a client for sending responses to the custom resource. | ||
* | ||
* @return a client for sending the response | ||
*/ | ||
protected CloudFormationResponse buildResponseClient() { | ||
return new CloudFormationResponse(client); | ||
} | ||
|
||
/** | ||
* Invoked when there is an error sending a response to the custom cloud formation resource. This method does not | ||
* get called if there are errors constructing the response itself, which instead is handled by sending an empty | ||
* FAILED response to the custom resource. This method will be invoked, however, if there is an error while sending | ||
* the FAILED response. | ||
* <p> | ||
* The method itself does nothing but subclasses may override to provide additional logging or handling logic. All | ||
* arguments provided are for contextual purposes. | ||
* <p> | ||
* Exceptions should not be thrown by this method. | ||
* | ||
* @param event the event | ||
* @param context execution context | ||
* @param response the response object that was attempted to be sent to the custom resource | ||
* @param exception the exception caught when attempting to call the custom resource URL | ||
*/ | ||
@SuppressWarnings("unused") | ||
protected void onSendFailure(CloudFormationCustomResourceEvent event, | ||
Context context, | ||
Response response, | ||
Exception exception) { | ||
// intentionally empty | ||
} | ||
|
||
/** | ||
* Returns the response object to send to the custom CloudFormation resource upon its creation. If this method | ||
* returns null, then the handler will send a successful but empty response to the CloudFormation resource. If this | ||
* method throws a RuntimeException, the handler will send an empty failed response to the resource. | ||
* | ||
* @param event an event of request type Create | ||
* @param context execution context | ||
* @return the response object or null | ||
*/ | ||
protected abstract Response create(CloudFormationCustomResourceEvent event, Context context); | ||
|
||
/** | ||
* Returns the response object to send to the custom CloudFormation resource upon its modification. If the method | ||
* returns null, then the handler will send a successful but empty response to the CloudFormation resource. If this | ||
* method throws a RuntimeException, the handler will send an empty failed response to the resource. | ||
* | ||
* @param event an event of request type Update | ||
* @param context execution context | ||
* @return the response object or null | ||
*/ | ||
protected abstract Response update(CloudFormationCustomResourceEvent event, Context context); | ||
|
||
/** | ||
* Returns the response object to send to the custom CloudFormation resource upon its deletion. If this method | ||
* returns null, then the handler will send a successful but empty response to the CloudFormation resource. If this | ||
* method throws a RuntimeException, the handler will send an empty failed response to the resource. | ||
* | ||
* @param event an event of request type Delete | ||
* @param context execution context | ||
* @return the response object or null | ||
*/ | ||
protected abstract Response delete(CloudFormationCustomResourceEvent event, Context context); | ||
} |
Oops, something went wrong.