/* * Copyright 2010-2019 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 software.amazon.cloudformation.proxy; import java.util.function.BiFunction; import java.util.function.Function; /** * This can be used by Read, Create, Update and Delete handlers when invoking * AWS services. Each CallChain when invoking a service call explicitly provide * a call graph name. This name is used as a key prefix to memoize request, * responses and results in each call sequences. This {@link CallChain} provides * a fluent API * design that ensure that right sequence of steps is followed * for making a service call. * * * Any service call should use {@link AmazonWebServicesClientProxy}. Here is the * minimum sequence for the calls. * *
    *
  1. {@link CallChain#initiate(String, ProxyClient, Object, StdCallbackContext)} *
  2. {@link RequestMaker#translateToServiceRequest(Function)} *
  3. {@link Caller#makeServiceCall(BiFunction)} *
  4. {@link Completed#done(Function)} *
* * @see AmazonWebServicesClientProxy */ public interface CallChain { /** * Provides an API initiator interface that works for all API calls that need * conversion, retry-backoff strategy, common exception handling and more * against desired state of the resource and callback context. This needs to be * instantiated once with the client, model and callback context and used. It * takes a reference to the desired state model, so any changes made on the * model object will be reflected here. The model and callback can accessed from * the instantiator instance * * @param the AWS Service client. * @param the model object being worked on * @param the callback context */ interface Initiator extends RequestMaker { /** * Each service call must be first initiated. Every call is provided a separate * name called call graph. This is essential from both a tracing perspective as * well as {@link StdCallbackContext} automated replay capabilities. * * @param callGraph, the name of the service operation this call graph is about. * @return Provides the next logical set in the fluent API. */ RequestMaker initiate(String callGraph); /** * @return the model associated with the API initiator. Can not be null */ ModelT getResourceModel(); /** * @return the callback context associated with API initiator, Can not be null */ CallbackT getCallbackContext(); /** * @return logger associated to log messages */ Logger getLogger(); /** * Can rebind a new model to the call chain while retaining the client and * callback context * * @param model, the new model for the callchain initiation * @param , this actual model type * @return new {@link Initiator} that now has the new model associated with it */ Initiator rebindModel(NewModelT model); /** * Can rebind a new callback context for a call chain while retaining the model * and client * * @param callback the new callback context * @param new callback context type * @return new {@link Initiator} that now has the new callback associated with * it */ Initiator rebindCallback(NewCallbackT callback); } /** * factory method can created an {@link Initiator} * * @param client AWS Service Client. Recommend using Sync client as the * framework handles interleaving as needed. * @param model the resource desired state model, usually * @param context callback context that tracks all outbound API calls * @param Actual client e.g. KinesisClient. * @param The type (POJO) of Resource model. * @param , callback context the extends {@link StdCallbackContext} * * @return an instance of the {@link Initiator} */ Initiator newInitiator(ProxyClient client, ModelT model, CallbackT context); /** * Each service call must be first initiated. Every call is provided a separate * name called call graph. This is eseential from both a tracing perspective as * well as {@link StdCallbackContext} automated replay capabilities. * * @param callGraph, the name of the service operation this call graph is about. * @param client, actual client needed to make the call wrapped inside * {@link ProxyClient} to support injection of scoped credentials * @param model, the actual resource model that defines the shape for setting up * this resource type. * @param cxt, Callback context used for supporting replay and dedupe * capabilities. * @param Actual client e.g. KinesisClient. * @param The type (POJO) of Resource model. * @param , callback context the extends {@link StdCallbackContext} * @return Provides the next logical set in the fluent API. */ RequestMaker initiate(String callGraph, ProxyClient client, ModelT model, CallbackT cxt); /** * This performs the translate step between the ModelT properties and what is * needed for making the service call. * * @param Actual client e.g. KinesisClient. * @param The type (POJO) of Resource model. * @param , callback context the extends {@link StdCallbackContext} */ interface RequestMaker { /** * use {@link #translateToServiceRequest(Function)} * * Take a reference to the tranlater that take the resource model POJO as input * and provide a request object as needed to make the Service call. * * @param maker, provide a functional transform from model to request object. * @param , the web service request created * @return returns the next step, to actually call the service. */ @Deprecated default Caller request(Function maker) { return translateToServiceRequest(maker); } /** * Take a reference to the tranlater that take the resource model POJO as input * and provide a request object as needed to make the Service call. * * @param maker, provide a functional transform from model to request object. * @param , the web service request created * @return returns the next step, to actually call the service. */ Caller translateToServiceRequest(Function maker); } /** * This Encapsulates the actual Call to the service that is being made via * caller. This allow for the proxy to intercept and wrap the caller in cases of * replay and provide the memoized response back * * @param , the AWS serivce request we are making * @param , the web service client to make the call * @param , the current model we are using * @param , the callback context for handling all AWS service request * responses */ interface Caller { @Deprecated default Stabilizer call(BiFunction, ResponseT> caller) { return makeServiceCall(caller); } Stabilizer makeServiceCall(BiFunction, ResponseT> caller); @Deprecated default Caller retry(Delay delay) { return backoffDelay(delay); } Caller backoffDelay(Delay delay); } /** * All service calls made will use the same call back interface for handling * both exceptions as well as actual response received from the call. The * ResponseT is either the actual response result in the case of success or the * Exception thrown in the case of faults. * * @param , the web service request that was made * @param the response or the fault (Exception) that needs to * handled * @param , the client that was used to invoke * @param , the resource model object that we are currently working * against * @param , the callback context that contains results * @param , the return from the callback. * * @see Exceptional */ @FunctionalInterface interface Callback { ReturnT invoke(RequestT request, ResponseT response, ProxyClient client, ModelT model, CallbackT context); } /** * This provide the handler with the option to provide an explicit exception * handler that would have service exceptions that was received. * * @param , the web service request that was made * @param the response or the fault (Exception) that needs to * handled * @param , the client that was used to invoke * @param , the resource model object that we are currently working * against * @param , the callback context that contains results */ interface Exceptional extends Completed { /** * @param handler, a predicate lambda expression that take the web request, * response, client, model and context and says continue or fail * operation * @return true of you want to attempt another retry of the operation. false to * indicate propagate error/fault. */ @Deprecated default Completed exceptFilter(Callback handler) { return retryErrorFilter(handler); } /** * @param handler, a predicate lambda expression that takes the web request, * exception, client, model and context to determine to retry the * exception thrown by the service or fail operation. This is the * simpler model then {@link #handleError(ExceptionPropagate)} for * most common retry scenarios If we need more control over the * outcome, then use {@link #handleError(ExceptionPropagate)} * @return true of you want to attempt another retry of the operation. false to * indicate propagate error/fault. */ Completed retryErrorFilter(Callback handler); /** * @param handler, a lambda expression that takes the web request, response, * client, model and context returns a successful or failed * {@link ProgressEvent} back or can rethrow service exception to * propagate errors. If handler needs to retry the exception, the it * will throw a * {@link software.amazon.awssdk.core.exception.RetryableException} * @return a ProgressEvent for the model */ @Deprecated default Completed exceptHandler(ExceptionPropagate> handler) { return handleError(handler); } /** * @param handler, a lambda expression that take the web request, response, * client, model and context and says continue or fail operation by * providing the appropriate {@link ProgressEvent} back. * @return If status is {@link OperationStatus#IN_PROGRESS} we will attempt * another retry. Otherwise failure is propagated. */ Completed handleError(ExceptionPropagate> handler); } /** * When implementing this interface, developers can either propagate the * exception as is. If the exception can be retried, throw * {@link software.amazon.awssdk.core.exception.RetryableException} * * @param the API request object * @param the exception that is thrown by the API * @param the service client * @param current desired state resource model * @param current callback context * @param result object */ @FunctionalInterface interface ExceptionPropagate { ReturnT invoke(RequestT request, E exception, ProxyClient client, ModelT model, CallbackT context) throws Exception; } /** * This provides an optional stabilization function to be incorporate before we * are done with the actual web service request. This is useful to ensure that * the web request created a resource that takes time to be live or available * before additional properties can be set on it. E.g. when one creates a * Kinesis stream is takes some time before the stream is active to do other * operations on it like set the retention period. * * {@code * private Boolean isStreamActive( * CreateStreamRequest req, * CreateStreamResponse res, * ProxyClient client, * ResourceModel model, * CallbackContext cxt) { * * DescribeStreamRequest r = * DescribeStreamRequest.builder().streamName(req.streamName()).build(); * DescribeStreamResponse dr = client.injectCredentialsAndInvokeV2( * r, client.client()::describeStream); * StreamDescription description = dr.streamDescription(); * model.setArn(description.streamARN()); return * (description.streamStatus() == StreamStatus.ACTIVE); } } * * @param , the web service request that was made * @param the response or the fault (Exception) that needs to * handled * @param , the client that was used to invoke * @param , the resource model object that we are currently working * against * @param , the callback context that contains results */ interface Stabilizer extends Exceptional { /** * @param callback, the stabilize predicate that is called several times to * determine success. * @return true if the condition of stabilization has been meet else false. */ Exceptional stabilize(Callback callback); } /** * One the call sequence has completed successfully, this is called to provide * the progress event. * * @param , the web service request that was made * @param the response or the fault (Exception) that needs to * handled * @param , the client that was used to invoke * @param , the resource model object that we are currently working * against * @param , the callback context that contains results */ interface Completed { /** * @param func, this works with only the response of the web service call to * provide {@link ProgressEvent} function * @return {@link ProgressEvent} for successful web call. */ ProgressEvent done(Function> func); /** * @param callback, similar to above function can make additional calls etc. to * return the {@link ProgressEvent} * @return {@link ProgressEvent} for a successful web call */ ProgressEvent done(Callback> callback); /** * @return {@link ProgressEvent} Helper function that provides a * {@link OperationStatus#SUCCESS} status when the callchain is done */ default ProgressEvent success() { return done((request, response, client, model, context) -> ProgressEvent.success(model, context)); } /** * @return {@link ProgressEvent} Helper function that provides a * {@link OperationStatus#IN_PROGRESS} status when the callchain is done */ default ProgressEvent progress() { return progress(0); } /** * @param callbackDelay the number of seconds to delay before calling back into * this externally * @return {@link ProgressEvent} Helper function that provides a * {@link OperationStatus#IN_PROGRESS} status when the callchain is done * with callback delay */ default ProgressEvent progress(int callbackDelay) { return done((request, response, client, model, context) -> ProgressEvent.defaultInProgressHandler(context, callbackDelay, model)); } } }