From 452069c60e8c533017ee8c6488e8f35fafd98205 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Fri, 25 Jan 2008 22:58:32 +0000 Subject: [PATCH] Refactoring for [ adempiere-Feature Requests-1877902 ] Implement JSR 223: Scripting callout Created MRule --- base/src/org/compiere/model/GridField.java | 9 - base/src/org/compiere/model/GridFieldVO.java | 48 +-- base/src/org/compiere/model/GridTab.java | 129 ++------ base/src/org/compiere/model/MRule.java | 297 ++++++++++++++++++ .../331b-trunk/078_FR1877902_EngineScript.sql | 7 + .../postgresql/078_FR1877902_EngineScript.sql | 8 + 6 files changed, 341 insertions(+), 157 deletions(-) create mode 100644 base/src/org/compiere/model/MRule.java create mode 100644 migration/331b-trunk/078_FR1877902_EngineScript.sql create mode 100644 migration/331b-trunk/postgresql/078_FR1877902_EngineScript.sql diff --git a/base/src/org/compiere/model/GridField.java b/base/src/org/compiere/model/GridField.java index 05ffaf830a..f7f3058742 100644 --- a/base/src/org/compiere/model/GridField.java +++ b/base/src/org/compiere/model/GridField.java @@ -1086,15 +1086,6 @@ public class GridField return m_vo.Callout; } - //FR [1877902] - /** - * Get Script Code - * @return Script Code - */ - public String getScriptCode() - { - return m_vo.scriptCode; - } /** * Get AD_Process_ID * @return process diff --git a/base/src/org/compiere/model/GridFieldVO.java b/base/src/org/compiere/model/GridFieldVO.java index f4014d9708..d8cbc0960d 100644 --- a/base/src/org/compiere/model/GridFieldVO.java +++ b/base/src/org/compiere/model/GridFieldVO.java @@ -140,52 +140,8 @@ public class GridFieldVO implements Serializable vo.Description = rs.getString (i); else if (columnName.equalsIgnoreCase("Help")) vo.Help = rs.getString (i); - else if (columnName.equalsIgnoreCase("Callout")) { + else if (columnName.equalsIgnoreCase("Callout")) vo.Callout = rs.getString (i); - // CarlosRuiz - globalqss - FR [ 1877902 ] Implement beanshell callout - // Vitor Perez - vpj-cd - FR [ 1877902 ] Implement JSR 223 Scripting APIs to Callout - String callout = vo.Callout; - int script_end = 8; - int engine_end = 0; - if (callout != null && callout.toLowerCase().startsWith("@script:")) { - engine_end = callout.indexOf(":", script_end); - if (engine_end <= 0) - { - CLogger.get().log(Level.SEVERE, "ColumnName=" + columnName, " Call Invalid "+ callout +" error in syntax please use something like @script:groovy:mycallout"); - continue; - } - String engine = callout.substring(script_end,engine_end); - if (engine== null) - { - CLogger.get().log(Level.SEVERE, "ColumnName=" + columnName, " Call Invalid "+ callout +" error in syntax please use something like @script:groovy:mycallout"); - continue; - } - String calloutname = callout.substring(engine_end); - if (calloutname== null) - { - CLogger.get().log(Level.SEVERE, "ColumnName=" + columnName, " Call Invalid "+ callout +" error in syntax please use something like @script:groovy:mycallout"); - continue; - } - String rule_value =engine + calloutname.trim(); - // TODO: Write MRule and create accessor by Value, EventType and RuleType - int script_id = DB.getSQLValue( - null, - "SELECT AD_Rule_ID FROM AD_Rule WHERE TRIM(Value)=? AND EventType='" - + X_AD_Rule.EVENTTYPE_Callout - + "' AND RuleType='" - + X_AD_Rule.RULETYPE_JSR223ScriptingAPIs - + "' AND IsActive='Y'", - rule_value); - if (script_id > 0) { - X_AD_Rule rule = new X_AD_Rule(ctx, script_id, null); - vo.scriptCode = rule.getScript(); - // TODO: pre-parse for better performance - // http://beanshell.org/manual/parser.html#Parsing_and_Performance - } else { - CLogger.get().log(Level.SEVERE, "ColumnName=" + columnName, " Rule not found:" + rule_value); - } - } - } else if (columnName.equalsIgnoreCase("AD_Process_ID")) vo.AD_Process_ID = rs.getInt (i); else if (columnName.equalsIgnoreCase("ReadOnlyLogic")) @@ -496,8 +452,6 @@ public class GridFieldVO implements Serializable public boolean IsParent = false; /** Callout */ public String Callout = ""; - /** Callout */ - public String scriptCode = null; /** Process */ public int AD_Process_ID = 0; /** Description */ diff --git a/base/src/org/compiere/model/GridTab.java b/base/src/org/compiere/model/GridTab.java index cf81005582..cfb12067d0 100644 --- a/base/src/org/compiere/model/GridTab.java +++ b/base/src/org/compiere/model/GridTab.java @@ -28,14 +28,9 @@ import java.util.logging.*; import javax.swing.event.*; -//import org.compiere.apps.ADialog; import org.compiere.util.*; -import bsh.EvalError; - import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.Invocable; import javax.script.ScriptException; /** @@ -2453,93 +2448,50 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable // FR [1877902] // CarlosRuiz - globalqss - implement beanshell callout // Victor Perez - vpj-cd implement JSR 223 Scripting - if (cmd.toLowerCase().startsWith("@script:")) { + if (cmd.toLowerCase().startsWith(MRule.SCRIPT_PREFIX)) { - if (field.getScriptCode() == null || field.getScriptCode().length() == 0) { - retValue = "Callout invalid error in syntax: " + cmd + " error in syntax please use something like @script:groovy:mycallout"; + MRule rule = MRule.get(m_vo.ctx, cmd.substring(MRule.SCRIPT_PREFIX.length())); + if (rule == null) { + retValue = "Callout " + cmd + " not found"; log.log(Level.SEVERE, retValue); return retValue; } - - int engine_end = 0; - engine_end = callout.indexOf(":", 8); - if (engine_end <= 0) - { - CLogger.get().log(Level.SEVERE, "Callout Invalid: " + cmd , " error in syntax please use something like @script:groovy:mycallout"); - return retValue; - } - String engine_name = callout.substring(8,engine_end); - if (engine_name== null) - { - CLogger.get().log(Level.SEVERE, "Callout Invalid: " + cmd , " error in syntax please use something like @script:groovy:mycallout"); - return retValue; - } - - // Convert from ctx to hashmap - HashMap script_ctx = new HashMap(); - // Convert properties to HashMap - Enumeration en = m_vo.ctx.keys(); - while (en.hasMoreElements()) - { - String key = en.nextElement().toString(); - // filter - if (key == null || key.length() == 0 - || key.startsWith("P") // Preferences - || (key.indexOf('|') != -1 && !key.startsWith(String.valueOf(m_vo.WindowNo))) // other Window Settings - || (key.indexOf('|') != -1 && key.indexOf('|') != key.lastIndexOf('|')) //other tab - ) - continue; - Object obj = m_vo.ctx.get(key); - if (key != null && key.length() > 0) - { - //log.fine( "Script.setEnvironment " + key, value); - if (value == null) - script_ctx.remove(key); - else - script_ctx.put(convertKey(key, m_vo.WindowNo), obj); - } + if ( ! (rule.getEventType().equals(MRule.EVENTTYPE_Callout) + && rule.getRuleType().equals(MRule.RULETYPE_JSR223ScriptingAPIs))) { + retValue = "Callout " + cmd + + " must be of type JSR 223 and event Callout"; + log.log(Level.SEVERE, retValue); + return retValue; } + + ScriptEngine engine = rule.getScriptEngine(); + // Window context are _ // Login context are __ + MRule.setContext(engine, m_vo.ctx, m_vo.WindowNo); + // now add the callout parameters windowNo, tab, field, value, oldValue to the engine // Parameter context are ___ - // Now Add windowNo, tab, field, value, oldValue to the hashmap - script_ctx.put("___WindowNo", m_vo.WindowNo); - script_ctx.put("___Tab", this); - script_ctx.put("___Field", field); - script_ctx.put("___Value", value); - script_ctx.put("___OldValue", oldValue); + engine.put("___WindowNo", m_vo.WindowNo); + engine.put("___Tab", this); + engine.put("___Field", field); + engine.put("___Value", value); + engine.put("___OldValue", oldValue); - ScriptEngineManager factory = new ScriptEngineManager(); - ScriptEngine engine = factory.getEngineByName(engine_name); - try { - Iterator it = script_ctx.keySet().iterator(); - while (it.hasNext()) - { - String key = (String)it.next(); - Object script_value = script_ctx.get(key); - if (script_value instanceof Boolean) - engine.put(key, ((Boolean)script_value).booleanValue()); - else if (script_value instanceof Integer) - engine.put(key,((Integer)script_value).intValue()); - else if (script_value instanceof Double) - engine.put(key,((Integer)script_value).intValue()); - else - engine.put(key,script_value); - } - - activeCallouts.add(cmd); - retValue = engine.eval(field.getScriptCode()).toString(); - activeCallouts.remove(cmd); - + activeCallouts.add(cmd); + retValue = engine.eval(rule.getScript()).toString(); } - catch (ScriptException e) + catch (Exception e) { log.log(Level.SEVERE, "", e); retValue = "Callout Invalid: " + e.toString(); return retValue; } + finally + { + activeCallouts.remove(cmd); + } } else { @@ -2575,7 +2527,7 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable log.log(Level.SEVERE, "start", e); retValue = "Callout Invalid: " + e.toString(); return retValue; - } + } finally { activeCallouts.remove(cmd); @@ -2593,31 +2545,6 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable return ""; } // processCallout - - /** - * Convert Key - * # -> _ - * @param key - * @param m_windowNo - * @return converted key - */ - private String convertKey (String key, int m_windowNo) - { - String k = m_windowNo + "|"; - if (key.startsWith(k)) - { - String retValue = "_" + key.substring(k.length()); - retValue = Util.replace(retValue, "|", "_"); - return retValue; - } - else - { - String retValue = Util.replace(key, "#", "__"); - return retValue; - } - } // convertKey - - /** * Get Value of Field with columnName * @param columnName column name diff --git a/base/src/org/compiere/model/MRule.java b/base/src/org/compiere/model/MRule.java new file mode 100644 index 0000000000..84b15033be --- /dev/null +++ b/base/src/org/compiere/model/MRule.java @@ -0,0 +1,297 @@ +/****************************************************************************** + * Product: Adempiere ERP & CRM Smart Business Solution * + * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. This program is distributed in the hope * + * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * + * See the GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + * For the text or an alternative of this public license, you may reach us * + * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA * + * or via info@compiere.org or http://www.compiere.org/license.html * + * Contributor(s): Carlos Ruiz - globalqss * + *****************************************************************************/ +package org.compiere.model; + +import java.sql.*; +import java.util.*; +import java.util.logging.*; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; + +import org.compiere.util.*; + +/** + * Persistent Rule Model + * @author Carlos Ruiz + * @version $Id: MRule.java + */ +public class MRule extends X_AD_Rule +{ + /** + * Get Rule from Cache + * @param ctx context + * @param AD_Rule_ID id + * @return MRule + */ + public static MRule get (Properties ctx, int AD_Rule_ID) + { + Integer key = new Integer (AD_Rule_ID); + MRule retValue = (MRule) s_cache.get (key); + if (retValue != null) + return retValue; + retValue = new MRule (ctx, AD_Rule_ID, null); + if (retValue.get_ID () != 0) + s_cache.put (key, retValue); + return retValue; + } // get + + /** + * Get Rule from Cache + * @param ctx context + * @param ruleValue case sensitive rule Value + * @return Rule + */ + public static MRule get (Properties ctx, String ruleValue) + { + if (ruleValue == null) + return null; + Iterator it = s_cache.values().iterator(); + while (it.hasNext()) + { + MRule retValue = (MRule)it.next(); + if (ruleValue.equals(retValue.getValue())) + return retValue; + } + // + MRule retValue = null; + String sql = "SELECT * FROM AD_Rule WHERE Value=? AND IsActive='Y'"; + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement (sql, null); + pstmt.setString(1, ruleValue); + ResultSet rs = pstmt.executeQuery (); + if (rs.next ()) + retValue = new MRule (ctx, rs, null); + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + s_log.log(Level.SEVERE, sql, e); + } + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + + if (retValue != null) + { + Integer key = new Integer (retValue.getAD_Rule_ID()); + s_cache.put (key, retValue); + } + return retValue; + } // get + + /** + * Get Model Validation Login Rules + * @param ctx context + * @return Rule + */ + public static ArrayList getModelValidatorLoginRules (Properties ctx) + { + ArrayList rules = new ArrayList(); + MRule rule = null; + String sql = "SELECT * FROM AD_Rule WHERE EventType=? AND IsActive='Y'"; + PreparedStatement pstmt = null; + try + { + pstmt = DB.prepareStatement (sql, null); + pstmt.setString(1, EVENTTYPE_ModelValidatorLoginEvent); + ResultSet rs = pstmt.executeQuery (); + while (rs.next ()) { + rule = new MRule (ctx, rs, null); + rules.add(rule); + } + rs.close (); + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + s_log.log(Level.SEVERE, sql, e); + } + try + { + if (pstmt != null) + pstmt.close (); + pstmt = null; + } + catch (Exception e) + { + pstmt = null; + } + + if (rules != null && rules.size() > 0) + return rules; + else + return null; + } // getModelValidatorLoginRules + + /** Cache */ + private static CCache s_cache = new CCache("AD_Rule", 20); + + /** Static Logger */ + private static CLogger s_log = CLogger.getCLogger (MRule.class); + + public static final String SCRIPT_PREFIX = "@script:"; + + /* Engine Manager */ + private ScriptEngineManager factory = null; + /* The Engine */ + ScriptEngine engine = null; + + /************************************************************************** + * Standard Constructor + * @param ctx context + * @param AD_Rule_ID id + * @param trxName transaction + */ + public MRule (Properties ctx, int AD_Rule_ID, String trxName) + { + super (ctx, AD_Rule_ID, trxName); + } // MRule + + /** + * Load Constructor + * @param ctx context + * @param rs result set + * @param trxName transaction + */ + public MRule (Properties ctx, ResultSet rs, String trxName) + { + super(ctx, rs, trxName); + } // MRule + + /** + * Before Save + * @param newRecord new + * @return true + */ + protected boolean beforeSave (boolean newRecord) + { + // Validate format for scripts + // must be engine:name + // where engine can be groovy, jython or beanshell + if (getRuleType().equals(RULETYPE_JSR223ScriptingAPIs)) { + String engineName = getEngineName(); + if (engineName == null || + (! (engineName.equalsIgnoreCase("groovy") + || engineName.equalsIgnoreCase("jython") + || engineName.equalsIgnoreCase("beanshell")))) { + log.saveError("Error", Msg.getMsg(getCtx(), "WrongScriptValue")); + return false; + } + } + return true; + } // beforeSave + + /** + * String Representation + * @return info + */ + public String toString() + { + StringBuffer sb = new StringBuffer ("MRule["); + sb.append (get_ID()).append ("-").append (getValue()).append ("]"); + return sb.toString (); + } // toString + + /** + * Script Engine for this rule + * @return ScriptEngine + */ + public ScriptEngine getScriptEngine() { + factory = new ScriptEngineManager(); + String engineName = getEngineName(); + if (engineName != null) + engine = factory.getEngineByName(getEngineName()); + return engine; + } + + public String getEngineName() { + int colonPosition = getValue().indexOf(":"); + if (colonPosition < 0) + return null; + return getValue().substring(0, colonPosition); + } + + /************************************************************************** + * Set Context ctx to the engine based on windowNo + * @param engine ScriptEngine + * @param ctx context + * @param windowNo window number + */ + public static void setContext(ScriptEngine engine, Properties ctx, int windowNo) { + Enumeration en = ctx.keys(); + while (en.hasMoreElements()) + { + String key = en.nextElement().toString(); + // filter + if (key == null || key.length() == 0 + || key.startsWith("P") // Preferences + || (key.indexOf('|') != -1 && !key.startsWith(String.valueOf(windowNo))) // other Window Settings + || (key.indexOf('|') != -1 && key.indexOf('|') != key.lastIndexOf('|')) //other tab + ) + continue; + Object value = ctx.get(key); + if (value != null) { + if (value instanceof Boolean) + engine.put(convertKey(key, windowNo), ((Boolean)value).booleanValue()); + else if (value instanceof Integer) + engine.put(convertKey(key, windowNo), ((Integer)value).intValue()); + else if (value instanceof Double) + engine.put(convertKey(key, windowNo), ((Double)value).doubleValue()); + else + engine.put(convertKey(key, windowNo), value); + } + } + } + + /** + * Convert Key + * # -> _ + * @param key + * @param m_windowNo + * @return converted key + */ + private static String convertKey (String key, int m_windowNo) + { + String k = m_windowNo + "|"; + if (key.startsWith(k)) + { + String retValue = "_" + key.substring(k.length()); + retValue = Util.replace(retValue, "|", "_"); + return retValue; + } + else + { + String retValue = Util.replace(key, "#", "__"); + return retValue; + } + } // convertKey + +} // MRule \ No newline at end of file diff --git a/migration/331b-trunk/078_FR1877902_EngineScript.sql b/migration/331b-trunk/078_FR1877902_EngineScript.sql new file mode 100644 index 0000000000..33451d17ac --- /dev/null +++ b/migration/331b-trunk/078_FR1877902_EngineScript.sql @@ -0,0 +1,7 @@ +-- Jan 24, 2008 10:27:22 PM COT +-- 1877902 - Implement JSR 223: Scripting callout +INSERT INTO AD_Message (AD_Client_ID,AD_Message_ID,AD_Org_ID,Created,CreatedBy,EntityType,IsActive,MsgText,MsgTip,MsgType,Updated,UpdatedBy,Value) VALUES (0,53023,0,TO_DATE('2008-01-24 22:27:19','YYYY-MM-DD HH24:MI:SS'),100,'D','Y','Wrong Script Value - format for JSR 223 Script must be engine:scriptName where supported engines must be something like groovy, jython, beanshell',NULL,'E',TO_DATE('2008-01-24 22:27:19','YYYY-MM-DD HH24:MI:SS'),100,'WrongScriptValue') +; + +INSERT INTO AD_Message_Trl (AD_Language,AD_Message_ID, MsgText,MsgTip, IsTranslated,AD_Client_ID,AD_Org_ID,Created,Createdby,Updated,UpdatedBy) SELECT l.AD_Language,t.AD_Message_ID, t.MsgText,t.MsgTip, 'N',t.AD_Client_ID,t.AD_Org_ID,t.Created,t.Createdby,t.Updated,t.UpdatedBy FROM AD_Language l, AD_Message t WHERE l.IsActive='Y' AND l.IsSystemLanguage='Y' AND l.IsBaseLanguage='N' AND t.AD_Message_ID=53023 AND EXISTS (SELECT * FROM AD_Message_Trl tt WHERE tt.AD_Language!=l.AD_Language OR tt.AD_Message_ID!=t.AD_Message_ID) +; \ No newline at end of file diff --git a/migration/331b-trunk/postgresql/078_FR1877902_EngineScript.sql b/migration/331b-trunk/postgresql/078_FR1877902_EngineScript.sql new file mode 100644 index 0000000000..f357c5cb61 --- /dev/null +++ b/migration/331b-trunk/postgresql/078_FR1877902_EngineScript.sql @@ -0,0 +1,8 @@ +-- Jan 24, 2008 10:27:22 PM COT +-- 1877902 - Implement JSR 223: Scripting callout +INSERT INTO AD_Message (AD_Client_ID,AD_Message_ID,AD_Org_ID,Created,CreatedBy,EntityType,IsActive,MsgText,MsgTip,MsgType,Updated,UpdatedBy,Value) VALUES (0,53023,0,TO_TIMESTAMP('2008-01-24 22:27:19','YYYY-MM-DD HH24:MI:SS'),100,'D','Y','Wrong Script Value - format for JSR 223 Script must be engine:scriptName where supported engines must be something like groovy, jython, beanshell',NULL,'E',TO_TIMESTAMP('2008-01-24 22:27:19','YYYY-MM-DD HH24:MI:SS'),100,'WrongScriptValue') +; + +INSERT INTO AD_Message_Trl (AD_Language,AD_Message_ID, MsgText,MsgTip, IsTranslated,AD_Client_ID,AD_Org_ID,Created,Createdby,Updated,UpdatedBy) SELECT l.AD_Language,t.AD_Message_ID, t.MsgText,t.MsgTip, 'N',t.AD_Client_ID,t.AD_Org_ID,t.Created,t.Createdby,t.Updated,t.UpdatedBy FROM AD_Language l, AD_Message t WHERE l.IsActive='Y' AND l.IsSystemLanguage='Y' AND l.IsBaseLanguage='N' AND t.AD_Message_ID=53023 AND EXISTS (SELECT * FROM AD_Message_Trl tt WHERE tt.AD_Language!=l.AD_Language OR tt.AD_Message_ID!=t.AD_Message_ID) +; +