Class AbstractSagaManager<T>

java.lang.Object
org.axonframework.modelling.saga.AbstractSagaManager<T>
All Implemented Interfaces:
DescribableComponent, MessageHandler, EventHandler, EventHandlingComponent, ReplayStatusChangedHandler, ResetHandler, ScopeAware
Direct Known Subclasses:
AnnotatedSagaManager

public abstract class AbstractSagaManager<T> extends Object implements EventHandlingComponent, ScopeAware
Abstract implementation of the SagaManager interface that provides basic functionality required by most SagaManager implementations. Provides support for Saga lifecycle management and asynchronous handling of events.
Since:
0.7
Author:
Allard Buijze
  • Constructor Details

  • Method Details

    • handle

      public MessageStream.Empty<Message> handle(EventMessage rawEvent, ProcessingContext context)
      Handles the given event within the given context.

      The result of handling is an empty stream.

      As an EventHandlingComponent, this method does not receive the Segment to filter Saga instances with directly. Instead, the EventProcessor attaches the Segment it is currently processing to the given context as a resource, retrievable through Segment.fromContext(org.axonframework.messaging.core.Context). When absent, for example when this method is invoked outside a segmented EventProcessor, the Segment.ROOT_SEGMENT is assumed, matching every Saga instance.

      Two distinct questions decide what happens here. canHandle(EventMessage, ProcessingContext) asks whether the Saga type declares a handler for this event at all, and Saga.canHandle(EventMessage, ProcessingContext) asks whether an individual instance holds the AssociationValue its handler resolves from the event. Only the second decides whether a Saga counts as having taken the event, which is what a SagaCreationPolicy.IF_NONE_FOUND policy consults before starting a new instance.

      Specified by:
      handle in interface EventHandler
      Parameters:
      rawEvent - The event to handle.
      context - The context to the given event is handled in.
      Returns:
      An empty stream containing nothing.
    • createSagaIdentifier

      protected String createSagaIdentifier(Segment segment)
      Creates a Saga identifier that will cause a Saga instance to be considered part of the given segment.
      Parameters:
      segment - The segment the identifier must match with
      Returns:
      an identifier for a newly created Saga
      Implementation Requirements:
      This implementation will repeatedly generate identifier using the IdentifierFactory, until one is returned that matches the given segment. See matchesSegment(Segment, String).
    • matchesSegment

      protected boolean matchesSegment(Segment segment, String sagaId)
      Checks whether the given sagaId matches with the given segment.

      For any complete set of segments, exactly one segment matches with any value.

      Parameters:
      segment - The segment to validate the identifier for
      sagaId - The identifier to test
      Returns:
      true if the identifier matches the segment, otherwise false
      Implementation Requirements:
      This implementation uses the Segment.matches(Object) to match against the Saga identifier
    • getSagaCreationPolicy

      protected abstract SagaInitializationPolicy getSagaCreationPolicy(EventMessage event, ProcessingContext context)
      Returns the Saga Initialization Policy for a Saga of the given sagaType and event. This policy provides the conditions to create new Saga instance, as well as the initial association of that saga.
      Parameters:
      event - The Event that is being dispatched to Saga instances.
      context - The ProcessingContext in which the event is being processed.
      Returns:
      The initialization policy for the Saga.
    • extractAssociationValues

      protected abstract Set<AssociationValue> extractAssociationValues(EventMessage event, ProcessingContext context)
      Extracts the AssociationValues from the given event as relevant for a Saga of given sagaType. A single event may be associated with multiple values.
      Parameters:
      event - The event containing the association information.
      context - The ProcessingContext in which the event is being processed.
      Returns:
      The AssociationValues indicating which Sagas should handle given event.
    • withHandlerPayload

      protected abstract EventMessage withHandlerPayload(EventMessage event, ProcessingContext context)
      Returns the given event with its payload converted to the type the Saga's handler declares for it.

      Events read from an event store carry a serialized payload. Handler matching and association resolution work on the payload's runtime type, so without this step every such event is ignored. Events whose payload already has the handler's type, and events no handler is declared for, are returned unchanged.

      Parameters:
      event - the event to convert
      context - the ProcessingContext providing the EventConverter
      Returns:
      the event with a converted payload, or the given event
    • canHandle

      protected abstract boolean canHandle(EventMessage event, ProcessingContext context)
      Indicates whether a Saga of the given sagaType has a handler for the given event.

      This check is independent of the Message.type(): Sagas resolve their handlers by inspecting the payload's runtime type, the same way a @SagaEventHandler method is matched, since the message name carried by a MessageType is not guaranteed to correlate with the payload's class. That makes it stricter than EventHandlingComponent.supports(QualifiedName), which the EventProcessor consults before it delivers an event, so this method is asked again on the way in.

      This is a question about the Saga type, not about any one instance. For the instance-level question, see Saga.canHandle(EventMessage, ProcessingContext).

      Parameters:
      event - The event to check for a handler.
      context - The ProcessingContext in which the event is being processed.
      Returns:
      true if a Saga of the given sagaType has a handler for the given event, false otherwise.
    • supportedEvents

      public abstract Set<QualifiedName> supportedEvents()
      Description copied from interface: EventHandlingComponent
      All supported events, referenced through a QualifiedName.
      Specified by:
      supportedEvents in interface EventHandlingComponent
      Returns:
      All supported events, referenced through a QualifiedName.
    • getSagaType

      public Class<T> getSagaType()
      Returns the class of Saga managed by this SagaManager
      Returns:
      the managed saga type
    • sequenceIdentifierFor

      public Object sequenceIdentifierFor(EventMessage event, ProcessingContext context)
      Returns the sequence identifier for the given event. When two events have the same sequence identifier (as defined by their equals method), they will be executed sequentially. Important: All EventHandlers for the same QualifiedName within a single EventHandlingComponent must return the same sequence identifier for a given event. Mixing different sequence identifiers within the scope of a single EventHandlingComponent is not supported and may lead to undefined behavior.

      Always the SequencingPolicy.BROADCAST sentinel, so every Segment of an EventProcessor is offered every event this Saga type handles. A Saga manager is never segment-routed by the event: which Segment owns a Saga follows from the Saga's identifier, which is only known after the association lookup in handle(EventMessage, ProcessingContext), and that lookup needs a SagaRepository and a unit of work that do not exist while an event is merely being scheduled. Axon Framework 4 reached the same arrangement from the other side, by answering its own admission check without consulting the Segment at all.

      Deriving the identifier from the event instead would route each event to a single Segment, and that Segment need not be the one owning the Saga. A Saga that associates with a second value is enough to break it: the follow-up event carrying that value hashes elsewhere, the owning Segment is never offered it, and the Segment that is offered it filters the Saga out again by identifier. The event is dropped with nothing logged.

      Broadcasting does not mean a Saga handles an event more than once. A TokenStore claim makes a Segment live on exactly one node, matchesSegment(Segment, String) lets only the Segment owning the Saga identifier invoke it, and creation is claimed by the single Segment matching the initial AssociationValue. Two limits are inherited from Axon Framework 4 rather than introduced here: a subscribing EventProcessor has no segments and no token store, so every node falls back to Segment.ROOT_SEGMENT and starts its own Saga; and a

      invalid reference
      SagaStore
      is keyed on the Saga identifier alone, with no uniqueness constraint over association values, so segment ownership is the only guard rather than a last line of defence at the storage layer.
      Specified by:
      sequenceIdentifierFor in interface EventHandlingComponent
      Parameters:
      event - The event for which to get the sequencing identifier.
      context - The processing context in which the event is being handled.
      Returns:
      A sequence identifier for the given event.
    • supportsReset

      public boolean supportsReset()
      Indicates whether this component supports reset operations.

      When true, this component can participate in replay operations and its EventHandlingComponent.handle(ResetContext, ProcessingContext) method will be called before replay begins.

      By default, reset is supported.

      Always false. Replaying events into Sagas would rerun their side effects, which is why Axon Framework 4 refused a reset as well.

      Specified by:
      supportsReset in interface EventHandlingComponent
      Returns:
      true if this component supports reset operations, false otherwise.
    • handle

      public MessageStream.Empty<Message> handle(ResetContext resetContext, ProcessingContext context)
      Handles the given ResetContext message, performing any necessary reset operations.

      This method is invoked before the processor begins replaying events. Handlers typically use this opportunity to clean up state that will be rebuilt during replay.

      If this method completes exceptionally, the reset operation will be aborted and no replay will occur.

      Throws a ResetNotSupportedException rather than returning a failed MessageStream, which the declared return type would otherwise invite. That is deliberate: an EventProcessor gates both its reset handling and its resetTokens call on supportsReset(), which is false here, so this method is unreachable through one and only a direct caller can arrive at it. A caller reaching past the gate has made a programming error, and Axon Framework 4 threw for it too.

      Specified by:
      handle in interface EventHandlingComponent
      Specified by:
      handle in interface ResetHandler
      Parameters:
      resetContext - The reset context message containing reset information and optional payload.
      context - The processing context for this operation.
      Returns:
      An empty message stream after handling completes successfully.
      Throws:
      ResetNotSupportedException - always, since Sagas cannot replay their side effects
    • send

      public void send(Message message, ProcessingContext context, ScopeDescriptor scopeDescription) throws Exception
      Send a Message to a Scope which is described by the given scopeDescription.

      A deadline message whose stored payload type resolves to no class is sent with an UnknownDeadlinePayload. Its delivery logs a warning naming the deadline, the stored type and the scope, so that such a deadline is not lost unnoticed.

      Specified by:
      send in interface ScopeAware
      Parameters:
      message - The Message to be sent to a Scope.
      context - The ProcessingContext in which the message is being processed.
      scopeDescription - A D extending ScopeDescriptor, describing the Scope to send the given message to.
      Throws:
      Exception - if sending the message failed. Might occur if the message handling process throws an exception.
    • canResolve

      public boolean canResolve(ScopeDescriptor scopeDescription)
      Description copied from interface: ScopeAware
      Check whether this implementation can resolve a Scope object based on the provided scopeDescription. Will return true in case it should be able to resolve the Scope and false if it cannot.
      Specified by:
      canResolve in interface ScopeAware
      Parameters:
      scopeDescription - a ScopeDescriptor describing the Scope to be resolved
      Returns:
      true in case it should be able to resolve the Scope and false if it cannot
    • describeTo

      public void describeTo(ComponentDescriptor descriptor)
      Description copied from interface: DescribableComponent
      Describe the properties of this DescribableComponent with the given descriptor.

      Components should call the appropriate describeProperty methods on the descriptor to register their properties. The descriptor is responsible for determining how these properties are formatted and structured in the final output.

      Best Practices: As a general rule, all relevant fields of a DescribableComponent implementation should be described in this method. However, developers have discretion to include only the fields that make sense in the context. Not every field may be meaningful for description purposes, especially internal implementation details. Furthermore, components might want to expose different information based on their current state. The final decision on what properties to include lies with the person implementing the describeTo method, who should focus on providing information that is useful for understanding the component's configuration and state.

      Example implementation:

       public void describeTo(ComponentDescriptor descriptor) {
           descriptor.describeProperty("name", this.name);
           descriptor.describeProperty("enabled", this.enabled);
           descriptor.describeProperty("configuration", this.configuration); // A nested component
           descriptor.describeProperty("handlers", this.eventHandlers);      // A collection
       }
       
      Specified by:
      describeTo in interface DescribableComponent
      Parameters:
      descriptor - The component descriptor to describe this DescribableComponentn its properties in.