io.mats3.api.intercept.MatsInitiateInterceptor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mats-intercept-api Show documentation
Show all versions of mats-intercept-api Show documentation
Extension to the Mats^3 API which adds interception capabilities.
package io.mats3.api.intercept;
import java.time.Instant;
import io.mats3.MatsEndpoint.MatsRefuseMessageException;
import io.mats3.MatsFactory;
import io.mats3.MatsFactory.FactoryConfig;
import io.mats3.MatsFactory.MatsPlugin;
import io.mats3.MatsInitiator;
import io.mats3.MatsInitiator.InitiateLambda;
import io.mats3.MatsInitiator.MatsInitiate;
/**
* Extension of MatsPlugin: Implement this interface to intercept Initiations, then register with
* {@link FactoryConfig#installPlugin(MatsPlugin)}.
*
* Meant for intercepting initiations with ability to modify the initiation, and to implement extra logging and metrics
* gathering.
*
* When more than one interceptor is installed, the ordering becomes interesting. If the following class is added twice,
* first with 1 as the argument, and then secondly with 2 as the argument ..:
*
*
* private static class MyMatsInitiateInterceptor implements MatsInitiateInterceptor {
*
* private final int _number;
*
* MyMatsInitiateInterceptor(int number) {
* _number = number;
* }
*
* @Override
* public void initiateStarted(InitiateStartedContext initiateStartedContext) {
* log.info("Started #" + _number);
* }
*
* @Override
* public void initiateIntercept(InitiateInterceptContext initiateInterceptContext,
* InitiateLambda initiateLambda, MatsInitiate matsInitiate) {
* log.info("Intercept pre #" + _number);
*
* // Wrap the MatsInitiate to catch "send(dto)", before invoking the lambda
* MatsInitiateWrapper wrappedMatsInitiate = new MatsInitiateWrapper(matsInitiate) {
* @Override
* public MessageReference send(Object messageDto) {
* log.info(".. send pre #" + _number);
* MessageReference send = super.send(messageDto);
* log.info(".. send post #" + _number);
* return send;
* }
* };
*
* // Invoke the lambda, with the wrapped MatsInitiate
* initiateLambda.initiate(wrappedMatsInitiate);
*
* log.info("Intercept post #" + _number);
* }
*
* @Override
* public void initiateCompleted(InitiateCompletedContext initiateCompletedContext) {
* log.info("Completed #" + _number);
* }
* }
*
*
* .. then the following sequence of log lines and operations ensues:
*
* - First the {@link MatsInitiateInterceptor#initiateStarted(InitiateStartedContext) initiateStarted(..)} are invoked
* in sequence:
* Started #1
* Started #2
* - Then the initiation goes into the transaction, and a "reverse stack" of lambdas are generated from the two
* interceptors'
* {@link MatsInitiateInterceptUserLambda#initiateInterceptUserLambda(InitiateInterceptUserLambdaContext, InitiateLambda, MatsInitiate)
* initiateIntercept(..)} methods, and then the resulting lambda is invoked:
* Intercept pre #1
* Intercept pre #2
* - .. the actual user provided lambda (the one provided by the endpoint) is invoked, performing a
*
send(..)
, which goes through the stack of MatsInitiateWrappers which the
* initiateIntercept(..)
invocations generated (thus hitting the last wrapping by #2 first):
* .. send pre #2
* .. send pre #1
* - .. the actual Mats API send(..) method is invoked, before we traverse out of the MatsInitiateWrappers:
* .. send post #1
* .. send post #2
* - .. and then the user lambda exits, traversing back up the
initiateIntercept(..)
stack
* Intercept post #2
* Intercept post #1
* - Finally, the {link {@link MatsInitiateInterceptor#initiateCompleted(InitiateCompletedContext)
* initiateComplete(..)} is invoked, in explicit reversed order:
* Completed #2
* Completed #1
*
* If you envision that these two interceptors are created for logging, where the first interceptor puts values on the
* MDC, while the second one logs, then this makes a bit of sense: First the MDC is set, then an entry log line is
* emitted, then an exit log line is emitted, then the MDC is cleared. However, interceptions of the inner call
* from the actual user provided lambda to MatsInitiate.send(..) ends up in what is seemingly the "wrong"
* sequential order, due to how the interception is logically performed: The #2 is the last
* initiateIntercept(..) that is invoked, which thus is the code that lastly wraps the MatsInitiate instance, and
* hence when the user code invokes matsInitiate.send(..), that lastly added wrapping is the first to be
* executed. What this means, is that you should probably not make severe ordering dependencies between interceptors
* which depends on wrapping the MatsInitiate instance, as your head will most probably hurt from the ensuing twisted
* reasoning!
*
* Note: This is only invoked for proper, actual initiations "from outside of Mats", i.e. using a
* {@link MatsInitiator} gotten with {@link MatsFactory#getDefaultInitiator()} or
* {@link MatsFactory#getOrCreateInitiator(String)} - and notice the special semantics of getDefaultInitiator(),
* whereby if such an seemingly "from the outside" initiation is invoked when code-flow-wise within a Stage, you will
* actually be "elevated" to be initiated "within Mats" using the Stage initiator - and hence this interceptor will not
* be invoked (it is no longer "from outside of Mats").
*
* The concept of "outside" vs. "inside" perhaps seems subtle, but there is a distinct difference: No processing will
* ever happen in a Mats fabric if no initiations happens "from the outside": It is always a "from the outside"
* initiation that will set Mats flows in action. Such a process flow might then set several new Mats flows in action
* (i.e. initiations "from the inside"), but those are dependent on the initial Mats flow that was set in motion "from
* the outside", and would never have been initiated was it not for such initiation.
*
* To catch initiations "from the inside", you will employ a {@link MatsStageInterceptor}.
*
* @author Endre Stølsvik - 2020-01-08 - http://endre.stolsvik.com
*/
public interface MatsInitiateInterceptor extends MatsPlugin {
/**
* Invoked right before user lambda is invoked.
*/
default void initiateStarted(InitiateStartedContext context) {
/* no-op */
}
/**
* Enables the intercepting of the invocation of the user lambda in an Initiation, with ability to wrap the
* {@link MatsInitiate} (and thus modify any request, send or publishes) - or even take over the entire initiation.
* Wrt. changing messages, you should also consider
* {@link MatsInitiateInterceptOutgoingMessages#initiateInterceptOutgoingMessages(InitiateInterceptOutgoingMessagesContext)}.
*
* Pulled out in separate interface, so that we don't need to invoke it if the interceptor doesn't need it.
*/
interface MatsInitiateInterceptUserLambda {
default void initiateInterceptUserLambda(InitiateInterceptUserLambdaContext context,
InitiateLambda initiateLambda, MatsInitiate matsInitiate) {
// Default: Call directly through
initiateLambda.initiate(matsInitiate);
}
}
/**
* While still within the initiation context, this interception enables modifying outgoing messages from the user
* lambda, setting trace properties, adding "sideloads", deleting a message, or initiating additional messages.
*
* Pulled out in separate interface, so that we don't need to invoke it if the interceptor doesn't need it.
*/
interface MatsInitiateInterceptOutgoingMessages extends MatsInitiateInterceptor {
void initiateInterceptOutgoingMessages(InitiateInterceptOutgoingMessagesContext context);
}
default void initiateCompleted(InitiateCompletedContext context) {
/* no-op */
}
interface InitiateInterceptContext {
/**
* @return the {@link MatsInitiator} employed for this initiation.
*/
MatsInitiator getInitiator();
/**
* @return when the initiation was started, as {@link Instant#now()}, i.e. when
* {@link MatsInitiator#initiate(InitiateLambda)} was invoked.
*/
Instant getStartedInstant();
/**
* @return when the initiation was started, as {@link System#nanoTime()}, i.e. when
* {@link MatsInitiator#initiate(InitiateLambda)} was invoked.
*/
long getStartedNanoTime();
}
interface InitiateStartedContext extends InitiateInterceptContext {
void initiate(InitiateLambda lambda);
}
interface InitiateInterceptUserLambdaContext extends InitiateInterceptContext {
void initiate(InitiateLambda lambda);
}
interface InitiateInterceptOutgoingMessagesContext extends InitiateInterceptContext,
CommonInterceptOutgoingMessagesContext {
}
interface InitiateCompletedContext extends InitiateInterceptContext, CommonCompletedContext {
/**
* @return the result of the initiation - returns NONE if there was no outgoing messages, REQUEST, SEND or
* PUBLISH if it was a single message, and MULTIPLE if it was more than one message (Note: also if those
* multiple were all of the same kind).
*/
InitiateProcessResult getInitiateProcessResult();
enum InitiateProcessResult {
NONE,
REQUEST,
SEND,
PUBLISH,
MULTIPLE,
/**
* Any exception thrown in the user lambda, causing rollback of the processing. This may both be code
* failures (e.g. {@link NullPointerException}, explicit validation failures (which probably should result
* in {@link MatsRefuseMessageException}), and database access or other types of external communication
* failures.
*/
USER_EXCEPTION,
/**
* If the messaging or processing system failed, this will be either
* {@link io.mats3.MatsInitiator.MatsBackendException MatsBackendException} (messaging handling or db
* commit), or {@link io.mats3.MatsInitiator.MatsMessageSendException MatsMessageSendException} (which is
* the "VERY BAD!" scenario where db is committed, whereupon the messaging commit failed - which quite
* possibly is a "notify the humans!"-situation, unless the user code is crafted to handle such a situation
* by being idempotent).
*/
SYSTEM_EXCEPTION
}
}
}