Class Itinerary

java.lang.Object
org.acemq.amqp.core.Itinerary

public final class Itinerary extends Object
An itinerary a message carries with it, naming every stop rather than every step.

The second of the two routing slips this library speaks, and the one for when there is no declaration to resolve against. RoutingSlip writes step names — validate,enrich,dispatch — which a Pipeline turns into exchanges and routing keys because it knows what those steps are. That is the better model when the pipeline is known up front: the wire stays readable in a management console, and a route is one short header rather than a document.

It is no model at all when the itinerary is decided per message and the consumer has never heard of it. This one carries the exchange and the routing key of every stop, so a service that knows nothing but how to read JSON can send the message onwards:


 Itinerary itinerary = Itinerary.empty()
         .then("orders-events", "order.validate", "validate")
         .then("orders-events", "order.charge", "charge")
         .then("orders-events", "order.ship", "ship");
 

The shape on the wire is the one Go, Python and Ruby write, field for field, because those three agreed on it first and a slip is worth nothing if only one library can read it:


 {"steps":[{"exchange":"orders-events","routingKey":"order.charge","name":"charge"}],
  "done":[{"exchange":"orders-events","routingKey":"order.validate","name":"validate",
           "completedAt":"2026-09-09T14:03:11Z"}]}
 

done is what has already happened, oldest first, so a slip that fails halfway says how far it got. It is omitted entirely while it is empty, which is what the Go and Ruby libraries do and what the Python one reads.

Immutable: then(java.lang.String, java.lang.String) and advance() return new instances. The Go and Ruby libraries mutate while a slip is being assembled and copy on advance; this one copies at both ends, because an itinerary held in a field and sent twice must not have been changed by the first send.

  • Field Details

  • Method Details

    • empty

      public static Itinerary empty()
      Returns:
      an itinerary with no stops on it yet
    • of

      public static Itinerary of(Itinerary.Stop... stops)
      Parameters:
      stops - the stops, in order
      Returns:
      an itinerary of those stops, with nothing done yet
    • then

      public Itinerary then(String exchange, String routingKey)
      Adds a stop to the end.
      Parameters:
      exchange - where that stop's message goes, empty for the default exchange
      routingKey - the routing key, which is the queue name when publishing without an exchange
      Returns:
      a new itinerary with the stop appended
    • then

      public Itinerary then(String exchange, String routingKey, String name)
      Parameters:
      exchange - where that stop's message goes
      routingKey - the routing key
      name - what to call it in a log, which need not be anything
      Returns:
      a new itinerary with the stop appended
    • next

      public Optional<Itinerary.Stop> next()
      Returns:
      the stop this message is going to, or empty when the itinerary is finished
    • advance

      public Itinerary advance()
      Returns:
      an itinerary with the first stop moved to done, stamped with the clock
    • isFinished

      public boolean isFinished()
      Returns:
      whether every stop has been made
    • steps

      public List<Itinerary.Stop> steps()
      Returns:
      the stops still to make, in order
    • done

      public List<Itinerary.Stop> done()
      Returns:
      the stops already made, oldest first
    • from

      public static Optional<Itinerary> from(Map<String,Object> headers)
      Reads an itinerary off a message's headers.
      Parameters:
      headers - the headers as the handler was given them
      Returns:
      the itinerary, or empty when the message is not carrying one
      Throws:
      AceFatalException - when the header is there and cannot be read. Fatal rather than retryable, and deliberately so: a slip that will not parse will not parse on the next attempt either, and a message going round the broker while nothing can tell where it is meant to go is the worst of both outcomes.
    • parse

      public static Itinerary parse(String json)
      Parameters:
      json - the itinerary as it travels
      Returns:
      the itinerary
      Throws:
      AceFatalException - when the text is not an itinerary
    • toHeader

      public String toHeader()
      Returns:
      the itinerary as it goes on the wire, to publish under HEADER
    • equals

      public boolean equals(@Nullable Object other)
      Overrides:
      equals in class Object
    • hashCode

      public int hashCode()
      Overrides:
      hashCode in class Object
    • toString

      public String toString()
      Overrides:
      toString in class Object