/****************************************************************************** * 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.wf; import java.awt.Point; import java.math.BigDecimal; import java.math.RoundingMode; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.logging.Level; import java.util.stream.Collectors; import org.adempiere.exceptions.DBException; import org.compiere.model.MColumn; import org.compiere.model.MRole; import org.compiere.model.Query; import org.compiere.model.X_AD_WF_Node; import org.compiere.util.DB; import org.compiere.util.Env; import org.compiere.util.Msg; import org.compiere.util.Util; import org.idempiere.cache.ImmutablePOCache; import org.idempiere.cache.ImmutablePOSupport; /** * Extended Workflow Node Model for AD_WF_Node * * @author Jorg Janke * @version $Id: MWFNode.java,v 1.2 2006/07/30 00:51:05 jjanke Exp $ * * @author Teo Sarca, www.arhipac.ro *
  • FR [ 2214883 ] Remove SQL code and Replace for Query *
  • BF [ 2815732 ] MWFNode.getWorkflow not working in trx * https://sourceforge.net/p/adempiere/bugs/1964/ */ public class MWFNode extends X_AD_WF_Node implements ImmutablePOSupport { /** * generated serial id */ private static final long serialVersionUID = 3328770995394833132L; /** * Get WF Node from Cache * @param AD_WF_Node_ID id * @return MWFNode */ public static MWFNode get (int AD_WF_Node_ID) { return get(Env.getCtx(), AD_WF_Node_ID); } /** * Get WF Node from Cache * @param ctx context * @param AD_WF_Node_ID id * @return MWFNode */ public static MWFNode get (Properties ctx, int AD_WF_Node_ID) { String key = Env.getAD_Language(ctx) + "_" + AD_WF_Node_ID; MWFNode retValue = s_cache.get (ctx, key, e -> new MWFNode(ctx, e)); if (retValue != null) return retValue; retValue = new MWFNode (ctx, AD_WF_Node_ID, (String)null); if (retValue.get_ID () == AD_WF_Node_ID) { s_cache.put (key, retValue, e -> new MWFNode(Env.getCtx(), e)); return retValue; } return null; } // get /** * Get updateable copy of MWFNode from cache * @param ctx * @param AD_WF_Node_ID * @param trxName * @return MWFNode */ public static MWFNode getCopy(Properties ctx, int AD_WF_Node_ID, String trxName) { MWFNode node = get(AD_WF_Node_ID); if (node != null) node = new MWFNode(ctx, node, trxName); return node; } /** Cache */ private static ImmutablePOCache s_cache = new ImmutablePOCache (Table_Name, 50, 0, false, 0); /** * UUID based Constructor * @param ctx Context * @param AD_WF_Node_UU UUID key * @param trxName Transaction */ public MWFNode(Properties ctx, String AD_WF_Node_UU, String trxName) { super(ctx, AD_WF_Node_UU, trxName); if (Util.isEmpty(AD_WF_Node_UU)) setInitialDefaults(); if (getAD_WF_Node_ID() > 0) { loadNext(); loadTrl(); } } /** * Standard Constructor - save to cache * @param ctx context * @param AD_WF_Node_ID id * @param trxName transaction */ public MWFNode (Properties ctx, int AD_WF_Node_ID, String trxName) { super (ctx, AD_WF_Node_ID, trxName); if (AD_WF_Node_ID == 0) setInitialDefaults(); if (getAD_WF_Node_ID() > 0) { loadNext(); loadTrl(); } } // MWFNode /** * Set the initial defaults for a new record */ private void setInitialDefaults() { // setAD_WF_Node_ID (0); // setAD_Workflow_ID (0); // setValue (null); // setName (null); setAction (ACTION_WaitSleep); setCost (Env.ZERO); setDuration (0); setEntityType (ENTITYTYPE_UserMaintained); // U setIsCentrallyMaintained (true); // Y setJoinElement (JOINELEMENT_XOR); // X setLimit (0); setSplitElement (SPLITELEMENT_XOR); // X setWaitingTime (0); setXPosition (0); setYPosition (0); } /** * Parent Constructor * @param wf workflow (parent) * @param Value value * @param Name name */ public MWFNode (MWorkflow wf, String Value, String Name) { this (wf.getCtx(), 0, wf.get_TrxName()); setClientOrg(wf); setAD_Workflow_ID (wf.getAD_Workflow_ID()); setValue (Value); setName (Name); m_durationBaseMS = wf.getDurationBaseSec() * 1000; } // MWFNode /** * Load Constructor - save to cache * @param ctx context * @param rs result set to load info from * @param trxName transaction */ public MWFNode (Properties ctx, ResultSet rs, String trxName) { super(ctx, rs, trxName); loadNext(); loadTrl(); } // MWFNode /** * Copy constructor * @param copy */ public MWFNode(MWFNode copy) { this(Env.getCtx(), copy); } /** * Copy constructor * @param ctx * @param copy */ public MWFNode(Properties ctx, MWFNode copy) { this(ctx, copy, (String) null); } /** * Copy constructor * @param ctx * @param copy * @param trxName */ public MWFNode(Properties ctx, MWFNode copy, String trxName) { this(ctx, 0, trxName); copyPO(copy); this.m_next = copy.m_next != null ? copy.m_next.stream().map(e -> {return new MWFNodeNext(ctx, e, trxName);}).collect(Collectors.toCollection(ArrayList::new)) : null; this.m_name_trl = copy.m_name_trl; this.m_description_trl = copy.m_description_trl; this.m_help_trl = copy.m_help_trl; this.m_translated = copy.m_translated; this.m_column = copy.m_column != null ? new MColumn(ctx, copy.m_column, trxName) : null; this.m_paras = copy.m_paras != null ? Arrays.stream(copy.m_paras).map(e ->{return new MWFNodePara(ctx, e, trxName);}).toArray(MWFNodePara[]::new) : null; this.m_durationBaseMS = copy.m_durationBaseMS; } /** Next Modes */ private List m_next = new ArrayList(); /** Translated Name */ private String m_name_trl = null; /** Translated Description */ private String m_description_trl = null; /** Translated Help */ private String m_help_trl = null; /** Translation Flag */ private boolean m_translated = false; /** Column */ private MColumn m_column = null; /** Process Parameters */ private MWFNodePara[] m_paras = null; /** Duration Base MS */ private long m_durationBaseMS = -1; /** * Set Client Org * @param AD_Client_ID client * @param AD_Org_ID org */ public void setClientOrg (int AD_Client_ID, int AD_Org_ID) { super.setClientOrg (AD_Client_ID, AD_Org_ID); } // setClientOrg /** * Load Next Nodes into {@link #m_next} */ private void loadNext() { m_next = new Query(getCtx(), MWFNodeNext.Table_Name, "AD_WF_NodeNext.AD_WF_Node_ID=? AND AD_WF_NodeNext.AD_Client_ID IN (0, ?)", get_TrxName()) .addJoinClause(" JOIN AD_WF_Node ON (AD_WF_Node.AD_WF_Node_ID=AD_WF_NodeNext.AD_WF_Next_ID AND AD_WF_Node.IsActive='Y')") .setParameters(new Object[]{get_ID(), Env.getAD_Client_ID(Env.getCtx())}) .setOnlyActiveRecords(true) .setOrderBy(MWFNodeNext.COLUMNNAME_SeqNo) .list(); boolean splitAnd = SPLITELEMENT_AND.equals(getSplitElement()); for (MWFNodeNext next : m_next) { next.setFromSplitAnd(splitAnd); if (is_Immutable()) next.markImmutable(); } if (log.isLoggable(Level.FINE)) log.fine("#" + m_next.size()); } // loadNext /** * Load Translation */ private void loadTrl() { if (Env.isBaseLanguage(getCtx(), "AD_Workflow") || get_ID() == 0) return; final String sql = "SELECT Name, Description, Help FROM AD_WF_Node_Trl" +" WHERE AD_WF_Node_ID=? AND AD_Language=?"; PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = DB.prepareStatement(sql, get_TrxName()); pstmt.setInt(1, get_ID()); pstmt.setString(2, Env.getAD_Language(getCtx())); rs = pstmt.executeQuery(); if (rs.next()) { m_name_trl = rs.getString(1); m_description_trl = rs.getString(2); m_help_trl = rs.getString(3); m_translated = true; } } catch (SQLException e) { //log.log(Level.SEVERE, sql, e); throw new DBException(e, sql); } finally { DB.close(rs, pstmt); rs = null; pstmt = null; } if (log.isLoggable(Level.FINE)) log.fine("Trl=" + m_translated); } // loadTrl /** * Get Number of Next Nodes * @return number of next nodes */ public int getNextNodeCount() { return m_next.size(); } // getNextNodeCount /** * Get next nodes for transitions * @param AD_Client_ID for client * @return array of next nodes */ public MWFNodeNext[] getTransitions(int AD_Client_ID) { loadNext(); ArrayList list = new ArrayList(); for (int i = 0; i < m_next.size(); i++) { MWFNodeNext next = m_next.get(i); if (next.getAD_Client_ID() == 0 || next.getAD_Client_ID() == AD_Client_ID) list.add(next); } MWFNodeNext[] retValue = new MWFNodeNext [list.size()]; list.toArray(retValue); return retValue; } // getNextNodes /** * Get Name * @param translated translated * @return Name */ public String getName(boolean translated) { if (translated && m_translated) return m_name_trl; return getName(); } // getName /** * Get Description * @param translated translated * @return Description */ public String getDescription(boolean translated) { if (translated && m_translated) return m_description_trl; return getDescription(); } // getDescription /** * Get Help * @param translated translated * @return Name */ public String getHelp(boolean translated) { if (translated && m_translated) return m_help_trl; return getHelp(); } // getHelp /** * Set Position * @param position point */ public void setPosition (Point position) { setPosition(position.x, position.y); } // setPosition /** * Set Position * @param x x * @param y y */ public void setPosition (int x, int y) { setXPosition(x); setYPosition(y); } // setPosition /** * Get Position * @return position point */ public Point getPosition () { return new Point (getXPosition(), getYPosition()); } // getPosition /** * Get Action Info * @return action info text */ public String getActionInfo() { String action = getAction(); if (ACTION_AppsProcess.equals(action)) return "Process:AD_Process_ID=" + getAD_Process_ID(); else if (ACTION_DocumentAction.equals(action)) return "DocumentAction=" + getDocAction(); else if (ACTION_AppsReport.equals(action)) return "Report:AD_Process_ID=" + getAD_Process_ID(); else if (ACTION_AppsTask.equals(action)) return "Task:AD_Task_ID=" + getAD_Task_ID(); else if (ACTION_SetVariable.equals(action)) return "SetVariable:AD_Column_ID=" + getAD_Column_ID(); else if (ACTION_SubWorkflow.equals(action)) return "Workflow:AD_Workflow_ID=" + getAD_Workflow_ID(); else if (ACTION_UserChoice.equals(action)) return "UserChoice:AD_Column_ID=" + getAD_Column_ID(); /* else if (ACTION_UserWorkbench.equals(action)) return "Workbench:?";*/ else if (ACTION_UserForm.equals(action)) return "Form:AD_Form_ID=" + getAD_Form_ID(); else if (ACTION_UserWindow.equals(action)) return "Window:AD_Window_ID=" + getAD_Window_ID(); else if (ACTION_UserInfo.equals(action)) return "Window:AD_InfoWindow_ID=" + getAD_InfoWindow_ID(); else if (ACTION_WaitSleep.equals(action)) return "Sleep:WaitTime=" + getWaitTime(); return "??"; } // getActionInfo /** * Get Attribute Name * @see org.compiere.model.X_AD_WF_Node#getAttributeName() * @return Attribute Name */ @Override public String getAttributeName () { if (getAD_Column_ID() == 0) return super.getAttributeName(); // We have a column String attribute = super.getAttributeName(); if (attribute != null && attribute.length() > 0) return attribute; setAttributeName(getColumn().getColumnName()); return super.getAttributeName (); } // getAttributeName /** * Get Column * @return column or null */ public MColumn getColumn() { if (getAD_Column_ID() == 0) return null; if (m_column == null) { if (is_Immutable()) m_column = MColumn.get(getCtx(), getAD_Column_ID()); else m_column = MColumn.getCopy(getCtx(), getAD_Column_ID(), get_TrxName()); } return m_column; } // getColumn /** * Is this an Approval setp? * @return true if User Approval */ public boolean isUserApproval() { if (!ACTION_UserChoice.equals(getAction())) return false; return getColumn() != null && "IsApproved".equals(getColumn().getColumnName()); } // isApproval /** * Is this a User Choice step? * @return true if User Choice */ public boolean isUserChoice() { return ACTION_UserChoice.equals(getAction()); } // isUserChoice /** * Is this a Manual user step? * @return true if Window/Form/Workbench */ public boolean isUserManual() { if (ACTION_UserForm.equals(getAction()) || ACTION_UserWindow.equals(getAction()) || ACTION_UserInfo.equals(getAction()) /*|| ACTION_UserWorkbench.equals(getAction())*/) return true; return false; } // isUserManual /** * Get Duration in ms * @return duration in ms */ public long getDurationMS () { long duration = super.getDuration (); if (duration == 0) return 0; if (m_durationBaseMS == -1) m_durationBaseMS = getAD_Workflow().getDurationBaseSec() * 1000; return duration * m_durationBaseMS; } // getDurationMS /** * Get Duration Limit in ms * @return duration limit in ms */ public long getLimitMS () { long limit = super.getLimit (); if (limit == 0) return 0; if (m_durationBaseMS == -1) m_durationBaseMS = getAD_Workflow().getDurationBaseSec() * 1000; return limit * m_durationBaseMS; } // getLimitMS /** * Get Duration Calendar Field * @return Calendar Field (Calendar.MINUTE, etc.) */ public int getDurationCalendarField() { return getAD_Workflow().getDurationCalendarField(); } // getDirationCalendarField /** * Calculate Dynamic Priority * @param seconds second after created * @return Dynamic Priority */ public int calculateDynamicPriority (int seconds) { if (seconds == 0 || getDynPriorityUnit() == null || getDynPriorityChange() == null || Env.ZERO.compareTo(getDynPriorityChange()) == 0) return 0; // BigDecimal divide = Env.ZERO; if (DYNPRIORITYUNIT_Minute.equals(getDynPriorityUnit())) divide = new BigDecimal (60); else if (DYNPRIORITYUNIT_Hour.equals(getDynPriorityUnit())) divide = new BigDecimal (3600); else if (DYNPRIORITYUNIT_Day.equals(getDynPriorityUnit())) divide = new BigDecimal (86400); else return 0; // BigDecimal change = new BigDecimal (seconds) .divide(divide, RoundingMode.DOWN) .multiply(getDynPriorityChange()); return change.intValue(); } // calculateDynamicPriority /** * Get Node Parameters * @return array of node parameters */ public MWFNodePara[] getParameters() { if (m_paras == null) { m_paras = MWFNodePara.getParameters(getCtx(), getAD_WF_Node_ID()); if (m_paras != null && m_paras.length > 0 && is_Immutable()) Arrays.stream(m_paras).forEach(e -> e.markImmutable()); } return m_paras; } // getParameters /** * Get Workflow * @return workflow * @deprecated please use {@link #getAD_Workflow()} */ @Deprecated public MWorkflow getWorkflow() { return getAD_Workflow(); } // getWorkflow @Override public MWorkflow getAD_Workflow() { return MWorkflow.getCopy(getCtx(), getAD_Workflow_ID(), get_TrxName()); } /** * String Representation * @return info */ @Override public String toString () { StringBuilder sb = new StringBuilder ("MWFNode["); sb.append(get_ID()) .append("-").append(getName()) .append(",Action=").append(getActionInfo()) .append ("]"); return sb.toString (); } // toString /** * User String Representation * @return info */ public String toStringX () { StringBuilder sb = new StringBuilder ("MWFNode["); sb.append(getName()) .append("-").append(getActionInfo()) .append("]"); return sb.toString (); } // toStringX @Override protected boolean beforeSave (boolean newRecord) { // Validate mandatory field for action String action = getAction(); if (action.equals(ACTION_WaitSleep)) ; else if (action.equals(ACTION_AppsProcess) || action.equals(ACTION_AppsReport)) { if (getAD_Process_ID() == 0) { log.saveError("FillMandatory", Msg.getElement(getCtx(), "AD_Process_ID")); return false; } } else if (action.equals(ACTION_AppsTask)) { if (getAD_Task_ID() == 0) { log.saveError("FillMandatory", Msg.getElement(getCtx(), "AD_Task_ID")); return false; } } else if (action.equals(ACTION_DocumentAction)) { if (getDocAction() == null || getDocAction().length() == 0) { log.saveError("FillMandatory", Msg.getElement(getCtx(), "DocAction")); return false; } } else if (action.equals(ACTION_EMail)) { if (getR_MailText_ID() == 0) { log.saveError("FillMandatory", Msg.getElement(getCtx(), "R_MailText_ID")); return false; } } else if (action.equals(ACTION_SetVariable)) { if (getAttributeValue() == null) { log.saveError("FillMandatory", Msg.getElement(getCtx(), "AttributeValue")); return false; } if (getAD_Column_ID() > 0) { // Validate that just advanced roles can manipulate secure/advanced column value via workflows MColumn column = MColumn.get(getCtx(), getAD_Column_ID(), get_TrxName ()); if (column.isSecure() || column.isAdvanced()) { if (! MRole.getDefault().isAccessAdvanced()) { log.saveError("AccessTableNoUpdate", Msg.getElement(getCtx(), column.getColumnName())); return false; } } } } else if (action.equals(ACTION_SubWorkflow)) { if (getAD_Workflow_ID() == 0) { log.saveError("FillMandatory", Msg.getElement(getCtx(), "AD_Workflow_ID")); return false; } } else if (action.equals(ACTION_UserChoice)) { if (getAD_Column_ID() == 0) { log.saveError("FillMandatory", Msg.getElement(getCtx(), "AD_Column_ID")); return false; } } else if (action.equals(ACTION_UserForm)) { if (getAD_Form_ID() == 0) { log.saveError("FillMandatory", Msg.getElement(getCtx(), "AD_Form_ID")); return false; } } else if (action.equals(ACTION_UserWindow)) { if (getAD_Window_ID() == 0) { log.saveError("FillMandatory", Msg.getElement(getCtx(), "AD_Window_ID")); return false; } } else if (action.equals(ACTION_UserInfo)) { if (getAD_InfoWindow_ID() == 0) { log.saveError("FillMandatory", Msg.getElement(getCtx(), "AD_InfoWindow_ID")); return false; } } return true; } // beforeSave /** * Check if the workflow node is valid for given date * @param date * @return true if node is valid for the given date */ public boolean isValidFromTo(Timestamp date) { Timestamp validFrom = getValidFrom(); Timestamp validTo = getValidTo(); if (validFrom != null && date.before(validFrom)) return false; if (validTo != null && date.after(validTo)) return false; return true; } @Override public MWFNode markImmutable() { if (is_Immutable()) return this; makeImmutable(); if (m_column != null) m_column.markImmutable(); if (m_next != null && m_next.size() > 0) m_next.stream().forEach(e -> e.markImmutable()); if (m_paras != null && m_paras.length > 0) Arrays.stream(m_paras).forEach(e -> e.markImmutable()); return this; } /** * Get workflow nodes with where clause. * @param ctx context * @param whereClause where clause w/o the WHERE keyword * @param trxName transaction * @return array of workflow nodes */ public static MWFNode[] getWFNodes (Properties ctx, String whereClause, String trxName) { List list = new Query(ctx,Table_Name,whereClause,trxName) .list(); MWFNode[] retValue = new MWFNode[list.size()]; list.toArray (retValue); return retValue; } // getWFNodes }