/******************************************************************************
* 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 *
*****************************************************************************/
package org.compiere.model;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import org.adempiere.base.Core;
import org.adempiere.base.event.EventManager;
import org.adempiere.exceptions.AdempiereException;
import org.compiere.print.MPrintFormat;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Language;
import org.compiere.util.Msg;
import org.compiere.util.Util;
import org.idempiere.distributed.IMessageService;
import org.idempiere.distributed.ITopic;
import org.osgi.service.event.Event;
/**
* Process Instance Model
*
* @author Jorg Janke
* @version $Id: MPInstance.java,v 1.3 2006/07/30 00:58:36 jjanke Exp $
*
* @author Teo Sarca, www.arhipac.ro
*
FR [ 2818478 ] Introduce MPInstance.createParameter helper method
* https://sourceforge.net/p/adempiere/feature-requests/756/
*/
public class MPInstance extends X_AD_PInstance
{
/**
* generated serial id
*/
private static final long serialVersionUID = -6414730734415159480L;
public static final String ON_RUNNING_JOB_CHANGED_TOPIC = "onRunningJobChanged";
private static CLogger s_log = CLogger.getCLogger (MPInstance.class);
/**
* UUID based Constructor
* @param ctx Context
* @param AD_PInstance_UU UUID key
* @param trxName Transaction
*/
public MPInstance(Properties ctx, String AD_PInstance_UU, String trxName) {
super(ctx, AD_PInstance_UU, trxName);
if (Util.isEmpty(AD_PInstance_UU))
setInitialDefaults();
}
/**
* Standard Constructor
* @param ctx context
* @param AD_PInstance_ID instance or 0
* @param ignored no transaction support
*/
public MPInstance (Properties ctx, int AD_PInstance_ID, String ignored)
{
super (ctx, AD_PInstance_ID, null);
// New Process
if (AD_PInstance_ID == 0)
setInitialDefaults();
} // MPInstance
/**
* Set the initial defaults for a new record
*/
private void setInitialDefaults() {
setIsProcessing (false);
}
/**
* Load Constructor
* @param ctx context
* @param rs result set
* @param ignored no transaction support
*/
public MPInstance (Properties ctx, ResultSet rs, String ignored)
{
super(ctx, rs, null);
} // MPInstance
/**
* Create Process Instance from Process and create parameters
* @param process process
* @param Record_ID Record
* @deprecated Please use {@link #MPInstance(MProcess, int, int, String)}
*/
@Deprecated
public MPInstance (MProcess process, int Record_ID)
{
this(process, -1, Record_ID, null);
}
/**
* Create and save new Process Instance from Process and record parameters
* @param process process
* @param Table_ID
* @param Record_ID Record id
* @param Record_UU Record uuid
*/
public MPInstance (MProcess process, int Table_ID, int Record_ID, String Record_UU)
{
this (process.getCtx(), 0, null);
setAD_Process_ID (process.getAD_Process_ID());
setAD_Table_ID(Table_ID);
setRecord_ID (Record_ID);
setRecord_UU(Record_UU);
setAD_User_ID(Env.getAD_User_ID(process.getCtx()));
if (!save()) // need to save for parameters
throw new IllegalArgumentException ("Cannot Save");
// Set Parameter Base Info
MProcessPara[] para = process.getParameters();
for (int i = 0; i < para.length; i++)
{
MPInstancePara pip = new MPInstancePara (this, para[i].getSeqNo());
pip.setParameterName(para[i].getColumnName());
pip.setInfo(para[i].getName());
pip.saveEx();
}
} // MPInstance
/**
* New Constructor
* @param ctx context
* @param AD_Process_ID Process ID
* @param Record_ID record
* @deprecated Please use {@link #MPInstance(Properties, int, int, int, String)}
*/
@Deprecated
public MPInstance (Properties ctx, int AD_Process_ID, int Record_ID)
{
this(ctx, AD_Process_ID, -1, Record_ID, null);
}
/**
* New Constructor
* @param ctx context
* @param AD_Process_ID Process ID
* @param Table_ID
* @param Record_ID record id
* @param Record_UU record uuid
*/
public MPInstance (Properties ctx, int AD_Process_ID, int Table_ID, int Record_ID, String Record_UU)
{
this(ctx, 0, null);
setAD_Process_ID (AD_Process_ID);
setAD_Table_ID(Table_ID);
setRecord_ID (Record_ID);
setRecord_UU(Record_UU);
setAD_User_ID(Env.getAD_User_ID(ctx));
setIsProcessing (false);
} // MPInstance
/** Parameters */
private MPInstancePara[] m_parameters = null;
/**
* Get Instance Parameters
* @return instance parameter array
*/
public MPInstancePara[] getParameters()
{
if (m_parameters != null)
return m_parameters;
//FR: [ 2214883 ] Remove SQL code and Replace for Query - red1
final String whereClause = "AD_PInstance_ID=?";
List list = new Query(getCtx(), I_AD_PInstance_Para.Table_Name, whereClause, null) // @TODO: Review implications of using transaction
.setParameters(getAD_PInstance_ID())
.setOrderBy("SeqNo, ParameterName")
.list();
//
m_parameters = new MPInstancePara[list.size()];
list.toArray(m_parameters);
return m_parameters;
} // getParameters
/**
* Get Process Parameters
* @return process parameters array
*/
public MProcessPara[] getProcessParameters()
{
final String whereClause = "AD_Process_ID=?";
List list = new Query(getCtx(), MProcessPara.Table_Name, whereClause, get_TrxName())
.setParameters(getAD_Process_ID())
.setOnlyActiveRecords(true)
.setOrderBy("SeqNo")
.list();
//
MProcessPara[] processParameters = new MProcessPara[list.size()];
list.toArray(processParameters);
return processParameters;
} // getParameters
/**
* Check whether a set of process instance parameters are equal
* to the current instance parameters.
* @param params array of instance parameters to compare
* @return true if the process instance parameters equals to this instance's instance parameters
*/
public boolean equalParameters(MPInstancePara[] params) {
//No parameters
if ((getParameters() == null || getParameters().length == 0) && (params == null || params.length == 0))
return true;
//Different number of parameters
if (getParameters().length != params.length)
return false;
int comparedParams = 0;
for (MPInstancePara instanceParameter : getParameters()) {
for (MPInstancePara para : params) {
if (instanceParameter.getParameterName().equals(para.getParameterName())) {
comparedParams++;
if (!instanceParameter.equalParameter(para)) {
return false;
}
break;
}
}
}
return comparedParams == getParameters().length; //all the compared parameters have the same name and value
}
/** Instance Log Entries */
private ArrayList m_log = new ArrayList();
/**
* Get Instance Logs
* @return array of instance logs
*/
public MPInstanceLog[] getLog()
{
// load it from DB
m_log.clear();
String sql = "SELECT * FROM AD_PInstance_Log WHERE AD_PInstance_ID=? ORDER BY Log_ID";
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement(sql, null);
pstmt.setInt(1, getAD_PInstance_ID());
rs = pstmt.executeQuery();
while (rs.next())
{
m_log.add(new MPInstanceLog(rs));
}
}
catch (Exception e)
{
log.log(Level.SEVERE, sql, e);
}
finally
{
DB.close(rs, pstmt);
rs = null;
pstmt = null;
}
MPInstanceLog[] retValue = new MPInstanceLog[m_log.size()];
m_log.toArray(retValue);
return retValue;
} // getLog
/**
* Add instance log
* @param P_Date date
* @param P_ID id
* @param P_Number number
* @param P_Msg msg
* @return added instance log
*/
public MPInstanceLog addLog (Timestamp P_Date, int P_ID, BigDecimal P_Number, String P_Msg)
{
return addLog(P_Date, P_ID, P_Number, P_Msg, 0, 0);
} // addLog
/**
* Add instance log
* @param P_Date date
* @param P_ID id
* @param P_Number number
* @param P_Msg msg
* @param AD_Table_ID tableID
* @param Record_ID recordID
* @return added instance log
*/
public MPInstanceLog addLog (Timestamp P_Date, int P_ID, BigDecimal P_Number, String P_Msg, int AD_Table_ID, int Record_ID)
{
MPInstanceLog logEntry = new MPInstanceLog (getAD_PInstance_ID(), m_log.size()+1,
P_Date, P_ID, P_Number, P_Msg, AD_Table_ID, Record_ID);
m_log.add(logEntry);
// save it to DB ?
return logEntry;
} // addLog
/**
* Set AD_Process_ID.
* Throw exception if current role has no permission to run the process.
* @param AD_Process_ID process
*/
public void setAD_Process_ID (int AD_Process_ID)
{
int AD_Role_ID = Env.getAD_Role_ID(getCtx());
if (AD_Role_ID != 0)
{
MRole role = MRole.get(getCtx(), AD_Role_ID);
Boolean access = role.getProcessAccess(AD_Process_ID);
if (access == null || !access.booleanValue()) {
MProcess proc = MProcess.get(getCtx(), AD_Process_ID);
StringBuilder procMsg = new StringBuilder("[");
if (! Language.isBaseLanguage (Env.getAD_Language(getCtx()))) {
procMsg.append(proc.get_Translation("Name")).append(" / ");
}
procMsg.append(proc.getName()).append("]");
if (Env.isReadOnlySession())
throw new AdempiereException(Msg.getMsg(getCtx(), "ReadOnlySession"));
else
throw new IllegalStateException(Msg.getMsg(getCtx(), "CannotAccessProcess", new Object[] {procMsg.toString(), role.getName()}));
}
}
super.setAD_Process_ID (AD_Process_ID);
} // setAD_Process_ID
/**
* Set Record ID.
* @param Record_ID record
**/
public void setRecord_ID (int Record_ID)
{
if (Record_ID < 0)
{
if (log.isLoggable(Level.INFO)) log.info("Set to 0 from " + Record_ID);
Record_ID = 0;
}
set_ValueNoCheck ("Record_ID", Integer.valueOf(Record_ID));
} // setRecord_ID
/**
* String Representation
* @see java.lang.Object#toString()
* @return info
*/
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder ("MPInstance[")
.append (get_ID())
.append(",OK=").append(isOK());
String msg = getErrorMsg();
if (msg != null && msg.length() > 0)
sb.append(msg);
sb.append ("]");
return sb.toString ();
} // toString
/**
* Dump Instance Logs to server log
*/
public void log()
{
if (log.isLoggable(Level.INFO)) {
log.info(toString());
MPInstanceLog[] pil = getLog();
for (int i = 0; i < pil.length; i++)
log.info(i + "=" + pil[i]);
}
} // log
/** Result OK = 1 */
public static final int RESULT_OK = 1;
/** Result FALSE = 0 */
public static final int RESULT_ERROR = 0;
/**
* Is it OK
* @return true if Result == OK
*/
public boolean isOK()
{
return getResult() == RESULT_OK;
} // isOK
/**
* Set Result
* @param ok
*/
public void setResult (boolean ok)
{
super.setResult (ok ? RESULT_OK : RESULT_ERROR);
} // setResult
/**
* After Save
* @param newRecord new
* @param success success
* @return success
*/
@Override
protected boolean afterSave (boolean newRecord, boolean success)
{
if (!success)
return success;
// Update Statistics
if (!newRecord
&& !isProcessing()
&& is_ValueChanged("IsProcessing"))
{
long ms = System.currentTimeMillis() - getCreated().getTime();
int seconds = (int)(ms / 1000);
if (seconds < 1)
seconds = 1;
String updsql = "UPDATE AD_Process SET Statistic_Count=Statistic_Count+1, Statistic_Seconds=Statistic_Seconds+? WHERE AD_Process_ID=?";
int no = DB.executeUpdate(updsql, new Object[] {seconds, getAD_Process_ID()}, true, null); // out of trx
if (no == 1) {
if (log.isLoggable(Level.FINE)) log.fine("afterSave - Process Statistics updated Sec=" + seconds);
} else {
log.warning("afterSave - Process Statistics not updated");
}
}
return success;
} // afterSave
/**
* Create Process Instance Parameter and save to database
* @param seqNo parameter sequence#
* @param parameterName parameter name
* @param value parameter value
* @return instance parameter
*/
public MPInstancePara createParameter(int seqNo, String parameterName, Object value)
{
MPInstancePara ip = new MPInstancePara(this, seqNo);
if (value == null)
{
ip.setParameter(parameterName, (String)null);
}
else if (value instanceof BigDecimal)
{
ip.setParameter(parameterName, (BigDecimal)value);
}
else if (value instanceof Integer)
{
ip.setParameter(parameterName, (Integer)value);
}
else if (value instanceof Timestamp)
{
ip.setParameter(parameterName, (Timestamp)value);
}
else if (value instanceof Boolean)
{
ip.setParameter(parameterName, (Boolean)value);
}
else
{
ip.setParameter(parameterName, value.toString());
}
//
ip.saveEx();
return ip;
}
@Override
public I_AD_Process getAD_Process() throws RuntimeException {
return MProcess.get(getAD_Process_ID());
}
/**
* Publish ON_RUNNING_JOB_CHANGED_TOPIC message to message service.
* If message service is not available, post as OSGi event instead.
* @param AD_User_ID
*/
public static void publishChangedEvent(int AD_User_ID) {
IMessageService service = Core.getMessageService();
if (service != null) {
ITopic topic = service.getTopic(ON_RUNNING_JOB_CHANGED_TOPIC);
topic.publish(AD_User_ID);
} else {
postOnChangedEvent(AD_User_ID);
}
}
/**
* Post ON_RUNNING_JOB_CHANGED_TOPIC OSGi event.
* @param AD_User_ID
*/
public static void postOnChangedEvent(int AD_User_ID) {
Map properties = new HashMap();
properties.put("AD_User_ID", AD_User_ID);
Event event = EventManager.newEvent(ON_RUNNING_JOB_CHANGED_TOPIC, properties, true);
EventManager.getInstance().postEvent(event);
}
/**
* Get list of process instance via AD_Process_ID and AD_User_ID
* @param ctx
* @param AD_Process_ID
* @param AD_User_ID
* @return process instance list
*/
public static List get(Properties ctx, int AD_Process_ID, int AD_User_ID) {
List list = new ArrayList();
List paramsStrAdded = new ArrayList();
List namedInstances = new Query(ctx, Table_Name, "AD_Process_ID=? AND AD_User_ID=? AND Name IS NOT NULL", null)
.setClient_ID()
.setOnlyActiveRecords(true)
.setParameters(AD_Process_ID, AD_User_ID)
.setOrderBy("Name")
.list();
for (MPInstance namedInstance : namedInstances) {
list.add(namedInstance);
paramsStrAdded.add(namedInstance.getParamsStr());
}
// unnamed instances
int lastRunCount = MSysConfig.getIntValue(MSysConfig.LASTRUN_RECORD_COUNT, 5, Env.getAD_Client_ID(ctx));
if (lastRunCount > 0) {
int maxLoopCount = 10 * lastRunCount;
// using JDBC instead of Query for performance reasons, AD_PInstance can be huge
String sql = "SELECT * FROM AD_PInstance "
+ " WHERE AD_Process_ID=? AND AD_User_ID=? AND IsActive='Y' AND AD_Client_ID=? AND Name IS NULL"
+ " ORDER BY Created DESC";
PreparedStatement pstmt = null;
ResultSet rs = null;
int runCount = 0;
int loopCount = 0;
try {
pstmt = DB.prepareStatement(sql, null);
pstmt.setFetchSize(lastRunCount);
pstmt.setInt(1, AD_Process_ID);
pstmt.setInt(2, AD_User_ID);
pstmt.setInt(3, Env.getAD_Client_ID(ctx));
rs = pstmt.executeQuery();
while (rs.next()) {
loopCount++;
MPInstance unnamedInstance = new MPInstance(ctx, rs, null);
String paramsStr = unnamedInstance.getParamsStr();
if (! paramsStrAdded.contains(paramsStr)) {
unnamedInstance.setName(Msg.getMsg(ctx, "LastRun") + " " + unnamedInstance.getCreated());
list.add(unnamedInstance);
paramsStrAdded.add(paramsStr);
runCount++;
if (runCount == lastRunCount)
break;
}
if (loopCount == maxLoopCount)
break;
}
} catch (Exception e)
{
s_log.log(Level.SEVERE, "Error while Fetching last run records", e);
} finally {
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
}
return list;
}
/**
* @return String concatenate all instance parameter values
*/
private String getParamsStr() {
StringBuilder cksum = new StringBuilder();
for (MPInstancePara ip : getParameters()) {
cksum.append("(")
.append(ip.getParameterName()).append("|")
.append(ip.getP_String()).append("|")
.append(ip.getP_String_To()).append("|")
.append(ip.getP_Number()).append("|")
.append(ip.getP_Number_To()).append("|")
.append(ip.getP_Date()).append("|")
.append(ip.getP_Date_To()).append("|")
.append(ip.getInfo()).append("|")
.append(ip.getInfo_To()).append("|")
.append(")");
}
if (getAD_Process().isReport()){
cksum.append(this.getAD_Language_ID()).append("|")
.append(this.getAD_PrintFormat_ID())
.append(this.getAD_Language_ID())
.append(this.getReportType())
.append(this.isSummary());
}
return cksum.toString();
}
/**
* Get instance info
* @param AD_PInstance_ID
* @return {@link PInstanceInfo}
* @throws SQLException
*/
public static PInstanceInfo getPInstanceInfo(int AD_PInstance_ID) throws SQLException {
PInstanceInfo info = null;
// Get Process Information: Name, Procedure Name, ClassName, IsReport, IsDirectPrint
String sql = "SELECT p.Name, p.ProcedureName,p.ClassName, p.AD_Process_ID," // 1..4
+ " p.isReport,p.IsDirectPrint,p.AD_ReportView_ID,p.AD_Workflow_ID," // 5..8
+ " CASE WHEN COALESCE(p.Statistic_Count,0)=0 THEN 0 ELSE p.Statistic_Seconds/p.Statistic_Count END," // 9
+ " p.JasperReport, p.AD_Process_UU " // 10..11
+ "FROM AD_Process p"
+ " INNER JOIN AD_PInstance i ON (p.AD_Process_ID=i.AD_Process_ID) "
+ "WHERE p.IsActive='Y'"
+ " AND i.AD_PInstance_ID=?";
if (!Env.isBaseLanguage(Env.getCtx(), "AD_Process"))
sql = "SELECT t.Name, p.ProcedureName,p.ClassName, p.AD_Process_ID," // 1..4
+ " p.isReport, p.IsDirectPrint,p.AD_ReportView_ID,p.AD_Workflow_ID," // 5..8
+ " CASE WHEN COALESCE(p.Statistic_Count,0)=0 THEN 0 ELSE p.Statistic_Seconds/p.Statistic_Count END," // 9
+ " p.JasperReport, p.AD_Process_UU " // 10..11
+ "FROM AD_Process p"
+ " INNER JOIN AD_PInstance i ON (p.AD_Process_ID=i.AD_Process_ID) "
+ " INNER JOIN AD_Process_Trl t ON (p.AD_Process_ID=t.AD_Process_ID"
+ " AND t.AD_Language='" + Env.getAD_Language(Env.getCtx()) + "') "
+ "WHERE p.IsActive='Y'"
+ " AND i.AD_PInstance_ID=?";
//
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement(sql,
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, null);
pstmt.setInt(1, AD_PInstance_ID);
rs = pstmt.executeQuery();
if (rs.next())
{
info = new PInstanceInfo();
info.name = rs.getString(1);
info.procedureName = rs.getString(2);
info.className = rs.getString(3);
info.AD_Process_ID = rs.getInt(4);
info.AD_Process_UU = rs.getString(11);
// Report
if ("Y".equals(rs.getString(5)))
{
info.isReport = true;
}
if ("Y".equals(rs.getString(6)))
{
info.isDirectPrint = true;
}
info.AD_ReportView_ID = rs.getInt(7);
info.AD_Workflow_ID = rs.getInt(8);
//
info.estimate = rs.getInt(9);
info.jasperReport = rs.getString(10);
}
}
finally
{
DB.close(rs, pstmt);
}
return info;
}
/**
* Instance info record class with fields from AD_PInstance+AD_Process
* @author hengsin
*/
public static class PInstanceInfo {
public int AD_Process_ID;
public String AD_Process_UU;
public int AD_ReportView_ID = 0;
public int AD_Workflow_ID = 0;
//translated name for current env context
public String name;
public String className;
public String procedureName = "";
public String jasperReport = "";
public boolean isReport = false;
public boolean isDirectPrint = false;
public int estimate;
}
/**
* Before Save
* @param newRecord new
* @return true
*/
@Override
protected boolean beforeSave (boolean newRecord)
{
if (newRecord) {
int sessionId = Env.getContextAsInt(Env.getCtx(), Env.AD_SESSION_ID);
if (sessionId > 0)
setAD_Session_ID(sessionId);
}
return true;
} // beforeSave
/**
* Set AD_PrintFormat_ID if empty, AD_Language_ID if empty and save the record.
* @param format
*/
public void updatePrintFormatAndLanguageIfEmpty(MPrintFormat format) {
if(getAD_PrintFormat_ID() <= 0 && format.getAD_PrintFormat_ID() > 0) {
setAD_PrintFormat_ID(format.getAD_PrintFormat_ID());
saveEx();
}
if(getAD_Language_ID() <= 0 && format.getLanguage() != null) {
setAD_Language_ID(MLanguage.get(Env.getCtx(), format.getLanguage()).getAD_Language_ID());
saveEx();
}
}
} // MPInstance