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 * <participant class="org.jpos.transaction.participant.JSParticipant" logger="Q2" realm="js" 052 * src='deploy/test.js' /> 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 private Invocable js; 086 boolean trace; 087 boolean hasPrepare; 088 boolean hasPrepareForAbort; 089 boolean hasCommit; 090 boolean hasAbort; 091 092 public int prepare (long id, Serializable context) { 093 return hasPrepare ? invokeWithResult("prepare", id, context) : PREPARED | READONLY; 094 } 095 public int prepareForAbort (long id, Serializable context) { 096 return hasPrepareForAbort ? invokeWithResult("prepareForAbort", id, context) : PREPARED | READONLY; 097 } 098 public void commit(long id, Serializable context) { 099 if (hasCommit) 100 invokeNoResult("commit", id, context); 101 } 102 103 public void abort(long id, Serializable context) { 104 if (hasAbort) 105 invokeNoResult("abort", id, context); 106 } 107 108 public void setConfiguration(Element e) throws ConfigurationException { 109 try (FileReader src = new FileReader(QFactory.getAttributeValue(e, "src"))) { 110 trace = "yes".equals(QFactory.getAttributeValue(e, "trace")); 111 ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); 112 engine.eval(src); 113 js = (Invocable) engine; 114 hasPrepare = hasFunction ("prepare"); 115 hasPrepareForAbort = hasFunction ("prepareForAbort"); 116 hasCommit = hasFunction ("commit"); 117 hasAbort = hasFunction ("abort"); 118 } catch (Exception ex) { 119 throw new ConfigurationException(ex.getMessage(), ex); 120 } 121 } 122 123 private boolean hasFunction (String functionName) throws ConfigurationException { 124 try { 125 js.invokeFunction(functionName, 0L, new Context()); 126 return true; 127 } catch (NoSuchMethodException e) { 128 return false; 129 } catch (ScriptException e) { 130 throw new ConfigurationException (e); 131 } 132 } 133 134 private int invokeWithResult (String functionName, long id, Serializable context) { 135 try { 136 return (Integer) js.invokeFunction(functionName, id, context); 137 } catch (Exception e) { 138 if (context instanceof Context) { 139 Context ctx = (Context) context; 140 ctx.log(e); 141 } else { 142 warn(id, e); 143 } 144 return ABORTED; 145 } 146 } 147 private void invokeNoResult (String functionName, long id, Serializable context) { 148 try { 149 js.invokeFunction(functionName, id, context); 150 } catch (Exception e) { 151 if (context instanceof Context) { 152 Context ctx = (Context) context; 153 ctx.log(e); 154 } else { 155 warn(id, e); 156 } 157 } 158 } 159}