All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.networknt.aws.lambda.LightLambdaExchange Maven / Gradle / Ivy

Go to download

A middleware Lambda function that handles all the cross-cutting concerns for the downstream Lambda function.

The newest version!
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 + '}'; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy