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

io.mats3.MatsEndpoint Maven / Gradle / Ivy

Go to download

API for Mats^3: Message-based Asynchronous Transactional Staged Stateless Services.

There is a newer version: 0.19.22-2024-11-09
Show newest version
package io.mats3;

import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;

import io.mats3.MatsConfig.StartStoppable;
import io.mats3.MatsFactory.ContextLocal;
import io.mats3.MatsFactory.FactoryConfig;
import io.mats3.MatsFactory.MatsWrapper;
import io.mats3.MatsInitiator.InitiateLambda;
import io.mats3.MatsInitiator.MatsInitiate;
import io.mats3.MatsInitiator.MessageReference;
import io.mats3.MatsStage.StageConfig;

/**
 * Represents a Mats Endpoint - you create instances from the {@link MatsFactory} (or use the Spring integration).
 * 

* Implementation Note: It shall be possible to use instances of MatsEndpoint as keys in a * HashMap, i.e. their equals and hashCode should remain stable throughout the life of the MatsFactory - * and similar instances but with different MatsFactory are not equals. Depending on the implementation, instance * equality may be sufficient. * * @author Endre Stølsvik - 2015-07-11 - http://endre.stolsvik.com */ public interface MatsEndpoint extends StartStoppable { /** * @return the config for this endpoint. If endpoint is not yet started, you may invoke mutators on it. */ EndpointConfig getEndpointConfig(); /** * @return the parent {@link MatsFactory}. */ MatsFactory getParentFactory(); /** * Adds a new stage to a multi-stage endpoint. If this is the last stage of a multi-stage endpoint, you must invoke * {@link #finishSetup()} afterwards - or you could instead use the {@link #lastStage(Class, ProcessReturnLambda)} * variant which does this automatically. * * @see MatsObject * @param * the type of the incoming DTO. The very first stage's incoming DTO is the endpoint's incoming DTO. If * the special type {@link MatsObject}, this stage can take any type. * @param processor * the lambda that will be invoked when messages arrive in the corresponding queue. */ MatsStage stage(Class incomingClass, ProcessLambda processor); /** * Variation of {@link #stage(Class, ProcessLambda)} that can be configured "on the fly". */ MatsStage stage(Class incomingClass, Consumer> stageConfigLambda, ProcessLambda processor); /** * Adds the last stage to a multi-stage endpoint, which also {@link #finishSetup() finishes setup} of the endpoint. * Note that the last-stage concept is just a convenience that lets the developer reply from the endpoint with a * return replyDTO statement - you may just as well add a standard stage, and invoke the * {@link ProcessContext#reply(Object)} method. Note: If using a normal stage as the last stage, you must remember * to invoke {@link #finishSetup()} afterwards, as that is then not done automatically. * * @param * the type of the incoming DTO. The very first stage's incoming DTO is the endpoint's incoming DTO. If * the special type {@link MatsObject}, this stage can take any type. * @param processor * the lambda that will be invoked when messages arrive in the corresponding queue. */ MatsStage lastStage(Class incomingClass, ProcessReturnLambda processor); /** * Variation of {@link #lastStage(Class, ProcessReturnLambda)} that can be configured "on the fly". */ MatsStage lastStage(Class incomingClass, Consumer> stageConfigLambda, ProcessReturnLambda processor); /** * @return a List of {@link MatsStage}s, representing all the stages of the endpoint. The order is the same as the * order in which the stages will be invoked. For single-staged endpoints and terminators, this list is of * size 1. */ List> getStages(); /** * The lambda that shall be provided by the developer for the process stage(s) for the endpoint - provides the * context, state and incoming message DTO. */ @FunctionalInterface interface ProcessLambda { void process(ProcessContext ctx, S state, I msg) throws MatsRefuseMessageException; } /** * Specialization of {@link MatsEndpoint.ProcessLambda ProcessLambda} that makes it possible to do a "return * replyDto" at the end of the stage, which is just a convenient way to invoke * {@link MatsEndpoint.ProcessContext#reply(Object)}. Used for the last process stage of a multistage endpoint. */ @FunctionalInterface interface ProcessReturnLambda { R process(ProcessContext ctx, S state, I msg) throws MatsRefuseMessageException; } /** * Specialization of {@link MatsEndpoint.ProcessLambda ProcessLambda} which does not have a state, and have the same * return-semantics as {@link MatsEndpoint.ProcessReturnLambda ProcessReturnLambda} - used for single-stage * endpoints as these does not have multiple stages to transfer state between. *

* However, since it is possible to send state along with the request, one may still use the * {@link MatsEndpoint.ProcessReturnLambda ProcessReturnLambda} for single-stage endpoints, but in this case you * need to code it up yourself by making a multi-stage and then just adding a single lastStage. */ @FunctionalInterface interface ProcessSingleLambda { R process(ProcessContext ctx, I msg) throws MatsRefuseMessageException; } /** * Specialization of {@link MatsEndpoint.ProcessLambda ProcessLambda} which does not have reply specified - used for * terminator endpoints. It has state, as the initiator typically have state that it wants the terminator to get. */ @FunctionalInterface interface ProcessTerminatorLambda { void process(ProcessContext ctx, S state, I msg) throws MatsRefuseMessageException; } /** * Should be invoked when all stages has been added. Will automatically be invoked by invocation of * {@link #lastStage(Class, ProcessReturnLambda)}, which again implies that it will be invoked when creating * {@link MatsFactory#single(String, Class, Class, ProcessSingleLambda) single-stage endpoints} and * {@link MatsFactory#terminator(String, Class, Class, ProcessTerminatorLambda) terminators} and * {@link MatsFactory#subscriptionTerminator(String, Class, Class, ProcessTerminatorLambda) subscription * terminators}. *

* This sets the state of the endpoint to "finished setup", and will invoke {@link #start()} on the endpoint, * unless {@link MatsFactory#holdEndpointsUntilFactoryIsStarted()} has been invoked prior to creating the * endpoint. *

* You may implement "delayed start" of an endpoint by not invoking finishedSetup() after setting it up. * Taking into account the first chapter of this JavaDoc, note that you must then only use the * {@link MatsFactory#staged(String, Class, Class) staged} type of Endpoint setup, as the others implicitly invokes * finishSetup(), and also not invoke {@link #lastStage(Class, ProcessReturnLambda) lastStage} on it as this * also implicitly invokes finishSetup(). When setting an Endpoint up without calling finishSetup(), even when * {@link MatsFactory#start()} is invoked, such a not-finished endpoint will then not be started. You may then later * invoke finishSetup(), e.g. when any needed caches are finished populated, and the endpoint will then be finished * and started. *

* Another way to implement "delayed start" is to obviously just not create the endpoint until later: MatsFactory * has no "that's it, now all endpoints must have been created"-lifecycle stage, and can fire up new * endpoints until the JVM is dead. */ void finishSetup(); /** * Starts the endpoint (unless {@link #finishSetup()} has NOT been invoked), invoking {@link MatsStage#start()} on * any not-yet started stages of the endpoint (which should be all of them at application startup). */ @Override void start(); /** * Waits till all stages of the endpoint has entered their receive-loops, i.e. invokes * {@link MatsStage#waitForReceiving(int)} on all {@link MatsStage}s of the endpoint. *

* Note: This method makes most sense for * {@link MatsFactory#subscriptionTerminator(String, Class, Class, ProcessTerminatorLambda) * SubscriptionTerminators}: These are based on MQ Topics, whose semantics are that if you do not listen right when * someone says something, you will not hear it. This means that a SubscriptionTerminator will never receive a * message that was sent before it had started the receive-loop. Thus, if you in a service-"boot" phase send * a message whose result will come in to a SubscriptionTerminator, you will not receive this result if the * receive-loop has not started. This is relevant for certain cache setups where you listen for event updates, and * when "booting" the cache, you need to be certain that you have started receiving updates before asking for the * "initial load" of the cache. It is also relevant for tools like the MatsFuturizer, which uses a * node-specific Topic for the final reply message from the requested service; If the SubscriptionTerminator has not * yet made it to the receive-loop, any replies will simply be lost and the future never completed. *

* Note: Currently, this only holds for the initial start. If the entity has started the receive-loop at some point, * it will always immediately return - even though it is currently stopped. * * @return whether it was started (i.e. true if successfully started listening for messages). */ @Override boolean waitForReceiving(int timeoutMillis); /** * Stops the endpoint, invoking {@link MatsStage#stop(int)} on all {@link MatsStage}s. * * @return whether it was successfully stopped (i.e. true if successfully stopped all listening * threads). */ @Override boolean stop(int gracefulShutdownMillis); /** * Should most probably only be used for testing! *

* First invokes {@link #stop(int) stop(gracefulShutdownMillis)}, and if successful removes the endpoint from * its MatsFactory. This enables a new endpoint to be registered with the same endpointId as the one removed (which * otherwise is not accepted). This might be of interest in testing scenarios, where you want to change the * implementation of an endpoint from one test to another. There is currently no known situation where this makes * sense to do "in production": Once the system is set up with the correct endpoints, it should most probably stay * that way! * * @return whether the {@link #stop(int gracefulShutdownMillis)} was successful, and thus whether the endpoint was * removed from its MatsFactory. */ boolean remove(int gracefulShutdownMillis); /** * For the incoming message type, this represents the equivalent of Java's {@link Object} - a "generic" incoming * message whose type is not yet determined. When you know, you invoke {@link #toClass(Class)} to get it "casted" * (i.e. deserialized) to the specified type. */ interface MatsObject { /** * Deserializes the incoming message class to the desired type - assuming that it actually is a serialized * representation of that class. * * @param type * the class that the incoming message should be deserialized to. * @param * the type of 'type' * @return the deserialized object. * @throws IllegalArgumentException * if the incoming message could not be deserialized to the desired type. */ T toClass(Class type) throws IllegalArgumentException; } /** * Provides for both configuring the endpoint (before it is started), and introspecting the configuration. */ interface EndpointConfig extends MatsConfig { /** * @return the endpointId if this {@link MatsEndpoint}. */ String getEndpointId(); /** * @return whether this Endpoint is "subscription based", as when created with * {@link MatsFactory#subscriptionTerminator(String, Class, Class, ProcessTerminatorLambda)}. */ boolean isSubscription(); /** * @return the class that will be sent as reply for this endpoint. */ Class getReplyClass(); /** * @return the class used for the endpoint's state. */ Class getStateClass(); /** * @return the class expected for incoming messages to this endpoint (decided by the first {@link MatsStage}). */ Class getIncomingClass(); /** * Sets the origin for this Endpoint, i.e. where it was created. Use this to set something sane if the * {@link #getOrigin() automatically created creation info} is useless, as it is when Mats' SpringConfig defines * the endpoints (thus it employs this method to set something more informative). It should be a single line, no * line feeds, but tooling might split the String into multiple lines on the character ';'. It should definitely * be short, but informative. */ EndpointConfig setOrigin(String info); /** * @return some human-interpretable information about where in the codebase this Endpoint was created. If this * is displayed in a multi-line capable situation, you should split on ';'. An attempt at some automatic * creation is performed, based on instantiating an exception and introspecting the result. If this * doesn't yield any good result, it can be overridden by {@link #setOrigin(String)}, as is done by * Mats' SpringConfig. */ String getOrigin(); // Overridden to return the more specific EndpointConfig instead of MatsConfig @Override EndpointConfig setAttribute(String key, Object value); // Overridden to return the more specific EndpointConfig instead of MatsConfig @Override EndpointConfig setConcurrency(int concurrency); // Overridden to return the more specific EndpointConfig instead of MatsConfig @Override EndpointConfig setInteractiveConcurrency(int concurrency); } /** * The part of {@link ProcessContext} that exposes the "getter" side of the context, which enables it to be exposed * outside of the process lambda. It is effectively the "passive" parts of the context, i.e. not initiating new * messages, setting properties etc. Look for usage in the "MatsFuturizer" tool in the tools-lib. */ interface DetachedProcessContext { /** * @return the {@link MatsInitiate#traceId(CharSequence) trace id} for the processed message. * @see MatsInitiate#traceId(CharSequence) */ String getTraceId(); /** * @return the endpointId that is processed, i.e. the id of this endpoint. Should probably never be * necessary, but accessible for introspection. */ String getEndpointId(); /** * @return the stageId that is processed, i.e. the id of this stage. It will be equal to * {@link #getEndpointId()} for the first stage in multi-stage-endpoint, and for the sole stage of a * single-stage and terminator endpoint. Should probably never be necessary, but accessible for * introspection. */ String getStageId(); /** * @return the {@link FactoryConfig#getAppName() AppName} of the MatsFactory from which the currently processing * message came. Thus, if this message is the result of a 'next' call, it will be yourself. */ String getFromAppName(); /** * @return the {@link FactoryConfig#getAppVersion() AppVersion} of the MatsFactory from which the currently * processing message came. Thus, if this message is the result of a 'next' call, it will be yourself. */ String getFromAppVersion(); /** * @return the stageId from which the currently processing message came. Note that the stageId of the initial * stage of an endpoint is equal to the endpointId. If this endpoint is the initial target of the * initiation, this value is equal to {@link #getInitiatorId()}. */ String getFromStageId(); /** * @return the {@link Instant} which this message was created on the sending stage. */ Instant getFromTimestamp(); /** * @return the {@link FactoryConfig#getAppName() AppName} of the MatsFactory that initiated the Flow which the * currently processing is a part of. Thus, if this endpoint is the initial target of the initiation, * this value is equal to {@link #getFromAppName()}. */ String getInitiatingAppName(); /** * @return the {@link FactoryConfig#getAppVersion() AppVersion} of the MatsFactory that initiated the Flow which * the currently processing is a part of. Thus, if this endpoint is the initial target of the * initiation, this value is equal to {@link #getFromAppVersion()}. */ String getInitiatingAppVersion(); /** * @return the "initiatorId" set by the initiation with {@link MatsInitiate#from(String)}. */ String getInitiatorId(); /** * @return the {@link Instant} which this message was initiated, i.e. sent from a MatsInitiator (or within a * Stage). */ Instant getInitiatingTimestamp(); /** * @return the unique messageId for the incoming message from Mats - which can be used to catch * double-deliveries. */ String getMatsMessageId(); /** * @return the unique messageId for the incoming message, from the underlying message system - which could be * used to catch double-deliveries, but do prefer {@link #getMatsMessageId()}. (For a JMS * Implementation, this will be the "JMSMessageID"). */ String getSystemMessageId(); /** * This is relevant if stashing or otherwise when a stage is accessing an external system (e.g. another MQ) * which have a notion of persistence. * * @return whether the current Mats flow is non-persistent - read {@link MatsInitiate#nonPersistent()}. */ boolean isNonPersistent(); /** * This is relevant if stashing or otherwise when a stage is accessing an external system (e.g. another MQ) * which have a notion of prioritization. * * @return whether the current Mats flow is interactive (prioritized) - read {@link MatsInitiate#interactive()}. */ boolean isInteractive(); /** * Hint to monitoring/logging/auditing systems that this call flow is not very valuable to fully audit, * typically because it is just a "getter" of information for display to a user, or is health check request to * see if the endpoint is up and answers in a timely manner. * * @return whether the current Mats flow is "no audit" - read {@link MatsInitiate#noAudit()}. */ boolean isNoAudit(); /** * @return the keys on which {@link #getBytes(String) bytes} lives. */ Set getBytesKeys(); /** * Get binary "sideloads" from the incoming message. * * @param key * the key for which to retrieve a binary payload from the incoming message. * @return the requested byte array. * * @see #getBytesKeys() * @see ProcessContext#addBytes(String, byte[]) * @see #getString(String) * @see #getTraceProperty(String, Class) */ byte[] getBytes(String key); /** * @return the keys on which {@link #getString(String) strings} lives. */ Set getStringKeys(); /** * Get String "sideloads" from the incoming message. * * @param key * the key for which to retrieve a String payload from the incoming message. * @return the requested String. * * @see #getStringKeys() * @see ProcessContext#addString(String, String) * @see #getBytes(String) * @see #getTraceProperty(String, Class) */ String getString(String key); /** * Retrieves the Mats Trace property with the specified name, deserializing the value to the specified class, * using the active MATS serializer. Read more on {@link ProcessContext#setTraceProperty(String, Object)}. * * @param propertyName * the name of the Mats Trace property to retrieve. * @param clazz * the class to which the value should be deserialized. * @return the value of the Mats Trace property, deserialized as the specified class. * @see ProcessContext#setTraceProperty(String, Object) */ T getTraceProperty(String propertyName, Class clazz); /** * @return a for-human-consumption, multi-line debug-String representing the current processing context, * typically the "MatsTrace" up to the current stage. The format is utterly arbitrary, can and will * change between versions and revisions, and shall NOT be used programmatically!! */ String toString(); } /** * A way for the process stage to communicate with the library, providing methods to invoke a request, send a reply * (for multi-stage endpoints, this provides a way to do a "early return"), initiate a new message etc. */ interface ProcessContext extends DetachedProcessContext { /** * Attaches a binary payload ("sideload") to the next outgoing message, being it a request, reply, next or * nextDirect. Note that for initiations, you have the same method on the {@link MatsInitiate} instance. *

* The rationale for having this is to not have to encode a largish byte array inside the JSON structure that * carries the Request or Reply DTO - byte arrays represent very badly in JSON. *

* Note: The byte array is not compressed (as might happen with the DTO), so if the payload is large, you might * want to consider compressing it before attaching it (and will then have to decompress it on the receiving * side). *

* Note: This will be added to the subsequent {@link #request(String, Object) request}, {@link #reply(Object) * reply} or {@link #next(Object) next} message - and then cleared. Thus, if you perform multiple request or * next calls, then each must have their binaries, strings and trace properties set separately. (Any * {@link #initiate(InitiateLambda) initiations} are separate from this, neither getting nor consuming binaries, * strings nor trace properties set on the ProcessContext - they must be set on the * {@link MatsInitiate MatsInitiate} instance within the initiate-lambda). * * @param key * the key on which to store the byte array payload, null is not allowed. The receiver * will have to use this key to get the payload out again. * @param payload * the payload to store, null is not allowed. * @see #getBytes(String) * @see #addString(String, String) * @see #getString(String) */ void addBytes(String key, byte[] payload); /** * Attaches a String payload ("sideload") to the next outgoing message, being it a request, reply, * next or nextDirect. Note that for initiations, you have the same method on the {@link MatsInitiate} instance. *

* The rationale for having this is to not have to encode a largish string document inside the JSON structure * that carries the Request or Reply DTO. *

* Note: The String payload is not compressed (as might happen with the DTO), so if the payload is large, you * might want to consider compressing it before attaching it and instead use the * {@link #addBytes(String, byte[]) addBytes(..)} method (and will then have to decompress it on the receiving * side). *

* Note: This will be added to the subsequent {@link #request(String, Object) request}, {@link #reply(Object) * reply} or {@link #next(Object) next} message - and then cleared. Thus, if you perform multiple request or * next calls, then each must have their binaries, strings and trace properties set separately. (Any * {@link #initiate(InitiateLambda) initiations} are separate from this, neither getting nor consuming binaries, * strings nor trace properties set on the ProcessContext - they must be set on the * {@link MatsInitiate MatsInitiate} instance within the initiate-lambda). * * @param key * the key on which to store the String payload, null is not allowed. The receiver will * have to use this key to get the payload out again. * @param payload * the payload to store, null is not allowed. * @see #getString(String) * @see #addBytes(String, byte[]) * @see #getBytes(String) */ void addString(String key, String payload); /** * Adds a property that will "stick" with the Mats Trace from this call on out. Note that for initiations, you * have the same method on the {@link MatsInitiate} instance. The functionality effectively acts like a * {@link ThreadLocal} when compared to normal java method invocations: If the Initiator adds it, all subsequent * stages will see it, on any stack level, including the terminator. If a stage in a service nested some levels * down in the stack adds it, it will be present in all subsequent stages including all the way to the * Terminator. Note that any initiations within a Stage will also inherit trace properties present on the * Stage's incoming message. *

* Possible use cases: You can for example "sneak along" some property meant for Service X through an invocation * of intermediate Service A (which subsequently calls Service X), where the signature (DTO) of the intermediate * Service A does not provide such functionality. Another usage would be to add some "global context variable", * e.g. "current user", that is available for any down-stream Service that requires it. Both of these scenarios * can obviously lead to pretty hard-to-understand code if used extensively: When employed, you should code * rather defensively, where if this property is not present when a stage needs it, it should throw * {@link MatsRefuseMessageException} and clearly explain that the property needs to be present. *

* Note: This will be added to the subsequent {@link #request(String, Object) request}, {@link #reply(Object) * reply} or {@link #next(Object) next} message - and then cleared. Thus, if you perform multiple request or * next calls, then each must have their binaries, strings and trace properties set separately. (Any * {@link #initiate(InitiateLambda) initiations} are separate from this, neither getting nor consuming binaries, * strings nor trace properties set on the ProcessContext - they must be set on the * {@link MatsInitiate MatsInitiate} instance within the initiate-lambda). *

* Note: incoming trace properties (that was present on the incoming message) will be added to all * outgoing message, including initiations within the stage. * * @param propertyName * the name of the property * @param propertyValue * the value of the property, which will be serialized using the active MATS serializer. * @see #getTraceProperty(String, Class) * @see #addString(String, String) * @see #addBytes(String, byte[]) */ void setTraceProperty(String propertyName, Object propertyValue); /** * Adds a measurement of a described variable, in a base unit, for this Stage - be sure to understand that * the three String parameters are constants for each measurement. To exemplify, you may measure five * different things in a Stage, i.e. "number of items in order", "total amount for order in dollar", etc - and * each of these obviously have different metricId, metricDescription and possibly different baseUnit from each * other. BUT, the specific arguments for "number of items in order" (outside the measure itself!) shall not * change between one Stage processing and the next: e.g. the metricDescription shall NOT be dynamically * constructed to e.g. say "Number of items in order 1234 for customer 5678". *

* Note: It is illegal to use the same 'metricId' for more than one measurement for a given stage, and this also * goes between measurements and {@link #logTimingMeasurement(String, String, long, String...) timing metrics}. *

* Inclusion as metric by plugin 'mats-intercept-micrometer': A new meter will be created (and cached), * of type DistributionSummary, with the 'name' set to * "mats.exec.ops.measure.{metricId}.{baseUnit}" ("measure"->"time" for timings), and * 'description' to description. (Tags/labels already added on the meter by the plugin include 'appName', * 'initiatorId', 'initiatingAppName', 'stageId' (for stages), and 'initiatorName' (for inits)). Read about * parameter 'labelKeyValue' below. *

* Inclusion as log line by plugin 'mats-intercept-logging': A log line will be output by each added * measurement, where the MDC for that log line will have an entry with MDC-key * "mats.exec.ops.measure.{metricId}.{baseUnit}". Read about parameter 'labelKeyValue' below. *

* It generally makes most sense if the same metrics are added for each processing of a particular Stage, i.e. * if the "number of items" are 0, then that should also be recorded along with the "total amount for order in * dollar" as 0, not just elided. Otherwise, your metrics will be skewed. *

* You should use a dot-notation for the metricId if you want to add multiple meters with a * hierarchical/subdivision layout. *

* The vararg 'labelKeyValue' is an optional element where the String-array consist of one or several alternate * key, value pairs. Do not employ this feature unless you know what the effects are, and only if you * actually need it! This will be added as labels/tags to the metric, and added to the SLF4J MDC for the * measurement log line with the key being "{metricKey}.tag.{labelKey}". The keys should be * constants as explained for the other parameters, while the value can change, but only between a given set of * values (think enum) - using e.g. the 'customerId' as value doesn't make sense and will blow up * your metric cardinality. Notice that if you do employ e.g. two labels, the first employing three values, and * the second employing values, you'll effectively create 12 different meters, where your measurement will go * to one of them. *

* NOTICE: If you want to do a timing, then instead use * {@link #logTimingMeasurement(String, String, long, String...)} * * @param metricId * constant, short, possibly dot-separated if hierarchical, id for this particular metric, e.g. * "items" or "amount", or "db.query.orders". * @param metricDescription * constant, textual description for this metric, e.g. "Number of items in customer order", "Total * amount of customer order" * @param baseUnit * the unit for this measurement, e.g. "quantity" (for a count measure), "dollar" (for an amount), or * "bytes" (for a document size). * @param measure * value of the measurement * @param labelKeyValue * a String-vararg array consisting of alternate key,value pairs which will becomes labels or tags or * entries for the metrics and log lines. Read the JavaDoc above; the keys shall be "static" for a * specific measure, while the values can change between a specific small set values. */ void logMeasurement(String metricId, String metricDescription, String baseUnit, double measure, String... labelKeyValue); /** * Same as {@link #logMeasurement(String, String, String, double, String...) addMeasurement(..)}, but * specifically for timings - Read that JavaDoc!. (The reason for having timings as a specific method, is * that different "output methods" like Micrometer/Prometheus metrics, and slf4j logging with MDC-based * key/value, employ different magnitudes for the time-based measurements). *

* Note: It is illegal to use the same 'metricId' for more than one measurement for a given stage, and this also * goes between timing metrics and {@link #logMeasurement(String, String, String, double, String...) * measurements}. *

* For the metrics-plugin 'mats-intercept-micrometer' plugin, the 'baseUnit' argument is deduced to * whatever is appropriate for the receiving metrics system, e.g. for Prometheus it is seconds, even * though you always record the measurement in nanoseconds using this method. *

* For the logging-plugin 'mats-intercept-logging' plugin, the timing in the log line will be in * milliseconds (with fractions), even though you always record the measurement in nanoseconds using this * method. * * @param metricId * constant, short, possibly dot-separated if hierarchical, id for this particular metric, e.g. * "db.query.orders" or "calcprofit". * @param metricDescription * constant, textual description for this metric, e.g. "Time taken to execute order query", "Time * taken to calculate profit or loss". * @param nanos * time taken in nanoseconds. * @param labelKeyValue * a String-vararg array consisting of alternate key,value pairs which will becomes labels or tags or * entries for the metrics and log lines. Read the JavaDoc at * {@link #logMeasurement(String, String, String, double, String...) addMeasurement(..)} */ void logTimingMeasurement(String metricId, String metricDescription, long nanos, String... labelKeyValue); /** * Returns a binary representation of the current Mats flow's incoming execution point, which can be * {@link MatsInitiate#unstash(byte[], Class, Class, Class, ProcessLambda) unstashed} again at a later time * using the {@link MatsInitiator}, thereby providing a simplistic "continuation" feature in Mats. You will have * to find storage for these bytes yourself - an obvious place is the co-transactional database that the stage * typically has available. This feature gives the ability to "pause" the current Mats flow, and later restore * the execution from where it left off, probably with some new information that have been gathered in the * meantime. This can typically relieve the Mats Stage Processing thread from having to wait for another * service's execution (whose execution must then be handled by some other thread). This could be a longer * running process, or a process whose execution time is variable, maybe residing on a Mats-external service * structure: E.g. some REST service that sometimes lags, or sometimes is down in smaller periods. Or a service * on a different Message Broker. Once this "Mats external" processing has finished, that thread can invoke * {@link MatsInitiate#unstash(byte[], Class, Class, Class, ProcessLambda) unstash(stashBytes,...)} to get the * Mats flow going again. Notice that functionally, the unstash-operation is a kind of initiation, only that * this type of initiation doesn't start a new Mats flow, rather continuing an existing flow. *

* Notice that this feature should not typically be used to "park" a Mats flow for days. One might have a * situation where a part of an order flow potentially needs manual handling, e.g. validating a person's * identity if this has not been validated before. It might (should!) be tempting to employ the stash function * then: Stash the Mats flow in a database. Make a GUI where the ID-validation can be performed by some * employee. When the ID is either accepted or denied, you unstash the Mats flow with the result, getting a very * nice continuous mats flow for new orders which is identical whether or not ID validation needs to be * performed. However, if this ID-validation process can take days or weeks to execute, it will be a poor * candidate for the stash-feature. The reason is that embedded within the execution context which you get a * binary serialization of, there might be several serialized state representations of the endpoints * laying upstream of this call flow. When you "freeze" these by invoking stash, you have immediately made a * potential future deserialization-crash if you change the code of those upstream endpoints (which quite * probably resides in different code bases than the one employing the stash feature), specifically changes of * the state classes they employ: When you deploy these code changes while having multiple flows frozen in * stashes, you will have a problem when they are later unstashed and the Mats flow returns to those endpoints * whose state classes won't deserialize back anymore. It is worth noting that you always have these problems * when doing deploys where the state classes of Mats endpoints change - it is just that usually, there won't be * any, and at least not many, such flows in execution at the precise deploy moment (and also, that changing the * state classes are in practice really not that frequent). However, by stashing over days, instead of a normal * Mats flow that take seconds, you massively increase the time window in which such deserialization problems * can occur. You at least have to consider this if employing the stash-functionality. *

* Note about data and metadata which should be stored along with the stash-bytes: You only get a binary * serialized incoming execution context in return from this method (which includes the incoming message, * incoming state, the execution stack and {@link ProcessContext#getTraceProperty(String, Class) trace * properties}, but not "sideloaded" {@link ProcessContext#getBytes(String) bytes} and * {@link ProcessContext#getString(String) strings}). The returned byte array are utterly opaque seen from the * Mats API side (however, depending on the serialization mechanism employed in the Mats implementation, you * might be able to peek into them anyway - but this should at most be used for debugging/monitoring * introspection). Therefore, any information from the incoming message, or from your state object, or anything * else from the {@link DetachedProcessContext} which is needed to actually execute the job that should be * performed outside of the Mats flow, must be picked out manually before exiting the process lambda. * This also goes for "sideloaded" objects ({@link ProcessContext#getBytes(String) bytes} and * {@link ProcessContext#getString(String) strings}) - which will not be available inside the unstashed process * lambda (they are not a part of the stash-bytes). Also, you should for debugging/monitoring purposes also * store at least the Mats flow's {@link ProcessContext#getTraceId() TraceId} and a timestamp along with the * stash and data. You should probably also have some kind of monitoring / health checks for stashes that have * become stale - i.e. stashes that have not been unstashed for a considerable time, and whose Mats flow have * thus stopped up, and where the downstream endpoints/stages therefore will not get invoked. *

* Notes: *

    *
  • Invoking {@code stash()} will not affect the stage processing in any way other than producing a * serialized representation of the current incoming execution point. You can still send out messages. You could * even reply, but then, what would be the point of stashing?
  • *
  • Repeated invocations within the same stage will yield (effectively) the same stash, as any processing * done inside the stage before invoking {@code stash()} don't affect the incoming execution point.
  • *
  • You will have to exit the current process lambda yourself - meaning that this cannot be used in a * {@link MatsEndpoint#lastStage(Class, ProcessReturnLambda) lastStage}, as you cannot return from such a stage * without actually sending a reply ({@code return null} replies with {@code null}). Instead employ a * {@link MatsEndpoint#stage(Class, ProcessLambda) normal stage}, using {@link ProcessContext#reply(Object)} to * return a reply if needed.
  • *
  • Mats won't care if you unstash() the same stash multiple times, but your downstream parts of the Mats * flow might find this a bit strange.
  • *
* * @return a binary representation of the current Mats flow's incoming execution point (i.e. any incoming state * and the incoming message - along with the Mats flow stack at this point). It shall start with the 4 * ASCII letters "MATS", and then 4 more letters representing which mechanism is employed to construct * the rest of the byte array. */ byte[] stash(); /** * Sends a request message, meaning that the specified endpoint will be invoked, with the reply-to endpointId * set to the next stage in the multi-stage endpoint. *

* This will throw if the current process stage is a terminator, single-stage endpoint or the last endpoint of a * multi-stage endpoint, as there then is no next stage to reply to. *

* Note: Legal outgoing flows: Either one or several {@link #request(String, Object) request} messages; OR a * single {@link #reply(Object) reply}, {@link #next(Object) next}, {@link #nextDirect(Object) nextDirect}, or * {@link #goTo(String, Object) goTo} message. The reason that multiple requests are allowed is that this could * be used in a scatter-gather scenario - where the replies come in to the next stage of the same * endpoint. However, multiple replies to the invoking endpoint makes very little sense, which is why * only one reply is allowed, and it cannot be combined with request or next, nor goto, as then the next stage * could also perform a reply. *

* Note: The current state and DTO is serialized when invoking this method. This means that in case of * multiple requests, you may change the state in between each request, and the next stage will get different * "incoming states" for each of the replies, which may be of use in a scatter-gather scenario. * * @param endpointId * which endpoint to invoke * @param requestDto * the message that should be sent to the specified endpoint. */ MessageReference request(String endpointId, Object requestDto); /** * Sends a reply to the requesting service. When returning an object from a * {@link MatsEndpoint#lastStage(Class, ProcessReturnLambda) lastStage} lambda, this is the method that actually * gets invoked. *

* This will be ignored if there is no endpointId on the stack, i.e. if this endpoint it is semantically a * terminator (the replyTo of an initiation's request), or if it is the last stage of an endpoint * that was invoked directly (using {@link MatsInitiate#send(Object) MatsInitiate.send(msg)}). *

* It is possible to do "early return" in a multi-stage endpoint by invoking this method in a stage that is not * the last. *

* Note: Legal outgoing flows: Either one or several {@link #request(String, Object) request} messages; OR a * single {@link #reply(Object) reply}, {@link #next(Object) next}, {@link #nextDirect(Object) nextDirect}, or * {@link #goTo(String, Object) goTo} message. The reason that multiple requests are allowed is that this could * be used in a scatter-gather scenario - where the replies come in to the next stage of the same * endpoint. However, multiple replies to the invoking endpoint makes very little sense, which is why * only one reply is allowed, and it cannot be combined with request or next, nor goto, as then the next stage * could also perform a reply. *

* Note: The current state and DTO is serialized when invoking this method. Any changes to the state * object or the DTO performed afterwards won't be present on the reply-receiving stage. * * @param replyDto * the reply DTO to return to the invoker. */ MessageReference reply(R replyDto); /** * Sends a message which passes the control to the next stage of a multi-stage endpoint. The functionality is * meant for doing conditional requests, e.g.: * *

         * if (something missing) {
         *     request another endpoint with reply coming to next stage.
         * }
         * else {
         *     pass to next stage
         * }
         * 
* * This can be of utility if an endpoint in some situations requires more information from a collaborating * endpoint, while in other situations it does not require that information. *

* Note: You should rather use {@link #nextDirect(Object)} if your transactional demarcation needs allows * it! *

* Note: Legal outgoing flows: Either one or several {@link #request(String, Object) request} messages; OR a * single {@link #reply(Object) reply}, {@link #next(Object) next}, {@link #nextDirect(Object) nextDirect}, or * {@link #goTo(String, Object) goTo} message. The reason that multiple requests are allowed is that this could * be used in a scatter-gather scenario - where the replies come in to the next stage of the same * endpoint. However, multiple replies to the invoking endpoint makes very little sense, which is why * only one reply is allowed, and it cannot be combined with request or next, nor goto, as then the next stage * could also perform a reply. *

* Note: The current state and DTO is serialized when invoking this method. Any changes to the state * object or the DTO performed afterwards won't be present in the subsequent stage. * * @see #nextDirect(Object) * * @param nextDto * the object for the next stage's incoming DTO, which must match what the next stage expects. When * using this method to skip a request, it probably often makes sense to set it to null, * which the next stage then must handle correctly. */ MessageReference next(Object nextDto); /** * Specialized, less resource demanding, and faster "direct" variant of {@link #next(Object)} which executes the * next stage of a multi-stage endpoint within the same stage processor and transactional demarcation that this * stage is in - that is, there is no actual message sent. This ensures that you neither incur any transactional * cost nor the overhead of serialization and sending a message on the message queue with subsequent receiving * and deserialization. *

* The functionality is meant for doing conditional calls, e.g.: * *

         * if (something missing) {
         *     request another endpoint with reply coming to next stage.
         * }
         * else {
         *     directly execute the next stage
         * }
         * 
* * This can be of utility if an endpoint in some situations requires more information from a collaborating * endpoint, while in other situations it does not require that information. A scenario nextDirect is * particularly good for is lazy cache population where the caching of the information only incurs cost if the * information is missing, since if the information is present in the cache, you can do a close to zero cost * nextDirect invocation. Had you been using {@link #next(Object) ordinary next}, you would incur the cost of * sending and receiving a message even though the information is present in the cache. *

* Implementation details which are unspecified and whose effects or non-effects shall not be relied on: *

    *
  • It is unspecified which thread runs the next stage: The invocation may be done by the same stage * processor thread which executes this stage, or the execution may be done by another thread. Therefore, you * cannot expect any ThreadLocals set in this lambda to to be present in the next.
  • *
  • It is unspecified how the next lambda is invoked: You cannot expect this to behave like a method call to * the next lambda (i.e. the next stage has been executed when the method returns), nor can you expect the * lambda to be executed only when the current lambda has exited (i.e. behaving like a message). Therefore, * invocation of this method should be the very last operation in the current lambda so that this does not make * a difference.
  • *
*

* Some notes: *

    *
  • There is no new message system message created for a nextDirect, so e.g. messageIds when in the * next stage will refer to the previous stage's incoming message.
  • *
  • Since there is no message system message, the message broker won't see any trace of a nextDirect. This * also means that you'll get less counts of messages on the next stage's queue.
  • *
  • The stage processing thread for the stage doing nextDirect is busy while the next stage executes, either * by actually executing it, or waiting for the execution, depending on the Mats implementation. If the next * stage is slow, any queue build-up will therefore happen on the stage that performed the nextDirect. Any * adjustment of concurrency must be done on the stage actually receiving message system messages.
  • *
  • It is legal to do "series" of nextDirects, i.e. that one stage does a nextDirect, and then the next stage * also does a nextDirect.
  • *
  • Neither the state object or the DTO is serialized and deserialized, but instead passed directly to the * next stage. This ties back to the point about unspecified way of invoking the next lambda and that the * invocation of nextDirect should be the last operation in the current stage: It is unspecified whether a * change to the state object or DTO after the invocation of nextDirect will be visible to the next * stage. (This as opposed to all the other message sending methods, where it is specified that the objects are * serialized upon method invocation, and any changes done afterwards will not be visible to the receiver.)
  • *
  • If the next stage throws an exception, it will be the current stage that eventually DLQs - this might be * confusing when researching an error.
  • *
  • If employing MatsTrace (which the JMS impl do), it will contain no record of the nextDirect having been * performed. For example, if the message crops up on a DLQ, any nextDirects in the flow will not be * visible.
  • *
  • There are implications for the Interceptors, some of which might be subtle. E.g. any preprocess and * deserialization timings, and message sizes, will be 0. Also, read up on the stageCompleted(..) * vs. stageCompletedNextDirect(..)
  • *
*

* Note: Legal outgoing flows: Either one or several {@link #request(String, Object) request} messages; OR a * single {@link #reply(Object) reply}, {@link #next(Object) next}, {@link #nextDirect(Object) nextDirect}, or * {@link #goTo(String, Object) goTo} message. The reason that multiple requests are allowed is that this could * be used in a scatter-gather scenario - where the replies come in to the next stage of the same * endpoint. However, multiple replies to the invoking endpoint makes very little sense, which is why * only one reply is allowed, and it cannot be combined with request or next, nor goto, as then the next stage * could also perform a reply. */ void nextDirect(Object nextDirectDto); /** * Sends a message which passes the current call stack over to another endpoint, so that when that endpoint * replies, it will return to the endpoint which invoked this endpoint. *

* This would be of use in dispatcher scenarios, where you might have a case-style evaluation of the incoming * message, and then either handle it yourself, or send the control over to some other endpoint (possibly one of * several other endpoints) - so that when they again reply, it will be to the original caller. *

* A specific dispatcher-like situation where this could be of use, is if you have an endpoint that for some * specific (known) entities consumes a particularly large amount of memory. For example, you might have a * specific set of frequent customers which when loaded takes much more memory than a normal customer. If you * get an influx of orders for these customers at the same time, your standard endpoint with a high concurrency * could lead to an out-of-memory situation. A solution here could be to instantiate that same endpoint code at * two different endpointIds - the public one, and a private variant with much lower concurrency. The standard, * public endpoint would at the initial stage evaluate if this was one of the known memory-hogging customers, * and if so, make a context.goto(..) over to the other private endpoint with lower concurrency thereby ensuring * that the memory usage would be contained. (The endpoint would have to evaluate if it is the public or private * instance wrt. whether it should do the eval-then-goto: Either by looking at its endpointId (check if it is * the private, low-concurrency variant), or by use of the {@link #goTo(String, Object, Object) * initialState}-feature, or by modifying the DTO before goTo, or by * {@link ProcessContext#addString(String, String) sideloads}). *

* Another use is tail calls, whereby one endpoint A does some preprocessing, and then invokes another * endpoint B, but where endpoint A really just want to directly reply with the reply from endpoint B. The extra * reply stage of endpoint A is thus totally useless and just incurs additional message passing and processing. * You can instead just goTo endpoint B, which achieves just this outcome: When endpoint B now replies, it * will reply to the caller of endpoint A. *

* Note: Legal outgoing flows: Either one or several {@link #request(String, Object) request} messages; OR a * single {@link #reply(Object) reply}, {@link #next(Object) next}, {@link #nextDirect(Object) nextDirect}, or * {@link #goTo(String, Object) goTo} message. The reason that multiple requests are allowed is that this could * be used in a scatter-gather scenario - where the replies come in to the next stage of the same * endpoint. However, multiple replies to the invoking endpoint makes very little sense, which is why * only one reply is allowed, and it cannot be combined with request or next, nor goto, as then the next stage * could also perform a reply. *

* Note: The current state and DTO is serialized when invoking this method. Any changes to the state * object or the DTO performed afterwards won't be present for the targeted endpoint. * * @param endpointId * which endpoint to go to. * @param gotoDto * the message that should be sent to the specified endpoint. Will in a dispatcher scenario often be * the incoming DTO for the current endpoint, while in a tail call situation be the requestDto for * the target endpoint. */ MessageReference goTo(String endpointId, Object gotoDto); /** * Variation of {@link #goTo(String, Object)} method, where the incoming state is sent along. *

* This only makes sense if the same code base "owns" both the current endpoint, and the endpoint to which * this message is sent. * * @param endpointId * which endpoint to go to. * @param gotoDto * the message that should be sent to the specified endpoint. Will in a dispatcher scenario often be * the incoming DTO for the current endpoint, while in a tail call situation be the requestDto for * the target endpoint. * @param initialTargetSto * the object which the target endpoint will get as its initial stage STO (State Transfer Object). */ MessageReference goTo(String endpointId, Object gotoDto, Object initialTargetSto); /** * Initiates a new message out to an endpoint. This is effectively the same as invoking * {@link MatsInitiator#initiate(InitiateLambda lambda) the same method} on a {@link MatsInitiator} gotten via * {@link MatsFactory#getOrCreateInitiator(String)}, only that this way works within the transactional context * of the {@link MatsStage} which this method is invoked within. Also, the traceId and from-endpointId is * predefined, but it is still recommended to set the traceId, as that will append the new string on the * existing traceId (joined with a "|" character), making log tracking (e.g. when debugging) better. Note: This * prepending will not happen if the first char of the TraceId is a "!" character - which will be removed. *

* IMPORTANT NOTICE!! The {@link MatsInitiator} returned from {@link MatsFactory#getDefaultInitiator() * MatsFactory.getDefaultInitiator()} is "magic" in that when employed from within a Mats Stage's context * (thread), it works exactly as this method: Any initiations performed participates in the Mats Stage's * transactional demarcation. Read more at the JavaDoc of default initiator's * {@link MatsFactory#getDefaultInitiator() JavaDoc}.. * * @param lambda * provides the {@link MatsInitiate} instance on which to create the message to be sent. */ void initiate(InitiateLambda lambda); /** * The Runnable will be performed after messaging and external resources (DB) have been committed. An example * can be if the Mats-lambda inserts a row in a database that should be processed by some other component (i.e. * a service running with some Threads), and thus wants to wake up that component telling it that new work is * available. Problem is then that if this "wakeUp()" call is done within the lambda, the row is not technically * there yet - as we're still within the SQL transaction demarcation. Therefore, if the process-service wakes up * really fast and tries to find the new work, it will not see anything yet. (It might then presume that e.g. * another node of the service-cluster took care of whatever woke it up, and go back to sleep.) *

* Note: This is per processing; Setting it is only relevant for the current message. If you invoke the method * more than once, only the last Runnable will be run. If you set it to null, you "cancel" any * previously set Runnable. *

* Note: If any Exception is raised from the stage lambda code after the Runnable has been set, or any Exception * is raised by the processing or committing, the Runnable will not be run. *

* Note: If the doAfterCommit Runnable throws a {@link RuntimeException}, it will be logged on * ERROR level, then ignored. * * @param runnable * the code to run right after the transaction of both external resources and messaging has been * committed. Setting to null "cancels" any previously set Runnable. */ void doAfterCommit(Runnable runnable); /** * Provides a way to get hold of (optional) attributes/objects from the Mats implementation, either specific to * the Mats implementation in use, or configured into this instance of the Mats implementation. Is mirrored by * the same method at {@link MatsInitiate#getAttribute(Class, String...)}. There is also a * ThreadLocal-accessible version at {@link ContextLocal#getAttribute(Class, String...)}. *

* Mandatory: If the Mats implementation has a transactional SQL Connection, it shall be available by * 'context.getAttribute(Connection.class)'. * * @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, the optionality pointing out that it depends on the Mats * implementation or configuration whether it is available. * * @see ProcessContext#getAttribute(Class, String...) * @see ContextLocal#getAttribute(Class, String...) */ Optional getAttribute(Class type, String... name); /** * @return default this for implementations, overridden by wrappers. */ default ProcessContext unwrapFully() { return this; } } /** * Can be thrown by the {@link ProcessLambda} of the {@link MatsStage}s to denote that it would prefer this message * to be instantly put on a Dead Letter Queue (the stage processing, including any database actions, will * still be rolled back as with any other exception thrown out of a ProcessLambda). This is just advisory - the * message might still be presented a number of times to the {@link MatsStage} in question even though the stage * throws MatsRefuseMessageException every time. */ class MatsRefuseMessageException extends Exception { public MatsRefuseMessageException(String message, Throwable cause) { super(message, cause); } public MatsRefuseMessageException(String message) { super(message); } } /** * A base Wrapper for {@link ProcessContext}, which simply implements ProcessContext, takes a ProcessContext * instance and forwards all calls to that. */ class ProcessContextWrapper implements MatsWrapper>, ProcessContext { /** * 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 ProcessContext instance, then override * {@link #unwrap()}. */ private ProcessContext _targetProcessContext; /** * Standard constructor, taking the wrapped {@link ProcessContext} instance. * * @param targetProcessContext * the {@link ProcessContext} instance which {@link #unwrap()} will return (and hence all forwarded * methods will use). */ public ProcessContextWrapper(ProcessContext targetProcessContext) { setWrappee(targetProcessContext); } /** * No-args constructor, which implies that you either need to invoke {@link #setWrappee(ProcessContext)} before * publishing the instance (making it available for other threads), or override {@link #unwrap()} to provide the * desired {@link ProcessContext} 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 ProcessContextWrapper() { /* no-op */ } /** * Sets the wrapped {@link ProcessContext}, 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 targetProcessContext * the {@link ProcessContext} which is returned by {@link #unwrap()}, unless that is overridden. */ public void setWrappee(ProcessContext targetProcessContext) { _targetProcessContext = targetProcessContext; } /** * @return the wrapped {@link ProcessContext}. All forwarding methods invokes this method to get the wrapped * {@link ProcessContext}, thus if you want to get creative wrt. how and when the ProcessContext is * decided, you can override this method. */ public ProcessContext unwrap() { if (_targetProcessContext == null) { throw new IllegalStateException("MatsEndpoint.ProcessContextWrapper.unwrap():" + " The '_targetProcessContext' is not set!"); } return _targetProcessContext; } @Override public ProcessContext unwrapFully() { return unwrap().unwrapFully(); } @Override public String getTraceId() { return unwrap().getTraceId(); } @Override public String getEndpointId() { return unwrap().getEndpointId(); } @Override public String getStageId() { return unwrap().getStageId(); } @Override public String getFromAppName() { return unwrap().getFromAppName(); } @Override public String getFromAppVersion() { return unwrap().getFromAppVersion(); } @Override public String getFromStageId() { return unwrap().getFromStageId(); } @Override public Instant getFromTimestamp() { return unwrap().getFromTimestamp(); } @Override public String getInitiatingAppName() { return unwrap().getInitiatingAppName(); } @Override public String getInitiatingAppVersion() { return unwrap().getInitiatingAppVersion(); } @Override public String getInitiatorId() { return unwrap().getInitiatorId(); } @Override public Instant getInitiatingTimestamp() { return unwrap().getInitiatingTimestamp(); } @Override public String getMatsMessageId() { return unwrap().getMatsMessageId(); } @Override public String getSystemMessageId() { return unwrap().getSystemMessageId(); } @Override public boolean isNonPersistent() { return unwrap().isNonPersistent(); } @Override public boolean isInteractive() { return unwrap().isInteractive(); } @Override public boolean isNoAudit() { return unwrap().isNoAudit(); } @Override public Set getBytesKeys() { return unwrap().getBytesKeys(); } @Override public byte[] getBytes(String key) { return unwrap().getBytes(key); } @Override public Set getStringKeys() { return unwrap().getStringKeys(); } @Override public String getString(String key) { return unwrap().getString(key); } @Override public T getTraceProperty(String propertyName, Class clazz) { return unwrap().getTraceProperty(propertyName, clazz); } @Override public void addBytes(String key, byte[] payload) { unwrap().addBytes(key, payload); } @Override public void addString(String key, String payload) { unwrap().addString(key, payload); } @Override public void setTraceProperty(String propertyName, Object propertyValue) { unwrap().setTraceProperty(propertyName, propertyValue); } @Override public void logMeasurement(String metricId, String metricDescription, String baseUnit, double measure, String... labelKeyValue) { unwrap().logMeasurement(metricId, metricDescription, baseUnit, measure, labelKeyValue); } @Override public void logTimingMeasurement(String metricId, String metricDescription, long nanos, String... labelKeyValue) { unwrap().logTimingMeasurement(metricId, metricDescription, nanos, labelKeyValue); } @Override public byte[] stash() { return unwrap().stash(); } @Override public MessageReference request(String endpointId, Object requestDto) { return unwrap().request(endpointId, requestDto); } @Override public MessageReference reply(R replyDto) { return unwrap().reply(replyDto); } @Override public MessageReference next(Object nextDto) { return unwrap().next(nextDto); } @Override public void nextDirect(Object nextDirectDto) { unwrap().nextDirect(nextDirectDto); } @Override public MessageReference goTo(String endpointId, Object gotoDto) { return unwrap().goTo(endpointId, gotoDto); } @Override public MessageReference goTo(String endpointId, Object gotoDto, Object initialTargetSto) { return unwrap().goTo(endpointId, gotoDto, initialTargetSto); } @Override public void initiate(InitiateLambda lambda) { unwrap().initiate(lambda); } @Override public void doAfterCommit(Runnable runnable) { unwrap().doAfterCommit(runnable); } @Override public Optional getAttribute(Class type, String... name) { return unwrap().getAttribute(type, name); } @Override public String toString() { return "ProcessContextWrapper[" + this.getClass().getSimpleName() + "]:" + unwrap().toString(); } } }