TransactionManager is one of the most important features of jPOS. It is used to implement transaction flows using TransactionParticipants. Usually the participants are organized in groups and GroupSelector interface is used to implement decision points in the transaction flow. TransactionManager calls each participant in the order they appear in the deployment configuration xml file. When it encounters a participant that implements GroupSelector interface, its select()
method is called which should return the names of the groups to be called next. TransactionManager then recursively calls participants in selected group(s).
This calling of participants is done twice. First, the prepare()
method is called during the PREPARE phase. If all the participants are PREPARED, the commit()
method of the participants is called in the COMMIT phase. A participant can abstain from being called during COMMIT by returning PREPARED | NO_JOIN
from its prepare()
method. For GroupSelectors, the prepare()
method is called first and then the select()
method is called.
If any of the participant returns ABORTED
from its prepare()
method, the transaction flow changes. From this point onwards, the prepareForAbort()
method is called for those remaining participants that implement the AbortParticipant interface.
Interestingly, if the participant that first aborts or any further participants happen to implement GroupSelector then their select()
method is called based on the new call-selector-on-abort
boolean property of the TransactionManager. Before this property was introduced, the TransactionManager would always call the select()
method of such GroupSelectors. Therefore the default value of call-selector-for-abort
property is true
to ensure backward compatibility.
Lets see an example. Suppose we have a TransactionManager configuration like below:
<txnmgr class="org.jpos.transaction.TransactionManager" logger="Q2"> <property name="queue" value="myTxnQueue" /> <property name="sessions" value="2" /> <property name="max-active-sessions" value="15" /> <property name="debug" value="true" /> .... <participant class="com.my.company.Participant1" logger="Q2" /> <participant class="com.my.company.Participant2" logger="Q2" /> <participant class="com.my.company.Selector1" logger="Q2"> <property name="12" value="group1 group2"/> <property name="34" value="group3 group4"/> </participant> <group name="group1"> <participant class="com.my.company.Participant11" logger="Q2" /> <participant class="com.my.company.Participant12" logger="Q2" /> <participant class="com.my.company.Selector2" logger="Q2"> <property name="ab" value="groupA"/> <property name="cd" value="groupC groupD"/> </participant> </group> <group name="groupA"> <participant class="com.my.company.Participant11A" logger="Q2" /> <participant class="com.my.company.Participant11B" logger="Q2" /> </group> <participant class="com.my.company.Participant3" logger="Q2" /> .... </txnmgr>
Let us assume that:
- Selector1 and Selector2 implement
GroupSelector
interface - Selector1 will return “12″
- Selector2 will return “ab”
- Participant12, Participant11A, Participant3 implement
AbortParticipant
interface - Participant1′s prepare method returns PREPARED | NO_JOIN
- Participant2′s prepare method returns PREPARED
- Selector1′s prepare() method returns ABORTED
Now, since the call-selector-on-abort
parameter is not defined, it defaults to true
and the transaction will be processed by the TransactionManager as below:
prepare: com.my.company.Participant1 NO_JOIN prepare: com.my.company.Participant2 prepare: com.my.company.Selector1 ABORTED selector: com.my.company.Selector1 group1 group2 prepareForAbort: com.my.company.Participant12 selector: com.my.company.Selector2 groupA groupB prepareForAbort: com.my.company.Participant11A prepareForAbort: com.my.company.Participant3 abort: com.my.company.Participant2 abort: com.my.company.Selector1 abort: com.my.company.Participant12 abort: com.my.company.Participant11A abort: com.my.company.Participant3 ....
Now if we set the call-selector-on-abort
property to false
…
<txnmgr class="org.jpos.transaction.TransactionManager" logger="Q2"> <property name="queue" value="myTxnQueue" /> <property name="sessions" value="2" /> <property name="max-active-sessions" value="15" /> <property name="debug" value="true" /> <property name="call-selector-on-abort" value="false" /> ....
With that the TransactionManager would behave something like this:
prepare: com.my.company.Participant1 NO_JOIN prepare: com.my.company.Participant2 prepare: com.my.company.Selector1 ABORTED prepareForAbort: com.my.company.Participant3 abort: com.my.company.Participant2 abort: com.my.company.Selector1 abort: com.my.company.Participant3 ....
As one can see, call-selector-for-abort
property significantly affects the transaction flow when the transaction aborts. If no participant aborts, this property does not come into picture at all.