/*
* 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.
*
*
* - {@link CallChain#initiate(String, ProxyClient, Object, StdCallbackContext)}
*
- {@link RequestMaker#translateToServiceRequest(Function)}
*
- {@link Caller#makeServiceCall(BiFunction)}
*
- {@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 super RequestT, Exception, ClientT, ModelT, CallbackT, Boolean> 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 super RequestT, Exception, ClientT, ModelT, CallbackT, Boolean> 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 super RequestT,
Exception, ClientT, ModelT, CallbackT, ProgressEvent> 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 super RequestT, Exception,
ClientT, ModelT, CallbackT, ProgressEvent> 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));
}
}
}