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 org.jdom2.Element;
022import org.jpos.core.ConfigurationException;
023import org.jpos.core.XmlConfigurable;
024import org.jpos.q2.QFactory;
025import org.jpos.transaction.AbortParticipant;
026import org.jpos.transaction.Context;
027import org.jpos.transaction.TransactionParticipant;
028import org.jpos.util.Log;
029
030import javax.script.Invocable;
031import javax.script.ScriptEngine;
032import javax.script.ScriptEngineManager;
033import javax.script.ScriptException;
034import java.io.FileReader;
035import java.io.Serializable;
036
037/**
038 * A TransactionParticipant whose prepare, commit and abort methods can be
039 * specified through JS scripts. <BR>
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 *  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 * Usage:
048 *
049 * <pre>
050 *     Add a transaction participant like this:
051 *     &lt;participant class="org.jpos.transaction.participant.JSParticipant" logger="Q2" realm="js"
052 *     src='deploy/test.js' /&gt;
053 *
054 *     test.js may look like this (all functions are optional)
055 *
056 *     var K = Java.type("org.jpos.transaction.TransactionConstants");
057 *     var prepare = function(id, ctx) {
058 *       var map = ctx.getMap();
059 *       ctx.log ("Prepare has been called");
060 *       ctx.log (map.TIMESTAMP);
061 *       map.NEWPROPERTY='ABC';
062 *       return K.PREPARED;
063 *     }
064 *
065 *     var prepareForAbort = function(id, ctx) {
066 *       ctx.put ("Test", "Test from JS transaction $id");
067 *       ctx.log ("prepareForAbort has been called");
068 *       return K.PREPARED;
069 *     }
070 *     var commit = function(id, ctx) {
071 *       ctx.log ("Commit has been called");
072 *     }
073 *
074 *     var abort = function(id, ctx) {
075 *       ctx.log ("Abort has been called");
076 *     }
077 * </pre>
078 *
079 * @author  @apr (based on AMarques' BSHTransactionParticipant)
080 */
081@SuppressWarnings("unchecked")
082public class JSParticipant extends Log
083    implements TransactionParticipant, AbortParticipant, XmlConfigurable 
084{
085    /** Default constructor; no instance state to initialise. */
086    public JSParticipant() {}
087    private Invocable js;
088    boolean trace;
089    boolean hasPrepare;
090    boolean hasPrepareForAbort;
091    boolean hasCommit;
092    boolean hasAbort;
093
094    public int prepare (long id, Serializable context) {
095        return hasPrepare ? invokeWithResult("prepare", id, context) : PREPARED | READONLY;
096    }
097    public int prepareForAbort (long id, Serializable context) {
098        return hasPrepareForAbort ? invokeWithResult("prepareForAbort", id, context) : PREPARED | READONLY;
099    }
100    public void commit(long id, Serializable context) {
101        if (hasCommit)
102            invokeNoResult("commit", id, context);
103    }
104
105    public void abort(long id, Serializable context) {
106        if (hasAbort)
107            invokeNoResult("abort", id, context);
108    }
109
110    public void setConfiguration(Element e) throws ConfigurationException {
111            try (FileReader src = new FileReader(QFactory.getAttributeValue(e, "src")))  {
112            trace = "yes".equals(QFactory.getAttributeValue(e, "trace"));
113            ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
114            engine.eval(src);
115            js = (Invocable) engine;
116            hasPrepare = hasFunction ("prepare");
117            hasPrepareForAbort = hasFunction ("prepareForAbort");
118            hasCommit = hasFunction ("commit");
119            hasAbort = hasFunction ("abort");
120        } catch (Exception ex) {
121            throw new ConfigurationException(ex.getMessage(), ex);
122        }
123    }
124
125    private boolean hasFunction (String functionName) throws ConfigurationException {
126        try {
127            js.invokeFunction(functionName, 0L, new Context());
128            return true;
129        } catch (NoSuchMethodException e) {
130            return false;
131        } catch (ScriptException e) {
132            throw new ConfigurationException (e);
133        }
134    }
135
136    private int invokeWithResult (String functionName, long id, Serializable context) {
137        try {
138            return (Integer) js.invokeFunction(functionName, id, context);
139        } catch (Exception e) {
140            if (context instanceof Context) {
141                Context ctx = (Context) context;
142                ctx.log(e);
143            } else {
144                warn(id, e);
145            }
146            return ABORTED;
147        }
148    }
149    private void invokeNoResult (String functionName, long id, Serializable context) {
150        try {
151            js.invokeFunction(functionName, id, context);
152        } catch (Exception e) {
153            if (context instanceof Context) {
154                Context ctx = (Context) context;
155                ctx.log(e);
156            } else {
157                warn(id, e);
158            }
159        }
160    }
161}