/******************************************************************************
* This file is part of Adempiere ERP Bazaar *
* http://www.adempiere.org *
* *
* Copyright (C) Heng Sin Low *
* Copyright (C) Contributors *
* *
* 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. *
* *
* Contributors: *
* - Teo Sarca *
*****************************************************************************/
package org.adempiere.util;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.util.Properties;
import java.util.logging.Level;
import javax.script.ScriptEngine;
import org.adempiere.base.Core;
import org.adempiere.exceptions.AdempiereException;
import org.compiere.model.MProcess;
import org.compiere.model.MRule;
import org.compiere.process.ProcessCall;
import org.compiere.process.ProcessInfo;
import org.compiere.process.ProcessInfoParameter;
import org.compiere.process.ProcessInfoUtil;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.compiere.util.Trx;
import org.compiere.wf.MWFProcess;
import org.compiere.wf.MWorkflow;
/**
* Helper methods for server process
* @author Low Heng Sin
* @author Teo Sarca, SC ARHIPAC SERVICE SRL
*
BF [ 1757523 ] Server Processes are using Server's context
* BF [ 2528297 ] Poor error message on jasper fail
* BF [ 2530847 ] Report is displayed even if java process fails
*/
public final class ProcessUtil {
public static final String JASPER_STARTER_CLASS = "org.adempiere.report.jasper.ReportStarter";
/** Logger */
private static final CLogger log = CLogger.getCLogger(ProcessUtil.class);
private ProcessUtil() {}
/**
* Start database store procedure
* @param processInfo
* @param ProcedureName
* @param trx
* @return boolean
*/
public static boolean startDatabaseProcedure (ProcessInfo processInfo, String ProcedureName, Trx trx) {
return startDatabaseProcedure(processInfo, ProcedureName, trx, true);
}
/**
* Start database store procedure
* @param processInfo
* @param ProcedureName
* @param trx
* @param managedTrx false if trx is managed by caller
* @return boolean
*/
public static boolean startDatabaseProcedure (ProcessInfo processInfo, String ProcedureName, Trx trx, boolean managedTrx) {
String sql = "{call " + ProcedureName + "(?)}";
String trxName = trx != null ? trx.getTrxName() : null;
CallableStatement cstmt = null;
try
{
//hengsin, add trx support, updateable support.
cstmt = DB.prepareCall(sql, ResultSet.CONCUR_UPDATABLE, trxName);
cstmt.setInt(1, processInfo.getAD_PInstance_ID());
cstmt.executeUpdate();
if (trx != null && trx.isActive() && managedTrx)
{
trx.commit(true);
}
}
catch (Exception e)
{
log.log(Level.SEVERE, sql, e);
if (trx != null && trx.isActive() && managedTrx)
{
trx.rollback();
}
processInfo.setSummary (Msg.getMsg(Env.getCtx(), "ProcessRunError") + " " + e.getLocalizedMessage());
processInfo.setError (true);
return false;
}
finally
{
DB.close(cstmt);
cstmt = null;
if (trx != null && managedTrx)
trx.close();
}
return true;
}
@Deprecated
public static boolean startJavaProcess(ProcessInfo pi, Trx trx) {
return startJavaProcess(Env.getCtx(), pi, trx);
}
/**
* @param ctx
* @param pi
* @param trx
* @return true if process completed successfully
*/
public static boolean startJavaProcess(Properties ctx, ProcessInfo pi, Trx trx) {
return startJavaProcess(ctx, pi, trx, true);
}
/**
* @param ctx
* @param pi
* @param trx
* @param managedTrx false if trx is managed by caller
* @return true if process completed successfully
*/
public static boolean startJavaProcess(Properties ctx, ProcessInfo pi, Trx trx, boolean managedTrx) {
return startJavaProcess(ctx, pi, trx, managedTrx, null);
}
/**
* @param ctx
* @param pi
* @param trx
* @param managedTrx false if trx is managed by caller
* @return true if process completed successfully
*/
public static boolean startJavaProcess(Properties ctx, ProcessInfo pi, Trx trx, boolean managedTrx, IProcessUI processMonitor) {
String className = pi.getClassName();
if (className == null) {
MProcess proc = new MProcess(ctx, pi.getAD_Process_ID(), trx.getTrxName());
if (proc.getJasperReport() != null)
className = JASPER_STARTER_CLASS;
}
ProcessCall process = null;
//invoke process factory
process = Core.getProcess(className);
if (process == null) {
pi.setSummary("Failed to create new process instance for " + className, true);
return false;
}
boolean success = false;
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(process.getClass().getClassLoader());
process.setProcessUI(processMonitor);
success = process.startProcess(ctx, pi, trx);
if (success && trx != null && managedTrx)
{
trx.commit(true);
}
}
catch (Throwable e)
{
pi.setSummary (Msg.getMsg(Env.getCtx(), "ProcessError") + " " + e.getLocalizedMessage(), true);
log.log(Level.SEVERE, pi.getClassName(), e);
success = false;
return false;
}
finally
{
if (trx != null && managedTrx)
{
if (!success) {
trx.rollback();
}
trx.close();
trx = null;
}
Thread.currentThread().setContextClassLoader(cl);
}
return success;
}
/**
* Start process written in script (javascript, groovy, etc)
* @param ctx
* @param pi
* @param trx
* @return true if process completed successfully
*/
public static boolean startScriptProcess(Properties ctx, ProcessInfo pi, Trx trx) {
String msg = null;
boolean success = true;
try
{
String cmd = pi.getClassName();
MRule rule = MRule.get(ctx, cmd.substring(MRule.SCRIPT_PREFIX.length()));
if (rule == null) {
log.log(Level.WARNING, cmd + " not found");
pi.setSummary ("ScriptNotFound", true);
return false;
}
if ( ! (rule.getEventType().equals(MRule.EVENTTYPE_Process)
&& rule.getRuleType().equals(MRule.RULETYPE_JSR223ScriptingAPIs))) {
log.log(Level.WARNING, cmd + " must be of type JSR 223 and event Process");
pi.setSummary ("ScriptNotFound", true);
return false;
}
ScriptEngine engine = rule.getScriptEngine();
if (engine == null) {
throw new AdempiereException("Engine not found: " + rule.getEngineName());
}
// Window context are W_
// Login context are G_
// Method arguments context are A_
// Parameter context are P_
MRule.setContext(engine, ctx, 0); // no window
// now add the method arguments to the engine
engine.put(MRule.ARGUMENTS_PREFIX + "Ctx", ctx);
if (trx == null) {
trx = Trx.get(Trx.createTrxName(pi.getTitle()+"_"+pi.getAD_PInstance_ID()), true);
trx.setDisplayName(ProcessUtil.class.getName()+"_startScriptProcess");
}
engine.put(MRule.ARGUMENTS_PREFIX + "Trx", trx);
engine.put(MRule.ARGUMENTS_PREFIX + "TrxName", trx.getTrxName());
engine.put(MRule.ARGUMENTS_PREFIX + "Record_ID", pi.getRecord_ID());
engine.put(MRule.ARGUMENTS_PREFIX + "AD_Client_ID", pi.getAD_Client_ID());
engine.put(MRule.ARGUMENTS_PREFIX + "AD_User_ID", pi.getAD_User_ID());
engine.put(MRule.ARGUMENTS_PREFIX + "AD_PInstance_ID", pi.getAD_PInstance_ID());
engine.put(MRule.ARGUMENTS_PREFIX + "Table_ID", pi.getTable_ID());
// Add process parameters
ProcessInfoParameter[] para = pi.getParameter();
if (para == null) {
ProcessInfoUtil.setParameterFromDB(pi);
para = pi.getParameter();
}
if (para != null) {
engine.put(MRule.ARGUMENTS_PREFIX + "Parameter", pi.getParameter());
for (int i = 0; i < para.length; i++)
{
String name = para[i].getParameterName();
if (para[i].getParameter_To() == null) {
Object value = para[i].getParameter();
if (name.endsWith("_ID") && (value instanceof BigDecimal))
engine.put(MRule.PARAMETERS_PREFIX + name, ((BigDecimal)value).intValue());
else
engine.put(MRule.PARAMETERS_PREFIX + name, value);
} else {
Object value1 = para[i].getParameter();
Object value2 = para[i].getParameter_To();
if (name.endsWith("_ID") && (value1 instanceof BigDecimal))
engine.put(MRule.PARAMETERS_PREFIX + name + "1", ((BigDecimal)value1).intValue());
else
engine.put(MRule.PARAMETERS_PREFIX + name + "1", value1);
if (name.endsWith("_ID") && (value2 instanceof BigDecimal))
engine.put(MRule.PARAMETERS_PREFIX + name + "2", ((BigDecimal)value2).intValue());
else
engine.put(MRule.PARAMETERS_PREFIX + name + "2", value2);
}
}
}
engine.put(MRule.ARGUMENTS_PREFIX + "ProcessInfo", pi);
msg = engine.eval(rule.getScript()).toString();
//transaction should rollback if there are error in process
if (msg != null && msg.startsWith("@Error@"))
success = false;
// Parse Variables
msg = Msg.parseTranslation(ctx, msg);
pi.setSummary (msg, !success);
}
catch (Exception e)
{
pi.setSummary("ScriptError", true);
log.log(Level.SEVERE, pi.getClassName(), e);
success = false;
}
if (success) {
if (trx != null)
{
try
{
trx.commit(true);
} catch (Exception e)
{
log.log(Level.SEVERE, "Commit failed.", e);
pi.addSummary("Commit Failed.");
pi.setError(true);
success = false;
}
trx.close();
}
} else {
if (trx != null)
{
trx.rollback();
trx.close();
}
}
return success;
}
/**
* Start workflow
* @param ctx
* @param pi
* @param AD_Workflow_ID
* @return MWFProcess
*/
public static MWFProcess startWorkFlow(Properties ctx, ProcessInfo pi, int AD_Workflow_ID) {
MWorkflow wf = MWorkflow.get (ctx, AD_Workflow_ID);
MWFProcess wfProcess = wf.start(pi, pi.getTransactionName());
if (log.isLoggable(Level.FINE)) log.fine(pi.toString());
return wfProcess;
}
/**
* Start a java process without closing the given transaction. Is used from the workflow engine.
* @param ctx
* @param pi
* @param trx
* @return true if process completed successfully
*/
public static boolean startJavaProcessWithoutTrxClose(Properties ctx, ProcessInfo pi, Trx trx) {
return startJavaProcess(ctx, pi, trx, false);
}
}