com.networknt.aws.lambda.LightLambdaExchange Maven / Gradle / Ivy
Show all versions of lambda-native Show documentation
package com.networknt.aws.lambda;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.networknt.aws.lambda.exception.LambdaExchangeStateException;
import com.networknt.aws.lambda.handler.MiddlewareHandler;
import com.networknt.aws.lambda.handler.chain.PooledChainLinkExecutor;
import com.networknt.aws.lambda.handler.chain.Chain;
import com.networknt.aws.lambda.handler.middleware.ExceptionUtil;
import com.networknt.aws.lambda.listener.LambdaExchangeFailureListener;
import com.networknt.aws.lambda.listener.LambdaRequestCompleteListener;
import com.networknt.aws.lambda.listener.LambdaResponseCompleteListener;
import com.networknt.status.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
/**
* Shared object among middleware threads containing information on the request/response event.
*/
public final class LightLambdaExchange {
private static final Logger LOG = LoggerFactory.getLogger(LightLambdaExchange.class);
private APIGatewayProxyRequestEvent request;
private APIGatewayProxyResponseEvent response;
private final Context context;
private final Map, Object> attachments = Collections.synchronizedMap(new HashMap<>());
private final PooledChainLinkExecutor executor;
private final List responseCompleteListeners = Collections.synchronizedList(new ArrayList<>());
private final List requestCompleteListeners = Collections.synchronizedList(new ArrayList<>());
private final List exchangeFailedListeners = Collections.synchronizedList(new ArrayList<>());
// Initial state
private static final int INITIAL_STATE = 0;
// Request data has been initialized in the exchange.
private static final int FLAG_REQUEST_SET = 1 << 1;
// Request portion of the exchange is complete.
private static final int FLAG_REQUEST_DONE = 1 << 2;
// Failure occurred during request execution.
private static final int FLAG_REQUEST_HAS_FAILURE = 1 << 3;
// Response data has been initialized in the exchange.
private static final int FLAG_RESPONSE_SET = 1 << 4;
// Response portion of the exchange is complete.
private static final int FLAG_RESPONSE_DONE = 1 << 5;
// Failure occurred during response execution.
private static final int FLAG_RESPONSE_HAS_FAILURE = 1 << 6;
// The chain has been fully executed
private static final int FLAG_CHAIN_EXECUTED = 1 << 7;
// the exchange is complete
private static final int FLAG_EXCHANGE_COMPLETE = 1 << 8;
private int state = INITIAL_STATE;
private int statusCode = 200;
private final Chain chain;
public LightLambdaExchange(Context context, Chain chain) {
this.context = context;
this.chain = chain;
this.executor = new PooledChainLinkExecutor();
}
public void executeChain() {
if (stateHasAnyFlags(FLAG_CHAIN_EXECUTED | FLAG_EXCHANGE_COMPLETE))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_CHAIN_EXECUTED | FLAG_EXCHANGE_COMPLETE);
if (stateHasAnyFlagsClear(FLAG_REQUEST_SET))
throw LambdaExchangeStateException
.missingStateException(this.state, FLAG_REQUEST_SET);
this.executor.executeChain(this, this.chain);
this.state |= FLAG_CHAIN_EXECUTED;
}
/**
* Sets the response object of the exchange.
*
* @param response -
*/
public void setInitialResponse(final APIGatewayProxyResponseEvent response) {
if (stateHasAnyFlags(FLAG_RESPONSE_SET))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_RESPONSE_SET);
this.response = response;
this.statusCode = response.getStatusCode();
this.state |= FLAG_RESPONSE_SET;
}
/**
* Sets the request object of the exchange.
*
* @param request -
*/
public void setInitialRequest(APIGatewayProxyRequestEvent request) {
if (this.state != INITIAL_STATE)
throw LambdaExchangeStateException
.invalidStateException(this.state, INITIAL_STATE);
this.request = request;
this.state |= FLAG_REQUEST_SET;
}
/**
* Returns the response object or an exception object if there was a failure.
*
* @return - return formatted response event.
*/
public APIGatewayProxyResponseEvent getResponse() {
if (stateHasAnyFlags(FLAG_CHAIN_EXECUTED | FLAG_EXCHANGE_COMPLETE))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_CHAIN_EXECUTED | FLAG_EXCHANGE_COMPLETE);
if (stateHasAnyFlagsClear(FLAG_RESPONSE_SET))
throw LambdaExchangeStateException
.missingStateException(this.state, FLAG_RESPONSE_SET);
if (stateHasAnyFlags(FLAG_RESPONSE_DONE))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_RESPONSE_DONE);
return response;
}
public Context getContext() {
return context;
}
public APIGatewayProxyRequestEvent getRequest() {
if (stateHasAnyFlags(FLAG_CHAIN_EXECUTED | FLAG_EXCHANGE_COMPLETE))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_CHAIN_EXECUTED | FLAG_EXCHANGE_COMPLETE);
if (stateHasAnyFlagsClear(FLAG_REQUEST_SET))
throw LambdaExchangeStateException
.missingStateException(this.state, FLAG_REQUEST_SET);
if (stateHasAnyFlags(FLAG_REQUEST_DONE))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_REQUEST_DONE);
return request;
}
public APIGatewayProxyRequestEvent getReadOnlyRequest() {
return request.clone();
}
public int getStatusCode() {
return statusCode;
}
/**
* Terminates the request portion of the exchange and invokes the exchangeRequestCompleteListeners.
* You cannot finalize a request that has already been finalized.
*
* @param fromListener - if the call is from a listener, do not invoke listeners again.
* @return - returns the complete and final request event.
*/
public APIGatewayProxyRequestEvent getFinalizedRequest(boolean fromListener) {
if(!fromListener) {
// the call any listener should not invoke listener again to prevent deal loop.
for (int i = requestCompleteListeners.size() - 1; i >= 0; --i) {
LambdaRequestCompleteListener listener = requestCompleteListeners.get(i);
listener.requestCompleteEvent(this);
requestCompleteListeners.remove(i);
}
}
if (stateHasAnyFlags(FLAG_REQUEST_DONE))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_REQUEST_DONE);
if (stateHasAnyFlagsClear(FLAG_REQUEST_SET))
throw LambdaExchangeStateException
.missingStateException(this.state, FLAG_REQUEST_SET);
if(!fromListener) {
this.state |= FLAG_REQUEST_DONE;
}
return request;
}
/**
* Terminates the response portion of the exchange and invokes the exchangeResponseCompleteListeners.
* You cannot finalize a response that has already been finalized.
*
* @param fromListener - if the call is from a listener, do not invoke listeners again.
* @return - returns the complete and final response event.
*/
public APIGatewayProxyResponseEvent getFinalizedResponse(boolean fromListener) {
if(!fromListener) {
// the call any listener should not invoke listener again to prevent deal loop.
for (int i = responseCompleteListeners.size() - 1; i >= 0; --i) {
LambdaResponseCompleteListener listener = responseCompleteListeners.get(i);
listener.responseCompleteEvent(this);
responseCompleteListeners.remove(i);
}
}
/*
* Check for failures first because a failed request could mean
* that we never set the response in the first place. If response is not null, don't bother.
*/
if (stateHasAnyFlags(FLAG_REQUEST_HAS_FAILURE | FLAG_RESPONSE_HAS_FAILURE) && response == null) {
LOG.error("Exchange has an error, returning middleware status.");
this.state |= FLAG_RESPONSE_DONE;
return ExceptionUtil.convert(this.executor.getChainResults());
}
if (stateHasAnyFlagsClear(FLAG_CHAIN_EXECUTED))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_CHAIN_EXECUTED);
if (stateHasAnyFlags(FLAG_RESPONSE_DONE | FLAG_EXCHANGE_COMPLETE))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_RESPONSE_DONE | FLAG_EXCHANGE_COMPLETE);
if (stateHasAnyFlagsClear(FLAG_RESPONSE_SET))
throw LambdaExchangeStateException
.missingStateException(this.state, FLAG_RESPONSE_SET);
if(!fromListener) {
this.state |= FLAG_RESPONSE_DONE;
this.state |= FLAG_EXCHANGE_COMPLETE;
}
return response;
}
/**
* Update the exchange. This happens automatically after each middleware execution,
* but it can be invoked manually by user logic.
*
* Once an exchange is marked as failed, no longer handle updates.
*
* @param status - status to update the exchange with.
*/
public void updateExchangeStatus(final Status status) {
/* No need to update anything if the exchange is marked as complete/failed. */
if (stateHasAnyFlags(FLAG_REQUEST_HAS_FAILURE | FLAG_RESPONSE_HAS_FAILURE | FLAG_EXCHANGE_COMPLETE)) {
return;
}
if (status.getSeverity().startsWith("ERR")) {
int oldState = this.state;
/* if we are making the update from a handler or a request complete listener. */
if ((this.isRequestInProgress() && !this.isResponseInProgress())
|| (this.isRequestComplete() && !this.isResponseInProgress())) {
LOG.error("Exchange has an error in the request phase.");
this.statusCode = status.getStatusCode();
this.state |= FLAG_REQUEST_HAS_FAILURE;
} else if (this.isResponseInProgress()) {
LOG.error("Exchange has an error in the response phase.");
this.statusCode = status.getStatusCode();
this.state |= FLAG_RESPONSE_HAS_FAILURE;
}
/* if a failure occurred, run failure listeners */
if (oldState != this.state) {
for (var failureListener : this.exchangeFailedListeners)
failureListener.exchangeFailedEvent(this);
}
}
}
/**
* Check to see if the exchange has any error state at all.
*
* @return - returns true if the exchange has a failure state.
*/
public boolean hasFailedState() {
return stateHasAnyFlags(FLAG_REQUEST_HAS_FAILURE | FLAG_RESPONSE_HAS_FAILURE);
}
/**
* Checks to see if the exchange is in the 'request in progress' state.
* The exchange is in the request state when the request chain is ready and has not finished executing.
*
* @return - returns true if the exchange is handing the request.
*/
public boolean isRequestInProgress() {
return this.stateHasAllFlags(FLAG_REQUEST_SET)
&& this.stateHasAllFlagsClear(FLAG_REQUEST_DONE);
}
public boolean isRequestComplete() {
return this.stateHasAllFlags(FLAG_REQUEST_DONE);
}
/**
* Checks to see if the exchange is in the response in progress state.
* The exchange is in the response state when the request chain is complete, and the response chain is ready and has not finished executing.
*
* @return - return true if the exchange is handling the response.
*/
public boolean isResponseInProgress() {
return this.stateHasAllFlags(FLAG_REQUEST_DONE | FLAG_RESPONSE_SET)
&& this.stateHasAllFlagsClear(FLAG_RESPONSE_DONE);
}
/**
* Checks to see if the exchange has the response portion complete.
*
* @return - true if the response is complete.
*/
public boolean isResponseComplete() {
return this.stateHasAllFlags(FLAG_RESPONSE_DONE);
}
/**
* Checks to see if the exchange is complete.
*
* @return - true if the exchange is complete.
*/
public boolean isExchangeComplete() {
return this.stateHasAllFlags(FLAG_EXCHANGE_COMPLETE);
}
/**
* @return int state
*/
public int getState() {
return state;
}
/**
* Checks to see if any of the provided flags is true.
*
* @param flags - flags to check.
* @return - returns true if any flags are true.
*/
private boolean stateHasAnyFlags(final int flags) {
return (this.state & flags) != 0;
}
/**
* Checks to see if any of the provided flags are not set.
*
* @param flags - flags to check.
* @return - returns true if any of the provided flags are false.
*/
private boolean stateHasAnyFlagsClear(final int flags) {
return (this.state & flags) != flags;
}
/**
* Checks to see if all provided flags are set.
*
* @param flags - flags to check.
* @return - returns true if all provided flags are true.
*/
private boolean stateHasAllFlags(final int flags) {
return (this.state & flags) == flags;
}
/**
* Checks to see if all provided flags are not set.
*
* @param flags - flags to check.
* @return - returns true if all flags are false.
*/
private boolean stateHasAllFlagsClear(final int flags) {
return (this.state & flags) == 0;
}
public LightLambdaExchange addExchangeFailedListener(final LambdaExchangeFailureListener listener) {
if (this.stateHasAnyFlags(FLAG_RESPONSE_DONE))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_RESPONSE_DONE);
this.exchangeFailedListeners.add(listener);
return this;
}
public LightLambdaExchange addResponseCompleteListener(final LambdaResponseCompleteListener listener) {
if (this.stateHasAnyFlags(FLAG_RESPONSE_DONE))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_RESPONSE_DONE);
this.responseCompleteListeners.add(listener);
return this;
}
public LightLambdaExchange addRequestCompleteListener(final LambdaRequestCompleteListener listener) {
if (this.stateHasAnyFlags(FLAG_REQUEST_DONE | FLAG_RESPONSE_DONE))
throw LambdaExchangeStateException
.invalidStateException(this.state, FLAG_REQUEST_DONE | FLAG_RESPONSE_DONE);
this.requestCompleteListeners.add(listener);
return this;
}
/**
* Adds an attachment to the exchange.
*
* @param key - Attachable key.
* @param o - object value.
* @param - Middleware key type.
*/
public void addAttachment(final Attachable key, final Object o) {
this.attachments.put(key, o);
}
/**
* Get attachment object for given key.
*
* @param attachable - middleware key
* @return - returns the object for the provided key. Can return null if it does not exist.
*/
public Object getAttachment(final Attachable> attachable) {
return this.attachments.get(attachable);
}
public Map, Object> getAttachments() {
return attachments;
}
/**
* Attachment key class to attach data to the exchange.
*
* @param -
*/
public static class Attachable {
private final Class key;
private Attachable(Class key) {
this.key = key;
}
public Class getKey() {
return key;
}
/**
* Creates a new attachable key.
*
* @param middleware - class to create a key for.
* @param - given class has to implement the MiddlewareHandler interface.
* @return - returns new attachable instance.
*/
public static Attachable createAttachable(final Class middleware) {
return new Attachable<>(middleware);
}
}
@Override
public String toString() {
return "LightLambdaExchange{" +
"request=" + request +
", response=" + response +
", context=" + context +
", attachments=" + attachments +
", executor=" + executor +
", state=" + state +
", statusCode=" + statusCode +
'}';
}
}