Skip to main content

Enum keys in TransactionManager contracts

· 5 min read
Alejandro Revilla
jPOS project founder

TransactionManager context contracts are a small feature with a large documentation payoff.

When a participant declares:

<requires>REQUEST</requires>
<provides>RESPONSE</provides>

the TransactionManager can do two useful things. First, it can abort early if a required input is missing. Second, when requires or optional is present, it can call the participant with a restricted clone of the Context and later merge back only the keys listed in provides.

That makes the TM configuration more than a list of Java classes. It becomes a readable contract for the transaction flow: this participant needs these inputs, may look at these optional values, and is expected to produce these outputs.

For many applications, string keys are enough. A participant stores AUTH_USER, another stores AUTH_PERMISSIONS, and the XML contract maps cleanly to the Context.

But some jPOS code uses typed keys. qREST, for example, stores request state under constants such as:

org.jpos.qrest.Constants.REQUEST
org.jpos.qrest.Constants.SESSION
org.jpos.qrest.Constants.RESPONSE

Those are enum constants, not strings.

The bug hiding in plain sight

Before this change, TransactionManager parsed every contract entry as a string.

That means this XML:

<requires>REQUEST,SESSION</requires>

did not mean "the qREST enum constants named REQUEST and SESSION." It meant "the string keys REQUEST and SESSION."

If the real Context contains org.jpos.qrest.Constants.REQUEST, a restricted clone looking for the string "REQUEST" will not include it. The participant then receives a Context that looks empty from its point of view.

Without restricted contexts, the participant still sees the full Context and works. With restricted contexts, the mismatch becomes visible immediately.

That is exactly the sort of bug a contract is supposed to expose. The problem was not the idea of contracts. The problem was that XML had no way to say "this key is not a string."

Why not change qREST?

One possible fix would have been to change qREST to store everything under enum.name() strings.

That looks attractive for about five minutes. It makes the XML shorter, and it avoids adding syntax to TransactionManager.

It is also the wrong layer.

qREST already has code that consistently uses enum keys:

ctx.get(Constants.REQUEST)
ctx.put(Constants.RESPONSE, response)

Changing those keys to strings would ripple through qREST and through any code that integrates with it using the same constants. It would turn a configuration limitation into a source compatibility issue.

The Context itself already accepts object keys. TransactionManager already calls Context methods using object arrays internally. The missing piece was simply the XML parser: it knew how to parse strings, but not how to name typed keys.

The new syntax

TransactionManager contracts now support explicit enum keys:

<participant class="org.example.MyParticipant">
<requires>enum:org.jpos.qrest.Constants.REQUEST,AUTH_USER</requires>
<optional>enum:org.jpos.qrest.Constants.SESSION</optional>
<provides>enum:org.jpos.qrest.Constants.RESPONSE</provides>
</participant>

The rule is deliberately boring:

  • a bare token is still a string key
  • an enum: token is resolved as a fully qualified enum constant
  • there is no magic lookup for bare names
  • there is no support for arbitrary static fields

So AUTH_USER remains the string key "AUTH_USER", while enum:org.jpos.qrest.Constants.REQUEST becomes the actual org.jpos.qrest.Constants.REQUEST enum constant.

The verbosity is intentional. If a Context key is not a string, the XML should make that fact obvious.

Mixed contracts

Mixed string and enum contracts are supported. This is useful in real applications where a framework participant consumes qREST state but application code adds its own string-keyed enrichment:

<participant class="com.acme.AuthzParticipant">
<requires>
enum:org.jpos.qrest.Constants.REQUEST,
AUTH_USER
</requires>
<optional>
enum:org.jpos.qrest.Constants.SESSION
</optional>
<provides>
enum:org.jpos.qrest.Constants.RESPONSE,
AUTH_PERMISSIONS
</provides>
</participant>

The participant receives a restricted Context containing the enum-keyed qREST request, the string-keyed AUTH_USER, and the optional qREST session if it was present. When it returns, only the qREST response and AUTH_PERMISSIONS are merged back.

That is the same restricted-context model as before, just with a way to name the keys accurately.

Backward compatibility

Existing configurations do not change.

This still means string keys:

<requires>REQUEST,AUTH_USER</requires>
<provides>RESPONSE,AUTH_PERMISSIONS</provides>

That matters because many jPOS applications have used string Context keys for a long time, and those keys are often part of local conventions.

The new syntax is opt-in. Use it only when the participant really uses enum keys in the Context.

A small Context cleanup

There was one more detail. Some Context helper methods accepted Object... parameters, but treated non-string keys too much like strings internally.

For enum contracts to work correctly, the restricted-context path has to preserve object identity:

ctx.put(Constants.REQUEST, request);
ctx.clone(Constants.REQUEST);

The clone must contain Constants.REQUEST as an enum key, not "REQUEST" as a string key.

The updated implementation keeps that object-key behavior while preserving the old string fallback behavior expected by existing code.

Why this matters

TransactionManager configurations are often the best map of a payment application. They show the flow, the participants, the decision points, and the operational shape of a transaction.

Contracts make that map sharper.

With enum-key support, contracts can now describe flows that mix application string keys and framework typed keys without weakening either side. qREST can keep using typed constants. Applications can keep using their existing string keys. TransactionManager can enforce and document both.

The result is a small syntax addition:

enum:fully.qualified.Enum.CONSTANT

and a more accurate contract between XML configuration and the Context that participants actually use.