io.mats3.MatsFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mats-api Show documentation
Show all versions of mats-api Show documentation
API for Mats^3: Message-based Asynchronous Transactional Staged Stateless Services.
package io.mats3;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import io.mats3.MatsConfig.StartStoppable;
import io.mats3.MatsEndpoint.EndpointConfig;
import io.mats3.MatsEndpoint.ProcessContext;
import io.mats3.MatsEndpoint.ProcessLambda;
import io.mats3.MatsEndpoint.ProcessReturnLambda;
import io.mats3.MatsEndpoint.ProcessSingleLambda;
import io.mats3.MatsEndpoint.ProcessTerminatorLambda;
import io.mats3.MatsInitiator.InitiateLambda;
import io.mats3.MatsInitiator.MatsInitiate;
import io.mats3.MatsStage.StageConfig;
/**
* The start point for all interaction with Mats - you need to get hold of an instance of this interface to be able to
* code and configure Mats endpoints, and to perform {@link #getDefaultInitiator() initiations} like sending a message,
* perform a request and publish a message. Getting hold of a MatsFactory is an implementation specific feature: The
* JmsMatsFactory
is the standard, providing static factory methods, and can be backed by an ActiveMQ- or
* Artemis-specific JMS ConnectionFactory.
*
* An alternative to Java-based programmatic creation of Mats Endpoints is using Mats SpringConfig integration where
* you use annotations like @EnableMats
, @MatsMapping
and @MatsClassMapping
.
* Employing Mats SpringConfig, you'll need to get an instance of MatsFactory into the Spring context. The module is
* called "mats-spring".
*
* It is worth realizing that all of the methods {@link #staged(String, Class, Class) staged(..., config)},
* {@link #single(String, Class, Class, ProcessSingleLambda) single(...)} +
* {@link #single(String, Class, Class, Consumer, Consumer, ProcessSingleLambda) w/configs};
* {@link #terminator(String, Class, Class, ProcessTerminatorLambda) terminator(...)} +
* {@link #terminator(String, Class, Class, Consumer, Consumer, ProcessTerminatorLambda) w/configs} are just convenience
* methods to the main {@link #staged(String, Class, Class) staged(...)}. These specializations could just as well have
* resided in a utility class. They are included in the API since these relatively few methods seem to cover most
* scenarios.
*
* (Exception to this are the {@link #subscriptionTerminator(String, Class, Class, ProcessTerminatorLambda)
* subscriptionTerminator(...)} +
* {@link #subscriptionTerminator(String, Class, Class, Consumer, Consumer, ProcessTerminatorLambda) w/configs}, as they
* have different semantics, read the JavaDoc).
*
* Regarding order of the Reply Message, State and Incoming Message, which can be a bit annoying to remember when
* creating endpoints, and when writing {@link ProcessLambda process lambdas}: They are always ordered like this: R,
* S, I, i.e. Reply, State, Incoming. This is to resemble a method signature having the implicit {@code this}
* (or {@code self}) reference as the first argument: {@code ReturnType methodName(this, arguments)}. Thus, if you
* remember that Mats is created to enable you to write messaging oriented endpoints that look like they are
* methods, then it might stick! The process lambda thus has args (context, state, incomingMsg), unless it lacks state.
* Even if it lacks state, the context is always first, and the incoming message is always last.
*
* - For a MultiStage endpoint, you will have all of reply message type, state type, and incoming message type. The
* params of the {@link #staged(String, Class, Class) staged(..)}-method of MatsFactory is [EndpointId, Reply
* Class, State Class] - and each of the added {@link MatsEndpoint#stage(Class, ProcessLambda) stages} specifies
* their own Incoming class and stage process lambda. The {@link ProcessLambda stage lambda} params will then be:
* [Context, State, Incoming]. If you employ the {@link MatsEndpoint#lastStage(Class, ProcessReturnLambda)
* lastStage(..)}, its {@link ProcessReturnLambda process return lambda} will have the reply type as its return
* type.
* - For a SingleStage endpoint, you will have incoming message, and reply message - there is no state, since that is
* an object that traverses between the stages in a multi-stage endpoint, and this endpoint is just a single stage. The
* params of the {@link #single(String, Class, Class, ProcessSingleLambda) single(..)}-method of MatsFactory is thus
* [EndpointId, Reply Class, Incoming Class, process lambda] - you specify the process lambda directly.
* The {@link ProcessSingleLambda process single lambda} params are: [Context, Incoming] - the reply type is the
* the return type of the lambda.
* - In a Terminator, you have an incoming message, and state (which the initiator set), but no Reply, as a terminator
* doesn't reply. The params of the {@link #terminator(String, Class, Class, ProcessTerminatorLambda)
* terminator(..)}-method of MatsFactory is thus [EndpointId, State Class, Incoming Class, process lambda]
* - also here specifying the process lambda directly. The lambda params of the {@link ProcessTerminatorLambda process
* terminator lambda} are: [Context, State, Incoming]
*
*
* All these type arguments and lambdas and whatnot in the JavaDoc can seem a bit overwhelming at first, but when
* actually coding Mats Endpoints, it clicks into place and hopefully gives a smooth experience.
*
* Note: It should be possible to use instances of MatsFactory
as keys in a HashMap
, i.e.
* their equals and hashCode should remain stable throughout the life of the MatsFactory. Depending on the
* implementation, instance equality may be sufficient. Note that the {@link MatsFactoryWrapper} is implemented to
* forward equals and hashCode to wrappee.
*
* @author Endre Stølsvik - 2015-07-11 - http://endre.stolsvik.com
*/
public interface MatsFactory extends StartStoppable {
/**
* @return the {@link FactoryConfig} on which to configure the factory, e.g. defaults for concurrency.
*/
FactoryConfig getFactoryConfig();
/**
* Simple Consumer<MatsConfig>-implementation that does nothing, for use where you e.g. only need to config
* the stage, not the endpoint, in e.g. the method
* {@link #terminator(String, Class, Class, Consumer, Consumer, ProcessTerminatorLambda)}.
*/
Consumer NO_CONFIG = config -> {
/* no-op */ };
/**
* Sets up a {@link MatsEndpoint} on which you will add stages. The first stage is the one that will receive the
* incoming (typically request) DTO, while any subsequent stage is invoked when the service that the previous stage
* sent a request to, replies.
*
* Unless the state object was sent along with the {@link MatsInitiate#request(Object, Object) request} or
* {@link MatsInitiate#send(Object, Object) send}, the first stage will get a newly constructed empty state
* instance, while the subsequent stages will get the state instance in the form it was left in the previous stage.
*
* @param endpointId
* the identification of this {@link MatsEndpoint}, which are the strings that should be provided to the
* {@link MatsInitiate#to(String)} or {@link MatsInitiate#replyTo(String, Object)} methods for this
* endpoint to get the message. Typical structure is "OrderService.placeOrder"
for public
* endpoints, or "OrderService.private.validateOrder"
for private (app-internal) endpoints.
* @param replyClass
* the class that this endpoint shall return.
* @param stateClass
* the class of the State DTO that will be sent along the stages.
* @return the {@link MatsEndpoint} on which to add stages.
*/
MatsEndpoint staged(String endpointId, Class replyClass, Class stateClass);
/**
* Variation of {@link #staged(String, Class, Class)} that can be configured "on the fly".
*/
MatsEndpoint staged(String endpointId, Class replyClass, Class stateClass,
Consumer super EndpointConfig> endpointConfigLambda);
/**
* Sets up a {@link MatsEndpoint} that just contains one stage, useful for simple "request the full person data for
* this/these personId(s)" scenarios. This sole stage is supplied directly, using a specialization of the processor
* lambda which does not have state (as there is only one stage, there is no other stage to pass state to), but
* which can return the reply by simply returning it on exit from the lambda.
*
* Do note that this is just a convenience for the often-used scenario where for example a request will just be
* looked up in the backing data store, and replied directly, using only one stage, not needing any multi-stage
* processing.
*
* @param endpointId
* the identification of this {@link MatsEndpoint}, which are the strings that should be provided to the
* {@link MatsInitiate#to(String)} or {@link MatsInitiate#replyTo(String, Object)} methods for this
* endpoint to get the message. Typical structure is "OrderService.placeOrder"
for public
* endpoints, or "OrderService.private.validateOrder"
for private (app-internal) endpoints.
* @param replyClass
* the class that this endpoint shall return.
* @param incomingClass
* the class of the incoming (typically request) DTO.
* @param processor
* the {@link MatsStage stage} that will be invoked to process the incoming message.
* @return the {@link MatsEndpoint}, but you should not add any stages to it, as the sole stage is already added.
*/
MatsEndpoint single(String endpointId,
Class replyClass,
Class incomingClass,
ProcessSingleLambda processor);
/**
* Variation of {@link #single(String, Class, Class, ProcessSingleLambda)} that can be configured "on the fly".
*/
MatsEndpoint single(String endpointId,
Class replyClass,
Class incomingClass,
Consumer super EndpointConfig> endpointConfigLambda,
Consumer super StageConfig> stageConfigLambda,
ProcessSingleLambda processor);
/**
* Sets up a {@link MatsEndpoint} that contains a single stage that typically will be the reply-to endpointId for a
* {@link MatsInitiate#request(Object, Object) request initiation}, or that can be used to directly send a
* "fire-and-forget" style {@link MatsInitiate#send(Object) invocation} to. The sole stage is supplied directly.
* This type of endpoint cannot reply, as it has no-one to reply to (hence "terminator").
*
* Do note that this is just a convenience for the often-used scenario where an initiation requests out to some
* service, and then the reply needs to be handled - and with that the process is finished. That last endpoint which
* handles the reply is what is referred to as a terminator, in that it has nowhere to reply to. Note that there is
* nothing hindering you in setting the replyTo endpointId in a request initiation to point to a single-stage or
* multi-stage endpoint - however, any replies from those endpoints will just go void.
*
* It is possible to {@link ProcessContext#initiate(InitiateLambda) initiate} from within a terminator, and one
* interesting scenario here is to do a {@link MatsInitiate#publish(Object) publish} to a
* {@link #subscriptionTerminator(String, Class, Class, ProcessTerminatorLambda) subscriptionTerminator}. The idea
* is then that you do the actual processing via a request, and upon the reply processing in the terminator, you
* update the app database with the updated information (e.g. "order is processed"), and then you publish an "update
* caches" message to all the nodes of the app, so that they all have the new state of the order in their caches
* (or, in a push-based GUI logic, you might want to update all users' view of that order). Note that you (as in the
* processing node) will also get that published message on your instance of the SubscriptionTerminator.
*
* It is technically possible {@link ProcessContext#reply(Object) reply} from within a terminator - but it hard to
* envision many wise usage scenarios for this, as the stack at a terminator would probably be empty.
*
* @param endpointId
* the identification of this {@link MatsEndpoint}, which are the strings that should be provided to the
* {@link MatsInitiate#to(String)} or {@link MatsInitiate#replyTo(String, Object)} methods for this
* endpoint to get the message. Typical structure is "OrderService.placeOrder"
for public
* endpoints (which then is of a "fire-and-forget" style, since a terminator is not meant to reply), or
* "OrderService.terminator.validateOrder"
for private (app-internal) terminators that is
* targeted by the {@link MatsInitiate#replyTo(String, Object) replyTo(endpointId,..)} invocation of an
* initiation.
* @param stateClass
* the class of the State DTO that will may be provided by the
* {@link MatsInitiate#request(Object, Object) request initiation} (or that was sent along with the
* {@link MatsInitiate#send(Object, Object) invocation}).
* @param incomingClass
* the class of the incoming (typically reply) DTO.
* @param processor
* the {@link MatsStage stage} that will be invoked to process the incoming message.
* @return the {@link MatsEndpoint}, but you should not add any stages to it, as the sole stage is already added.
*/
MatsEndpoint terminator(String endpointId,
Class stateClass,
Class incomingClass,
ProcessTerminatorLambda processor);
/**
* Variation of {@link #terminator(String, Class, Class, ProcessTerminatorLambda)} that can be configured "on the
* fly".
*/
MatsEndpoint terminator(String endpointId,
Class stateClass,
Class incomingClass,
Consumer super EndpointConfig> endpointConfigLambda,
Consumer super StageConfig> stageConfigLambda,
ProcessTerminatorLambda processor);
/**
* Special kind of terminator that, in JMS-style terms, subscribes to a topic instead of listening to a queue (i.e.
* it uses "pub-sub"-style messaging, instead of queue-based). You may only communicate with this type of endpoints
* by using the {@link MatsInitiate#publish(Object)} or {@link MatsInitiate#replyToSubscription(String, Object)}
* methods.
*
* Notice that the concurrency of a SubscriptionTerminator is always 1, as it makes no sense to have multiple
* processors for a subscription - all of the processors would just get an identical copy of each message. If
* you do need to handle massive amounts of messages, or your work handling is slow, you should instead of handling
* the work in the processor itself, rather accept the message as fast as possible and send the work out to be
* processed by some kind of thread pool. (The tool MatsFuturizer
does this for its Future-completion
* handling).
*
* @param endpointId
* the identification of this {@link MatsEndpoint}, which are the strings that should be provided to the
* {@link MatsInitiate#to(String)} or {@link MatsInitiate#replyTo(String, Object)} methods for this
* endpoint to get the message.
* @param stateClass
* the class of the State DTO that will may be provided by the
* {@link MatsInitiate#request(Object, Object) request initiation} (or that was sent along with the
* {@link MatsInitiate#send(Object, Object) invocation}).
* @param incomingClass
* the class of the incoming (typically reply) DTO.
* @param processor
* the {@link MatsStage stage} that will be invoked to process the incoming message.
* @return the {@link MatsEndpoint}, but you should not add any stages to it, as the sole stage is already added.
*/
MatsEndpoint subscriptionTerminator(String endpointId,
Class stateClass,
Class incomingClass,
ProcessTerminatorLambda processor);
/**
* Variation of {@link #subscriptionTerminator(String, Class, Class, ProcessTerminatorLambda)} that can be
* configured "on the fly", but notice that the concurrency of a SubscriptionTerminator is always 1.
*/
MatsEndpoint subscriptionTerminator(String endpointId,
Class stateClass,
Class incomingClass,
Consumer super EndpointConfig> endpointConfigLambda,
Consumer super StageConfig> stageConfigLambda,
ProcessTerminatorLambda processor);
/**
* @return all {@link MatsEndpoint}s created on this {@link MatsFactory}.
*/
List> getEndpoints();
/**
* @param endpointId
* which {@link MatsEndpoint} to return, if present.
* @return the requested {@link MatsEndpoint} if present, {@link Optional#empty()} if not.
*/
Optional> getEndpoint(String endpointId);
/**
* Gets or creates the default Initiator (whose name is 'default') from which to initiate new Mats processes, i.e.
* send a message from "outside of Mats" to a Mats endpoint - NOTICE: This is an active object that can carry
* backend resources, and it is Thread Safe: You are not supposed to create one instance per message you send!
*
* IMPORTANT NOTICE!! The MatsInitiator returned from this specific method is special when used within a Mats
* Stage's context (i.e. the Thread running a Mats Stage): Any initiations performed with it within a Mats Stage
* will have the same transactional demarcation as an initiation performed using
* {@link ProcessContext#initiate(InitiateLambda) ProcessContext.initiate(..)}. The idea here is that you thus can
* create methods that both can be used from the outside of a Mats Stage (thus resulting in an ordinary initiation),
* but if the same method is invoked within a Mats Stage, the initiation will partake in the same transactional
* demarcation as the rest of what happens within that Mats Stage. Note that you get a bit strange semantics wrt.
* the exceptions that {@link MatsInitiator#initiate(InitiateLambda) MatsInitiator.initiate(..)} and
* {@link MatsInitiator#initiateUnchecked(InitiateLambda) MatsInitiator.initiateUnchecked(..)} raises: Outside of a
* Mats Stage, they can throw in the given situations those exceptions describe. However, within a Mats Stage, they
* will never throw those exceptions, since the actual initiation is not performed until the Mats Stage exits. But,
* if you want to make such a dual mode method that can be employed both outside and within a Mats Stage, you should
* thus code for the "Outside" mode, handling those Exceptions as you would in an ordinary initiation.
*
* If you would really NOT want this - i.e. you for some reason want the initiation performed within the stage to
* execute even though the Mats Stage fails - you may use {@link #getOrCreateInitiator(String)}, and you can even
* request the same underlying default initiator by just supplying that method with the argument "default". Please,
* however, make sure you understand the quite heavy consequence of this: If the Mats Stage throws, the
* retry-mechanism will kick in, running the Mats Stage one more time, and you will thus potentially send that
* message many times - one time per retry - since such an initiation with a NON-default MatsInitiator is
* specifically then not part of the Stage's transactional demarcation.
*
* Just to ensure that this point comes across: The returned MatsInitiator is Thread Safe, and meant for reuse:
* You are not supposed to create one instance of {@link MatsInitiator} per message you need to send, and
* then close it afterwards - rather either create one for the entire application, or e.g. for each component:
* The {@code MatsInitiator} can have underlying backend resources attached to it - which also means that it needs
* to be {@link MatsInitiator#close() closed} for a clean application shutdown (Note that all MatsInitiators are
* closed when {@link #stop(int) MatsFactory.stop()} is invoked).
*
* @return the default MatsInitiator
, whose name is 'default', on which messages can be
* {@link MatsInitiator#initiate(InitiateLambda) initiated}.
* @see ContextLocal#getAttribute(Class, String...)
*/
MatsInitiator getDefaultInitiator();
/**
* Gets or creates a new Initiator from which to initiate new Mats processes, i.e. send a message from "outside of
* Mats" to a Mats endpoint - NOTICE: This is an active object that can carry backend resources, and it is Thread
* Safe: You are not supposed to create one instance per message you send!
*
* A reason for wanting to make more than one {@link MatsInitiator} could be that each initiator might have its own
* connection to the underlying message broker. You also might want to name the initiators based on what part of the
* application uses it; The name of the initiator shows up in monitors and tooling.
*
* IMPORTANT NOTICE!! Please read the JavaDoc of {@link #getDefaultInitiator()} for important information
* wrt. transactional demarcation when employing a NON-default MatsInitiator within a Mats Stage.
*
* Just to ensure that this point comes across: The returned MatsInitiator is Thread Safe, and meant for reuse:
* You are not supposed to create one instance of {@link MatsInitiator} per message you need to send, and
* then close it afterwards - rather either create one for the entire application, or e.g. for each component:
* The {@code MatsInitiator} can have underlying backend resources attached to it - which also means that it needs
* to be {@link MatsInitiator#close() closed} for a clean application shutdown (Note that all MatsInitiators are
* closed when {@link #stop(int) MatsFactory.stop()} is invoked).
*
* @return a {@link MatsInitiator}, on which messages can be {@link MatsInitiator#initiate(InitiateLambda)
* initiated}.
*/
MatsInitiator getOrCreateInitiator(String name);
/**
* @return all {@link MatsInitiator}s created on this {@link MatsFactory}.
*/
List getInitiators();
/**
* Starts all endpoints that has been created by this factory, by invoking {@link MatsEndpoint#start()} on them.
* (Any plugins which aren't started will also be started - this will happen if the MatsFactory has actively
* been {@link #stop(int) stopped} after start, and then started again).
*
* Clears the {@link #holdEndpointsUntilFactoryIsStarted()}-flag, so that any endpoints created after this method
* has been invoked will start immediately.
*/
@Override
void start();
/**
* If this method is invoked before any endpoint is created, the endpoints will not start even though
* {@link MatsEndpoint#finishSetup()} is invoked on them, but will wait till {@link #start()} is invoked on the
* factory. This feature should be employed in most setups where the MATS endpoints might use other services or
* components whose order of creation and initialization are difficult to fully control, e.g. typically in an IoC
* container like Spring. Set up the "internal machinery" of the system, including internal services, and only after
* all this is running, then fire up the endpoints. If this is not done, the endpoints might start consuming
* messages off of the MQ (there might already be messages waiting when the service boots), and thus invoke
* services/components that are not yet fully started.
*
* Note: To implement delayed start for a specific endpoint, simply hold off on invoking
* {@link MatsEndpoint#finishSetup()} until you are OK with it being started and hence starts consuming messages
* (e.g. when the needed cache service is finished populated); This semantics works both when
* holdEndpointsUntilFactoryIsStarted() has been invoked or not.
*
* @see MatsEndpoint#finishSetup()
* @see MatsEndpoint#start()
*/
void holdEndpointsUntilFactoryIsStarted();
/**
* Waits until all endpoints have fully entered the receive-loops, i.e. runs
* {@link MatsEndpoint#waitForReceiving(int)} on all the endpoints started from this factory.
*
* Note: If there are no Endpoints registered, this will immediately return true
!
*/
@Override
boolean waitForReceiving(int timeoutMillis);
/**
* Stops all endpoints and initiators, by invoking {@link MatsEndpoint#stop(int)} on all the endpoints, and
* {@link MatsInitiator#close()} on all initiators that has been created by this factory. They can be started again
* individually, or all at once by invoking {@link #start()}
*
* Should be invoked at application shutdown.
*
* @return true
if all Endpoints (incl. e.g. threads) and resources (e.g. JMS Connections) closed
* successfully.
*/
@Override
boolean stop(int gracefulShutdownMillis);
/**
* Convenience method, particularly for Spring's default destroy mechanism: Default implemented to invoke
* {@link #stop(int) stop(30 seconds)}.
*/
default void close() {
stop(30_000);
}
/**
* In a situation where you might be given a {@link MatsFactoryWrapper}, but need to find a wrappee that implements
* a specific interface, this method allows you to just always call matsFactory.unwrapTo(iface)
instead
* of first checking whether it is a proxy and only then cast and unwrap.
*
* @return default this
if iface.isAssignableFrom(thisClass)
, otherwise throws
* IllegalArgumentException
(overridden by wrappers).
*/
@SuppressWarnings("unchecked")
default I unwrapTo(Class iface) {
if (iface == null) {
throw new NullPointerException("iface");
}
if (iface.isAssignableFrom(getClass())) {
return (I) this;
}
throw new IllegalArgumentException("This [" + this + "] doesn't implement [" + iface + "].");
}
/**
* In a situation where you might be given a {@link MatsFactoryWrapper}, but need the actual implementation, this
* method allows you to just always call matsFactory.unwrapFully()
instead of first checking whether it
* is a proxy and only then cast and unwrap.
*
* @return default this
for implementations (overridden by wrappers).
*/
default MatsFactory unwrapFully() {
return this;
}
/**
* Provides ThreadLocal access to attributes from the {@link MatsInitiate} initiate context and {@link MatsStage}
* process context - currently {@link #getAttribute(Class, String...)}, which can provide you with the
* transactionally demarcated SQL Connection if the Mats implementation provides such.
*/
class ContextLocal {
private static volatile BiFunction, String[], Optional>> callback;
/**
* Provides a ThreadLocal-accessible variant of the {@link ProcessContext#getAttribute(Class, String...)
* ProcessContext.getAttribute(..)} and {@link MatsInitiate#getAttribute(Class, String...)
* MatsInitiate.getAttribute(..)} methods: If the executing thread is within a
* {@link MatsInitiator#initiate(InitiateLambda) Mats initiation}, or is currently processing a
* {@link MatsStage}, the return value will be the same as if the relevant getAttribute(..)
method
* was invoked. Otherwise, if the current thread is not processing a stage or performing an initiation,
* Optional.empty()
is returned.
*
* If the Mats implementation has a transactional SQL Connection, it shall be available by
* ContextLocal.getAttribute(Connection.class)
when in the relevant contexts (init or stage).
*
* @param type
* The expected type of the attribute
* @param name
* The (optional) (hierarchical) name(s) of the attribute.
* @param
* The type of the attribute.
* @return Optional of the attribute in question - if not in relevant context, Optional.empty()
is
* always returned.
*
* @see ProcessContext#getAttribute(Class, String...)
* @see MatsInitiate#getAttribute(Class, String...)
* @see #getDefaultInitiator()
*/
@SuppressWarnings("unchecked")
public static Optional getAttribute(Class type, String... name) {
return (Optional) callback.apply(type, name);
}
}
/**
* Provides for a way to configure factory-wide elements and defaults.
*/
interface FactoryConfig extends MatsConfig {
/**
* Sets the name of the MatsFactory, default is "" (empty string).
*
* @param name
* the name that the MatsFactory should have.
*/
FactoryConfig setName(String name);
/**
* @return the name of the MatsFactory, as set by {@link #setName(String)}, shall return "" (empty string) if
* not set, never {@code null}.
*/
String getName();
/**
* @return the number of CPUs that Mats shall assume there is available. Default should be
* {@link Runtime#availableProcessors() Runtime.getRuntime().availableProcessors()}. Implementations
* should honor the System Property "mats.cpus" and let that override this number, e.g. "-Dmats.cpus=3"
* on command line.
*/
int getNumberOfCpus();
/**
* Sets a Function that may modify the TraceId of Mats flows that are initiated "from the outside", i.e. not
* from within a Stage. The intended use is to automatically prefix the Mats flow TraceId with some sort of
* contextual request id, typically when the Mats flow is initiated in a HTTP/REST context where the incoming
* request carries a "X-Request-ID" or "X-Correlation-ID" header. The idea is then that this header is picked
* out in some form of filter, placed in some ThreadLocal context, and then prepended to the TraceId that the
* Mats flow is initiated with using the provided function. For example, if the the HTTP Request has a
* X-Request-ID of "abc", and the Mats flow is initiated with TraceId "123", the resulting TraceId for the Mats
* flow would be "abc+123" (the plus character being the preferred separator for composed TraceIds, i.e.
* TraceIds that are put together by successively more specific information).
*
* The Function gets the entire user provided TraceId as input (as set via
* {@link MatsInitiate#traceId(CharSequence)}, and should return a fully formed TraceId that should be used
* instead. Needless to say, it should never throw, and if it doesn't have any contextual id to prefix with, it
* should return whatever that was passed into it.
*
* @param modifier
* the function that accepts the TraceId that the Mats flow is initiated with, and returns the
* TraceId that the Mats flow should actually get.
* @return this
for chaining.
*/
FactoryConfig setInitiateTraceIdModifier(Function modifier);
/**
* Sets the prefix that should be applied to the endpointIds to get queue or topic name in the underlying
* messaging system - the default is "mats."
. Needless to say, two MatsFactories which are
* configured differently here will not be able to communicate. Do not change this unless you have a
* compelling reason to why, as you then stray away from the standard, and it will be harder to later merge
* two setups.
*
* @param prefix
* the prefix that should be applied to the endpointIds to get queue or topic name in the underlying
* messaging system. Default is "mats."
.
* @return this
for chaining.
*/
FactoryConfig setMatsDestinationPrefix(String prefix);
/**
* @return the prefix that is applied to endpointIds to get the queue and topic and topic names. Defaults to
* "mats."
.
*/
String getMatsDestinationPrefix();
/**
* Sets the key name on which to store the "wire representation" of the Mats message if the underlying mechanism
* uses some kind of Map - the default is "mats:trace"
. (The JMS Implementation uses a JMS
* MapMessage, and this is the key). Needless to say, two MatsFactories which are configured differently here
* will not be able to communicate. Do not change this unless you have a compelling reason to why, as you
* then stray away from the standard, and it will be harder to later merge two setups.
*
* @param key
* the key name on which to store the "wire representation" of the Mats message if the underlying
* mechanism uses some kind of Map. Default is "mats:trace"
.
* @return this
for chaining.
*/
FactoryConfig setMatsTraceKey(String key);
/**
* @return the key name on which to store the "wire representation" of the Mats message if the underlying
* mechanism uses some kind of Map. (The JMS Implementation uses a JMS MapMessage, and this is the key).
* Default is "mats:trace"
.
*/
String getMatsTraceKey();
/**
* @return the name of the application/service that employs MATS, set at MatsFactory construction time.
*/
String getAppName();
/**
* @return the version string of the application/service that employs MATS, set at MatsFactory construction
* time.
*/
String getAppVersion();
/**
* Sets the nodename that {@link #getNodename()} should return. This is not necessary if the current hosts's
* hostname is a good enough nodename (which it in a normal production system should be, both running on iron,
* VMs and containers like Docker or Kubernetes), as that is what is returned by the getter by default. Must be
* set before anything that depends on the return value of {@link #getNodename()} is started, i.e. right away
* after having created the MatsFactory instance.
*
* @param nodename
* the nodename that this MatsFactory should report from {@link #getNodename()}.
* @return this
for chaining.
*/
FactoryConfig setNodename(String nodename);
/**
* Returns a node-specific identifier, that is, a name which is different between different instances of the
* same app running of different nodes. This can be used to make node-specific topics, which are nice when you
* need a message to return to the node that sent it, due to some synchronous process waiting for the message
* (which entirely defeats the Messaging Oriented Middleware Architecture, but sometimes you need a solution..).
* This string is used for such a purpose in the MatsFuturizer tool and the "MatsSockets" WebSocket system. The
* nodename is also used by the MatsTrace "wire protocol" as debug information. It is not used by any
* Mats-internal parts.
*
* @return the nodename, which by default should be the hostname which the application is running on.
*/
String getNodename();
/**
* @return the Mats Implementation name.
*/
String getMatsImplementationName();
/**
* @return the Mats Implementation version.
*/
String getMatsImplementationVersion();
/**
* This method is only relevant for tooling, and thus "hidden away" in this config class. It uses the
* MatsFactory-configured deserializer mechanism to instantiate the specified class. The rationale for this is
* to either test whether a STO (State Transfer Object) or a DTO (Data Transfer Object) actually can be
* instantiated (if e.g. Jackson is used as serialization mechanism, which is default for the "MatsTrace"
* implementation, it needs a no-args constructor to be able to instantiate, while if using GSON - which employs
* "Objenesis" for instantiation - a non-args constructor is not necessary) - or to just get hold of a new
* instance to do some kind of introspection (this is e.g. needed for Mats' SpringConfig when using
* {@code @MatsClassMapping}).
*
* @param type
* the Class that should be instantiated.
* @param
* the type of the Class.
* @return a new instance
* @throws RuntimeException
* if the class could not be instantiated (e.g. lacking no-args constructor which can be an issue
* depending on the serialization mechanism).
*/
T instantiateNewObject(Class type);
/**
* Installs a new plugin. The plugin will be {@link MatsPlugin#start(MatsFactory)} started} immediately - you
* may inspect which Endpoints are at that point already present. It will subsequently have its
* {@link MatsPlugin#addingEndpoint(MatsEndpoint) endpointAdded(MatsEndpoint)} and
* {@link MatsPlugin#removedEndpoint(MatsEndpoint) endpointRemoved(MatsEndpoint)} methods invoked as Endpoints
* are added and removed from the MatsFactory. All these operations are done within a synchronized construct, so
* that the plugin shall be able to have a consistent view of the MatsFactory.
*
* The MatsFactory might have special handling for a given plugin based on the interfaces it implements. For
* example, the JMS implementation know about the 'mats-intercept-api' module, and will act accordingly.
*
* @param plugin
* the plugin to install, null
is not allowed.
* @return the config object, for method chaining.
* @throws IllegalStateException
* if the plugin instance is already installed.
*/
FactoryConfig installPlugin(MatsPlugin plugin) throws IllegalStateException;
/**
* Returns the list of plugins that are installed on this MatsFactory, filtered by the provided class or
* interface. If you want all plugins, use MatsPlugin.class
.
*
* @param filterByClass
* Which type of plugins to return. MatsPlugin.class
returns all installed plugins.
* null
is not allowed.
* @return the selected list of plugins
* @param
* What type the plugin is. This might be an interface of the actual plugin.
*/
List getPlugins(Class filterByClass);
/**
* Removes the specified plugin from this MatsFactory. The plugin will have its {@link MatsPlugin#preStop()} and
* {@link MatsPlugin#stop()} methods invoked, and will subsequently be out of the equation.
*
* @param instanceToRemove
* the plugin instance to remove.
*/
boolean removePlugin(MatsPlugin instanceToRemove);
// Overridden to return the more specific FactoryConfig instead of MatsConfig
@Override
FactoryConfig setAttribute(String key, Object value);
// Overridden to return the more specific FactoryConfig instead of MatsConfig
@Override
FactoryConfig setConcurrency(int concurrency);
// Overridden to return the more specific FactoryConfig instead of MatsConfig
@Override
FactoryConfig setInteractiveConcurrency(int concurrency);
}
interface MatsPlugin {
/**
* Invoked when installed, before it is added to the MatsFactory. This is the place to do any
* initialization that needs to be done before the plugin will get other invocations. The state of the
* MatsFactory is effectively locked while this method is invoked - so that you can get a consistent view of
* what Endpoints is already added to the MatsFactory, vs. what endpoints are
* {@link #addingEndpoint(MatsEndpoint) added} and {@link #removedEndpoint(MatsEndpoint removed)} later.
*
* Notice again that it is not added to the MatsFactory unless this method returns without throwing an
* exception. This also means that if you register any Endpoints in this method, or create Initiators, the
* corresponding {@link #addingEndpoint(MatsEndpoint) addingEndpoint(..)} or
* {@link #addingInitiator(MatsInitiator) addingInitiator(..)} will not be invoked.
*
*
* @param matsFactory
* the MatsFactory instance that this plugin is installed on.
*/
default void start(MatsFactory matsFactory) {
}
/**
* Invoked before the Initiator is added to the MatsFactory. For interception needs, check out
* 'mats-intercept-api'.
*
* @param initiator
* the initiator that is about to be added.
*/
default void addingInitiator(MatsInitiator initiator) {
}
/**
* Invoked when the Endpoint's {@link MatsEndpoint#finishSetup() finishSetup()} is invoked, before the Endpoint
* is added to the MatsFactory. For interception needs, check out 'mats-intercept-api'.
*
* @param endpoint
* the endpoint that is about to be added.
*/
default void addingEndpoint(MatsEndpoint, ?> endpoint) {
}
/**
* Invoked after the Endpoint has been removed from the MatsFactory - note that this will not be invoked for all
* endpoints when the plugin is {@link FactoryConfig#removePlugin(MatsPlugin) removed} or the MatsFactory is
* {@link MatsFactory#stop(int) stopped}, so you must handle any per-Endpoint cleanup in {@link #preStop()} and
* {@link #stop()}.
*
* Note that removing an Endpoint is primarily meant for testing procedures, so this should not be a common
* scenario in production.
*
* @param endpoint
* the endpoint that was removed.
*/
default void removedEndpoint(MatsEndpoint, ?> endpoint) {
}
/**
* Stop step 1/2: E.g. set "runflags" to false, and signal or interrupt threads, and clear out any per-Endpoint
* resources. Invoked after the plugin is {@link FactoryConfig#removePlugin(MatsPlugin) removed} from the
* MatsFactory, or when the MatsFactory is {@link MatsFactory#stop(int) stopped}. The plugin is not removed upon
* MatsFactory stop (and neither are Initiators and Endpoints), and will be started again by invoking
* {@link MatsFactory#start()}).
*
* Note: Upon MatsFactory stopping, this method is invoked before the Initiators and Endpoints are
* stopped. The state of the MatsFactory is effectively locked while this method is invoked.
*/
default void preStop() {
}
/**
* Stop step 2/2: E.g. wait for threads to finish and close resources - make sure this happens in a timely
* fashion. Invoked after the plugin is {@link FactoryConfig#removePlugin(MatsPlugin) removed} from the
* MatsFactory, or when the MatsFactory is {@link MatsFactory#stop(int) stopped}. The plugin it is not removed
* upon MatsFactory stop (and neither are Initiators and Endpoints), and will be started again by invoking
* {@link MatsFactory#start()}).
*
* Note: Upon MatsFactory stopping, this method is invoked after the Initiators and Endpoints are
* stopped. The state of the MatsFactory is effectively locked while this method is invoked.
*/
default void stop() {
}
}
/**
* Base Wrapper interface which Mats-specific Wrappers implements, defining four "wrappee" methods.
*
* @param
* the type of the wrapped instance.
*/
interface MatsWrapper {
void setWrappee(T target);
T unwrap();
@SuppressWarnings("unchecked")
default I unwrapTo(Class iface) {
if (iface == null) {
throw new NullPointerException("iface");
}
if (iface.isAssignableFrom(getClass())) {
return (I) this;
}
T unwrapped = unwrap();
if (iface.isAssignableFrom(unwrapped.getClass())) {
return (I) unwrapped;
}
if (unwrapped instanceof MatsWrapper) {
return ((MatsWrapper) unwrapped).unwrapTo(iface);
}
throw new IllegalArgumentException("This [" + this + "] doesn't implement [" + iface
+ "], and neither do the wrappee [" + unwrapped + "].");
}
/**
* @return the fully unwrapped instance: If the returned instance from {@link #unwrap()} is itself a
* {@link MatsWrapper MatsWrapper}, it will recurse down by invoking this method
* (unwrapFully()
) again on the returned target.
*/
default T unwrapFully() {
T target = unwrap();
// ?: If further wrapped, recurse down. Otherwise return.
if (target instanceof MatsWrapper) {
// -> Yes, further wrapped, so recurse
@SuppressWarnings("unchecked")
MatsWrapper wrapped = (MatsWrapper) target;
return wrapped.unwrapFully();
}
// E-> No, not wrapped - this is the end target.
return target;
}
}
/**
* A base Wrapper for {@link MatsFactory}, which simply implements MatsFactory, takes a MatsFactory instance and
* forwards all calls to that. Use this if you need to wrap the MatsFactory, where most of the methods are
* pass-through to the target, as any changes to the MatsFactory interface then won't break your wrapper.
*
* Note: The {@link #hashCode()} and {@link #equals(Object)} are implemented to forward to the wrappee.
*/
class MatsFactoryWrapper implements MatsWrapper, MatsFactory {
/**
* This field is private - all methods invoke {@link #unwrap()} to get the instance, which you should too if you
* override any methods. If you want to take control of the wrapped MatsFactory instance, then override
* {@link #unwrap()}.
*/
private MatsFactory _targetMatsFactory;
/**
* Standard constructor, taking the wrapped {@link MatsFactory} instance.
*
* @param targetMatsFactory
* the {@link MatsFactory} instance which {@link #unwrap()} will return (and hence all forwarded
* methods will use).
*/
public MatsFactoryWrapper(MatsFactory targetMatsFactory) {
setWrappee(targetMatsFactory);
}
/**
* No-args constructor, which implies that you either need to invoke {@link #setWrappee(MatsFactory)} before
* publishing the instance (making it available for other threads), or override {@link #unwrap()} to provide the
* desired {@link MatsFactory} instance. In these cases, make sure to honor memory visibility semantics - i.e.
* establish a happens-before edge between the setting of the instance and any other threads getting it.
*/
public MatsFactoryWrapper() {
/* no-op */
}
/**
* Sets the wrapped {@link MatsFactory}, e.g. in case you instantiated it with the no-args constructor. Do
* note that the field holding the wrapped instance is not volatile nor synchronized. This means that if you
* want to set it after it has been published to other threads, you will have to override both this method and
* {@link #unwrap()} to provide for needed memory visibility semantics, i.e. establish a happens-before edge
* between the setting of the instance and any other threads getting it. A volatile
field would
* work nice.
*
* @param targetMatsFactory
* the {@link MatsFactory} which is returned by {@link #unwrap()}, unless that is overridden.
*/
public void setWrappee(MatsFactory targetMatsFactory) {
_targetMatsFactory = targetMatsFactory;
}
/**
* @return the wrapped {@link MatsFactory}. All forwarding methods invokes this method to get the wrapped
* {@link MatsFactory}, thus if you want to get creative wrt. how and when the MatsFactory is decided,
* you can override this method.
*/
public MatsFactory unwrap() {
if (_targetMatsFactory == null) {
throw new IllegalStateException("MatsFactory.MatsFactoryWrapper.unwrap():"
+ " The '_targetMatsFactory' is not set!");
}
return _targetMatsFactory;
}
/**
* Resolve the "deadly diamond of death", by calling to MatsWrapper.super.unwrapTo(iface)
;
*/
@Override
public I unwrapTo(Class iface) {
return MatsWrapper.super.unwrapTo(iface);
}
@Override
public MatsFactory unwrapFully() {
return unwrap().unwrapFully();
}
@Override
public FactoryConfig getFactoryConfig() {
return unwrap().getFactoryConfig();
}
@Override
public MatsEndpoint staged(String endpointId, Class replyClass, Class stateClass) {
return unwrap().staged(endpointId, replyClass, stateClass);
}
@Override
public MatsEndpoint staged(String endpointId, Class replyClass, Class stateClass,
Consumer super EndpointConfig> endpointConfigLambda) {
return unwrap().staged(endpointId, replyClass, stateClass, endpointConfigLambda);
}
@Override
public MatsEndpoint single(String endpointId, Class replyClass, Class incomingClass,
ProcessSingleLambda processor) {
return unwrap().single(endpointId, replyClass, incomingClass, processor);
}
@Override
public MatsEndpoint single(String endpointId, Class replyClass, Class incomingClass,
Consumer super EndpointConfig> endpointConfigLambda,
Consumer super StageConfig> stageConfigLambda, ProcessSingleLambda processor) {
return unwrap().single(endpointId, replyClass, incomingClass, endpointConfigLambda,
stageConfigLambda, processor);
}
@Override
public MatsEndpoint terminator(String endpointId, Class stateClass, Class incomingClass,
ProcessTerminatorLambda processor) {
return unwrap().terminator(endpointId, stateClass, incomingClass, processor);
}
@Override
public MatsEndpoint terminator(String endpointId, Class stateClass, Class incomingClass,
Consumer super EndpointConfig> endpointConfigLambda,
Consumer super StageConfig> stageConfigLambda, ProcessTerminatorLambda processor) {
return unwrap().terminator(endpointId, stateClass, incomingClass, endpointConfigLambda,
stageConfigLambda, processor);
}
@Override
public MatsEndpoint subscriptionTerminator(String endpointId, Class stateClass,
Class incomingClass, ProcessTerminatorLambda processor) {
return unwrap().subscriptionTerminator(endpointId, stateClass, incomingClass, processor);
}
@Override
public MatsEndpoint subscriptionTerminator(String endpointId, Class stateClass,
Class incomingClass, Consumer super EndpointConfig> endpointConfigLambda,
Consumer super StageConfig> stageConfigLambda, ProcessTerminatorLambda processor) {
return unwrap().subscriptionTerminator(endpointId, stateClass, incomingClass,
endpointConfigLambda, stageConfigLambda, processor);
}
@Override
public List> getEndpoints() {
return unwrap().getEndpoints();
}
@Override
public Optional> getEndpoint(String endpointId) {
return unwrap().getEndpoint(endpointId);
}
@Override
public MatsInitiator getDefaultInitiator() {
return unwrap().getDefaultInitiator();
}
@Override
public MatsInitiator getOrCreateInitiator(String name) {
return unwrap().getOrCreateInitiator(name);
}
@Override
public List getInitiators() {
return unwrap().getInitiators();
}
@Override
public void holdEndpointsUntilFactoryIsStarted() {
unwrap().holdEndpointsUntilFactoryIsStarted();
}
@Override
public void start() {
unwrap().start();
}
@Override
public boolean waitForReceiving(int timeoutMillis) {
return unwrap().waitForReceiving(timeoutMillis);
}
@Override
public boolean stop(int gracefulShutdownMillis) {
return unwrap().stop(gracefulShutdownMillis);
}
@Override
public int hashCode() {
return unwrap().hashCode();
}
@Override
public boolean equals(Object obj) {
return unwrap().equals(obj);
}
@Override
public String toString() {
return this.getClass().getSimpleName() + "[" + unwrap().toString() + "]";
}
}
}