001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.transaction.participant;
020
021import bsh.EvalError;
022import org.jdom2.Element;
023import org.jpos.core.ConfigurationException;
024import org.jpos.core.XmlConfigurable;
025import org.jpos.q2.QFactory;
026import org.jpos.transaction.AbortParticipant;
027import org.jpos.transaction.TransactionParticipant;
028import org.jpos.util.LogEvent;
029import org.jpos.util.Logger;
030import org.jpos.util.SimpleLogSource;
031
032import java.io.IOException;
033import java.io.Serializable;
034import java.util.HashMap;
035import java.util.Map;
036
037/** A TransactionParticipant whose prepare, commit and abort methods can be 
038 *  specified through beanshell scripts. <BR>
039 *
040 *  To indicate what code to execute for any of the methods just add an element 
041 *  named 'prepare', 'commit' or 'abort' contained in that of the participant. <BR>
042 *
043 *  See BSHMethod for details on the syntax of these elements. The value to return 
044 *  in the prepare method should be stored in the script variable named "result".
045 *  None of these tags are mandatory. <BR>
046 *
047 *  You can subclass BSHTransactionParticipant and override the default... 
048 *  methods. That way you can provide default behaviour for a participant and 
049 *  override it at deploy time through scripts. 
050 * 
051 * @see BSHMethod
052 * @author  AMarques
053 */
054@SuppressWarnings("unchecked")
055public class BSHTransactionParticipant extends SimpleLogSource
056    implements TransactionParticipant, AbortParticipant, XmlConfigurable 
057{
058    
059    /** BeanShell method for the prepare phase. */
060    protected BSHMethod prepareMethod;
061    /** BeanShell method for the prepare-for-abort phase. */
062    protected BSHMethod prepareForAbortMethod;
063    /** BeanShell method for the commit phase. */
064    protected BSHMethod commitMethod;
065    /** BeanShell method for the abort phase. */
066    protected BSHMethod abortMethod;
067
068    /** Whether to log trace events. */
069    boolean trace;
070    
071    /** Creates a new instance of BSHTransactionParticipant */
072    public BSHTransactionParticipant() {
073        super();
074    }
075    
076    public void abort(long id, java.io.Serializable context) {
077        LogEvent ev = new LogEvent(this, "abort");
078        if (abortMethod != null) {
079            try {
080                executeMethod(abortMethod, id, context, ev, "");
081            } catch (Exception ex) {
082                ev.addMessage(ex);
083            }
084        } else {
085            defaultAbort(id, context, ev);
086        }
087        if (trace)
088            Logger.log(ev);
089    }
090    
091    /**
092     * Default abort handling when no BeanShell abort method is configured (no-op).
093     * @param id transaction id
094     * @param context transaction context
095     * @param ev log event
096     */
097    protected void defaultAbort(long id, Serializable context, LogEvent ev) {}
098    
099    public void commit(long id, java.io.Serializable context) {
100        LogEvent ev = new LogEvent(this, "commit");
101        if (commitMethod != null) {
102            try {
103                executeMethod(commitMethod, id, context, ev, "");
104            } catch (Exception ex) {
105                ev.addMessage(ex);
106            }
107        } else {
108            defaultCommit(id, context, ev);
109        }
110        if (trace)
111            Logger.log(ev);
112    }
113    
114    /** Default commit implementation (no-op).
115     * @param id transaction id
116     * @param context transaction context
117     * @param ev log event
118     */
119    protected void defaultCommit(long id, Serializable context, LogEvent ev) {}
120    
121    public int prepare(long id, java.io.Serializable context) {
122        LogEvent ev = new LogEvent(this, "prepare");
123        int result = ABORTED | READONLY;
124        if (prepareMethod != null) {
125            try {
126                result = (Integer) executeMethod(prepareMethod, id, context, ev, "result");
127            } catch (Exception ex) {
128                ev.addMessage(ex);
129            }
130        } else {
131            result = defaultPrepare(id, context, ev);
132        }
133        ev.addMessage("result", Integer.toBinaryString(result));
134        if (trace)
135            Logger.log(ev);
136        return result;
137    }
138
139    /** {@inheritDoc} */
140    public int prepareForAbort(long id, java.io.Serializable context) {
141        LogEvent ev = new LogEvent(this, "prepare-for-abort");
142        int result = ABORTED | READONLY;
143        if (prepareForAbortMethod != null) {
144            try {
145                result = (Integer) executeMethod(prepareForAbortMethod, id, context, ev, "result");
146            } catch (Exception ex) {
147                ev.addMessage(ex);
148            }
149        } 
150        ev.addMessage("result", Integer.toBinaryString(result));
151        if (trace)
152            Logger.log(ev);
153        return result;
154    }
155    
156    /** Default prepare implementation; returns PREPARED|READONLY.
157     * @param id transaction id
158     * @param context transaction context
159     * @param ev log event
160     * @return transaction result code
161     */
162    protected int defaultPrepare(long id, Serializable context, LogEvent ev) {
163        return PREPARED | READONLY;
164    }
165    
166    public void setConfiguration(Element e) throws ConfigurationException {
167        try {
168            prepareMethod = BSHMethod.createBshMethod(e.getChild("prepare"));
169            prepareForAbortMethod = BSHMethod.createBshMethod(e.getChild("prepare-for-abort"));
170            commitMethod = BSHMethod.createBshMethod(e.getChild("commit"));
171            abortMethod = BSHMethod.createBshMethod(e.getChild("abort"));
172            trace = "yes".equals (QFactory.getAttributeValue (e, "trace"));
173        } catch (Exception ex) {
174            throw new ConfigurationException(ex.getMessage(), ex);
175        }
176    }
177    
178    /**
179     * Executes the given BSHMethod with the standard transaction parameters.
180     * @param m the BSHMethod to execute
181     * @param id transaction id
182     * @param context transaction context
183     * @param evt log event
184     * @param resultName the result variable name
185     * @return the value of resultName after execution
186     * @throws bsh.EvalError on BeanShell evaluation error
187     * @throws java.io.IOException if the script cannot be read
188     */
189    protected Object executeMethod(BSHMethod m, long id, Serializable context, LogEvent evt, String resultName) 
190    throws EvalError, IOException {
191        Map params = new HashMap();
192        params.put("context", context);
193        params.put("id", id);
194        params.put("evt", evt);
195        params.put("self", this);
196        return m.execute(params, resultName);
197    }
198}
199