package $block{jfcpackagename}.app;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Function;

import javax.persistence.PessimisticLockException;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.logging.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.ScrollableResults;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.transaction.support.TransactionTemplate;

import jp.jasminesoft.jfc.ActionParameter;
import jp.jasminesoft.jfc.FilenameList;
import jp.jasminesoft.jfc.JFCErrorManager;
import jp.jasminesoft.jfc.JFCUtils;
import jp.jasminesoft.jfc.ScriptCodeRunner;
import jp.jasminesoft.jfc.PropertyManager;
import jp.jasminesoft.jfc.WorkflowSettingUtil;
import jp.jasminesoft.jfc.WorkFlowUtils;
import jp.jasminesoft.jfc.core.exception.BusinessLogicException;
import jp.jasminesoft.jfc.core.exception.WfParticipantNotFoundException;
import jp.jasminesoft.jfc.core.group.ElementUtils;
import jp.jasminesoft.jfc.core.util.LockUtils;
import jp.jasminesoft.jfc.core.util.StoreModelUtils;
import jp.jasminesoft.jfc.core.util.SystemUtils;
import jp.jasminesoft.jfc.dao.DataBindingContext;
import jp.jasminesoft.jfc.hibernate.DetachedCriteria;
import jp.jasminesoft.jfc.model.ContainerBase;
import jp.jasminesoft.jfc.model.juser.Juser;
import jp.jasminesoft.jfc.service.JFCEntityService;
import jp.jasminesoft.util.StringUtil;
import jp.jasminesoft.util.JavaVersion;
import $block{jfcpackagename}.app.jfcflow_setting.JfcflowSettingEntityService;
import $block{jfcpackagename}.app.jfcflow_setting.JfcflowSettingUtil;
import $block{jfcpackagename}.app.jfcworkflow_setting.JfcworkflowSettingService;
import $block{jfcpackagename}.app.jfcworkstate.JfcworkstateHelper;
import $block{jfcpackagename}.app.jfcworkstate.JfcworkstateService;
import $block{jfcpackagename}.model.jfcflow_setting.JfcflowSetting;
import $block{jfcpackagename}.model.jfcflow_setting.Layer;
import $block{jfcpackagename}.model.jfcparticipant_setting.JfcparticipantSetting;
import $block{jfcpackagename}.model.jfcparticipant_setting.JfcparticipantSettingMeta;
import $block{jfcpackagename}.model.jfcproxy_setting.JfcproxySetting;
import $block{jfcpackagename}.model.jfcsuspendworkstate.Jfcsuspendworkstate;
import $block{jfcpackagename}.model.jfcworkflow_setting_p.Beginnode;
import $block{jfcpackagename}.model.jfcworkflow_setting_p.Condition;
import $block{jfcpackagename}.model.jfcworkflow_setting_p.Endnode;
import $block{jfcpackagename}.model.jfcworkflow_setting_p.JfcworkflowSettingP;
import $block{jfcpackagename}.model.jfcworkflow_setting_p.RemandType;
import $block{jfcpackagename}.model.jfcworkflow_setting_p.ScriptSetting;
import $block{jfcpackagename}.model.jfcworkstate.Jfcworkstate;
import $block{jfcpackagename}.model.jfcworkstate.JfcworkstateMeta;
import $block{jfcpackagename}.model.jfcworkstate_lp.JfcworkstateLp;
import $block{jfcpackagename}.model.jfcworkstate_p.JfcworkstateP;
import jp.jasminesoft.workflow.ApplicationContextFileGenerator;
import jp.jasminesoft.workflow.RuleNode;
import jp.jasminesoft.workflow.RuleNodeUtil;
import jp.jasminesoft.workflow.WorkEvent;
import jp.jasminesoft.workflow.WorkflowService;

/**
 * WorkFlowManager provides management functions for the UI class part.
 *  
 * @author JasmineSoft
 */
public class WorkFlowManager implements jp.jasminesoft.jfc.WorkFlowConstants {
    private static WorkFlowManager _instance = new WorkFlowManager();
    
    private final static Logger logger = org.apache.logging.log4j.LogManager.getLogger(WorkFlowManager.class.getName());

    /**
     * Node name of application node.
     */
    // 2009.09.16 Delete "node" from node name
    public final static String APPLICATION_NODE_NAME = "1";

    /**
     * Cancelable node name.
     */
    public final static String CANCELABLE_NODE_NAME = "2";

    /**
     * Sequence node type.
     */
    public final static String SEQUENCE_NODE_TYPE = "1";

    /**
     * Concurrence node type.
     */
    public final static String CONCURRENCE_NODE_TYPE = "2";

    /**
     * The data registered by CSV upload.
     */
    private final static String JFC_CSV_UPLOAD_DATA = "jfcCSVUploadData";

    /**
     * Cache key prefix of workflow setting.
     */
    private final static String JFCWORKFLOW_FLOW_SETTING_CACHE_PREFIX = "jfcflowSettingCache_";

    /**
     * Cache key prefix of workflow id.
     */
    private final static String JFCWORKFLOW_FLOWID_CACHE_PREFIX = "jfcworkflowFlowIdCache_";

    /**
     * Cache key prefix of workflow setting.
     */
    private final static String JFCWORKFLOW_SETTING_CACHE_PREFIX = "jfcworkflowSettingCache_";

    /**
     * Cache key prefix of proxy settings.
     */
    private final static String JFCWORKFLOW_PROXY_SETTINGS_CACHE = "jfcproxySettingsCache";

    /**
     * Cache key prefix of proxy setting.
     */
    private final static String JFCWORKFLOW_PROXY_SETTING_CACHE_PREFIX = "jfcproxySettingCache_";

    /**
     * Cache key prefix of applicant juser.
     */
    private final static String JFCWORKFLOW_APPLICANT_CACHE_PREFIX = "jfcworkflowApplicantCache_";

    /**
     * Cache key prefix of workflow data.
     */
    private final static String JFCWORKFLOW_MODELDATA_CACHE_PREFIX = "jfcworkflowModelDataCache_";

    private static String engineName = null;
    static {
        String EngineName =
            PropertyManager.getProperty("jp.jasminesoft.jfc.ScriptCodeRunner.engineName");
        if (EngineName != null && EngineName.length() > 0) {
            engineName = EngineName;
        }
	if (engineName == null) {
	    engineName = "nashorn";
	}
    }

    protected WorkFlowManager() {
    }
    
    public static WorkFlowManager getInstance(ActionParameter p) {
        return _instance;
    }

    /**
     * The prefix when the participant is a group.
     */
    public final static String GroupidParticipantHeader = "groupid:";

    /**
     * The key of the request variable that holds a flag indicating whether to save 
     * the flow participant in the workflow application model when returned from the script.
     */
    public final static String AdmitUsersOrGroupFromScript = "jfcAdmitUsersOrGroupFromScript";

    /**
     * Item name to get latest data of workflow model.
     */
    protected final static Set<String> JFCWORKFLOW_TARGETITEM = new HashSet<String>(
        Arrays.asList(
            "jfcWorkflowCreateUser",
            "jfcWorkflowAdmitUser",
            "jfcWorkflowProcessedLayerId",
            "jfcWorkflowCurrentNode"
        )
    );
    
    public void clearAllJfcworkflowCache(ActionParameter p) {
        $block{jfcpackagename}.app.CacheManager cman = $block{jfcpackagename}.app.CacheManager.getInstance(p);
        cman.clearJfcflowSetting();
        cman.clearJfcparticipantSetting();
	WorkFlowUtils.clearAllJfcworkflowCache(p);// BR 13632, 2023.3.23
    }

    /**
     * Get whether or not the participant is a group ID.
     */
    public static boolean isGroupidParticipant(String participant) {
        return participant.startsWith(GroupidParticipantHeader);
    }

    /**
     * Return group ID from flow participant group.
     */
    public static int[] getGroupid(String participant) {
        String strGroupid = StringUtils.substringAfter(participant, GroupidParticipantHeader);
        int[] groupid = new int[1];
        try {
            groupid[0] = Integer.parseInt(strGroupid);
        } catch (NumberFormatException e) {
        }
        return groupid;
    }

    /**
     * Return flow participants.
     */
    public $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] getParticipants(String modelname, String pkey, ActionParameter p) {
        return getParticipants(modelname, pkey, null, p);
    }

    /**
     * Return flow participant setting.
     */
    public JfcparticipantSetting getJfcparticipantSetting(String modelname, String pkey, ActionParameter p) {
        $block{jfcpackagename}.app.CacheManager cman = $block{jfcpackagename}.app.CacheManager.getInstance(p);

        Integer flowId = getFlowId(modelname, pkey, p);
        JfcparticipantSetting jfcparticipantSetting = (JfcparticipantSetting) cman.getJfcparticipantSetting(String.valueOf(flowId));
        if (jfcparticipantSetting != null) {
            return jfcparticipantSetting;
        }
        jp.jasminesoft.jfc.service.JFCEntityService entityService = (jp.jasminesoft.jfc.service.JFCEntityService)p.appctx.getBean("JfcparticipantSettingEntityService");
        JfcparticipantSettingMeta meta = new JfcparticipantSettingMeta();
        DetachedCriteria criteria = DetachedCriteria.forClass(meta.entityClass());
        criteria.eq(meta.id, flowId);
        List<JfcparticipantSetting> list = entityService.find(criteria);
        if (!list.isEmpty()) {
            jfcparticipantSetting = (JfcparticipantSetting) list.get(0);
            cman.putJfcparticipantSetting(jfcparticipantSetting);
            return jfcparticipantSetting;
        }
        return null;
    }

    public String getParticipantLayerId(String modelname, String pkey, String participantId, ActionParameter p) {
        JfcparticipantSetting jfcparticipantSetting = getJfcparticipantSetting(modelname, pkey, p);
        if (jfcparticipantSetting == null) {
    		return participantId;
        }
        $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants = jfcparticipantSetting.getParticipants();
        for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participant : participants) {
            if (participantId.equals(participant.getParticipantIdAsInteger().toString())) {
                return participant.getLayerId();
            }
        }
        return participantId;
    }

    /**
     * Get participants.
     */
    public $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] getParticipants(String modelname, String pkey, String layerId, ActionParameter p) {
        JfcparticipantSetting jfcparticipantSetting = getJfcparticipantSetting(modelname, pkey, p);
        if (jfcparticipantSetting == null) {
            return new $block{jfcpackagename}.model.jfcparticipant_setting.Participants[0];
        }
        if (StringUtils.isBlank(layerId)) {
            return jfcparticipantSetting.getParticipants();
        } else {
            List<$block{jfcpackagename}.model.jfcparticipant_setting.Participants> participantList = new ArrayList<$block{jfcpackagename}.model.jfcparticipant_setting.Participants>();
            $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants = jfcparticipantSetting.getParticipants();
            for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participant : participants) {
                if (layerId.equals(participant.getLayerId())) {
                    participantList.add(participant);
                }
            }
            return participantList.toArray(new $block{jfcpackagename}.model.jfcparticipant_setting.Participants[0]);
        }
    }

    /**
     * {@link Function} の拡張インタフェース。
     * 引数を {@link org.hibernate.Session} に固定しています。
     * @param <R> 戻り値の型
     */
    @FunctionalInterface
    public interface SessionConsumer<R>
            extends Function<org.hibernate.Session, R> {
    }

    /**
     * {@link Function} で実装された処理を既存のトランザクションを利用して
     * 実行します(既存トランザクションがなければ新規トランザクションを開始します)。
     * @param <R> 戻り値の型
     * @param function トランザクション内の処理
     * @param p {@link ActionParameter}
     * @return 処理の戻り値
     */
    public static <R> R doTransaction(
            SessionConsumer<R> function, ActionParameter p) {
        TransactionTemplate requiredTransactionTemplate = p.appctx.getBean(
                "RequiredTransactionTemplate", TransactionTemplate.class);
        return requiredTransactionTemplate.execute( status -> {
            org.hibernate.Session session = p.appctx.getBean(
                    "sessionFactory", SessionFactory.class).getCurrentSession();
            return function.apply(session);
        });
    }

    /**
     * Get flow layer.
     */
    public Layer[] getFlowLayers(String modelname, String pkey, ActionParameter p) {
        JfcparticipantSetting jfcparticipantSetting = getJfcparticipantSetting(modelname, pkey, p);
        if (jfcparticipantSetting == null) {
            return new $block{jfcpackagename}.model.jfcflow_setting.Layer[0];
        }
        int flowSettingId = jfcparticipantSetting.getFlowName();
        $block{jfcpackagename}.app.CacheManager cman = $block{jfcpackagename}.app.CacheManager.getInstance(p);
        JfcflowSetting jfcflowSetting = cman.getJfcflowSetting(String.valueOf(flowSettingId));
        if (jfcflowSetting != null) {
            return jfcflowSetting.getLayer();
        }
        String expression =
            "SELECT _jfcflow_setting " +
            "  FROM JfcflowSetting as _jfcflow_setting " +
            " WHERE _jfcflow_setting.id = :flowSettingId ";

        SessionConsumer<Layer[]> function = new SessionConsumer<Layer[]>() {
            /** {@inheritDoc} **/
            @Override
            public Layer[] apply(org.hibernate.Session session) {
                JfcflowSetting jfcflowSetting = null;
                Query query = session.createQuery(expression);
                query.setInteger("flowSettingId", flowSettingId);

                query.setFetchSize(100);
                try (ScrollableResults results = query.scroll()) {
                    while (results.next()) {
                        jfcflowSetting = (JfcflowSetting) results.get(0);
                    }
                    if (jfcflowSetting != null) {
                        cman.putJfcflowSetting(jfcflowSetting);
                        return jfcflowSetting.getLayer();
                    }
                }
                return new $block{jfcpackagename}.model.jfcflow_setting.Layer[0];
            }
        };
        return doTransaction(function, p);
    }

    /**
     * Returns the user name to be set to RuleNode from the flow participant setting.
     */
    public String getRuleNodeUserName(String modelname, String modelpkey, $block{jfcpackagename}.model.jfcparticipant_setting.Participants participants, ActionParameter p) {
        // If the account name can be acquired from the flow participant setting, the account name is set.
        String userId = participants.getParticipantName();
        // It is null if group is set for participant.
        if (userId != null) {
            // BR7945 2014.12.10
            // 代理人を設定するとワークフロー適用モデルの承認者に保存されるため、
            // アカウント名を返す
            // Returns to the account name because it is stored in the approver of the workflow model
            // when setting an agent.
            return userId;
        }
        Integer groupId = participants.getParticipantGroupidAsInteger();
        if (groupId != null) {
            // When a group is set, add "groupid:" to the group ID and return it.
            return GroupidParticipantHeader + groupId;
        }
        // Execute script and return account or group ID.
        return getAdmitUsersOrGroupFromScript(modelname, modelpkey, participants.getParticipantScript(), p);
    }

    /**
     * Return map of workflow Rule from flow participant setting.
     */
    public Map<String, Object> getRuleMap(String modelname, String pkey, $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants, ActionParameter p) {
        Map<String, Object> ruleMap = new HashMap<String, Object>();
        // Get the RuleNode info.
        List<RuleNode> ruleNodeList = new ArrayList<RuleNode>();

        boolean hasConcurrenceNode = false;
        Map<String, String> users = new TreeMap<String, String>();   // Holds the account name of the approver for each node.
        Map<String, String> proxies = new TreeMap<String, String>(); // Holds the account name of the proxy for each node

        for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participant : participants) {
            // Add approver
            String user = users.get(participant.getLayerId());
            if (user == null) {
                users.put(participant.getLayerId(), getRuleNodeUserName(modelname, pkey, participant, p));
            } else {
                // If there are multiple approvers, they are separated by "|".
                users.put(participant.getLayerId(),
                          user + "|" + getRuleNodeUserName(modelname, pkey, participant, p));
            }
            // Confirm whether it contains a concurrence node.
            if (CONCURRENCE_NODE_TYPE.equals(participant.getNodeType())) {
                hasConcurrenceNode = true;
            }
        }
        StringBuilder admitUsersAndProxies = null;
        for (Iterator it = users.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry)it.next();
            String node = (String)entry.getKey();
            int nodeId = Integer.valueOf(node);

            admitUsersAndProxies = new StringBuilder();
            admitUsersAndProxies.append((String)entry.getValue());
            ruleNodeList.add(new RuleNode(nodeId, admitUsersAndProxies.toString()));
        }
        ruleMap.put("rule", RuleNodeUtil.createRule(ruleNodeList));

        // Get the approval number setting of the concurrence node.
        if (hasConcurrenceNode) {
            StringBuilder conditionName = null;
            for (Layer layer : getFlowLayers(modelname, pkey, p)) {
                if (CONCURRENCE_NODE_TYPE.equals(String.valueOf(layer.getNodeType()))) {
                    conditionName = new StringBuilder();
                    // 2009.11.04 Restore old code when deleting "node" caused variable replacement to stop.
                    conditionName.append("node")
                        .append(String.valueOf(layer.getLayerId()))
                        .append("cond");
                    ruleMap.put(conditionName.toString(), String.valueOf(layer.getApprovalNum()));
                }
            }
        }
        return ruleMap;
    }

    /**
     * Returns the user account name to be approved.
     * If a proxy is set for the target account, return the account name of the proxy agent.
     */
    public String getUsername(String modelname, String userId, ActionParameter p) {
    	final String usernameCacheKey = "jfcWorkflowUsername_" + modelname + "_" + userId;
    	String username = (String) p.request.getAttribute(usernameCacheKey);
    	if (username != null) {
    		return username;
    	}
        String proxyUserId = getProxyByUserId(modelname, userId, p);
        if (StringUtils.isBlank(proxyUserId)) {
        	username = userId;
        } else {
        	username = proxyUserId;
        }
        p.request.setAttribute(usernameCacheKey, username);
        return username;
    }

    public final boolean GET_PROXY = true; // In the case of a proxy user search.
    public final boolean GET_USER = false; // In the case of a mandator user search.

    /**
     * Get the proxy and mandator from the proxy setting info.
     */
    public JfcproxySetting[] getJfcproxySetting(String modelname, String userid, boolean isProxySearch, ActionParameter p) {
        final String jfcWorkflowCacheKey = JFCWORKFLOW_PROXY_SETTING_CACHE_PREFIX + modelname + "_" + userid + "_" + isProxySearch;
        JfcproxySetting[] jfcproxySettingCache = (JfcproxySetting[]) p.request.getAttribute(jfcWorkflowCacheKey);
        if (jfcproxySettingCache != null) {
           return jfcproxySettingCache;
        }
        List<JfcproxySetting> jfcproxySettingList = getJfcproxySettings(p);
        if (jfcproxySettingList != null && jfcproxySettingList.size() == 0) {
            jfcproxySettingCache = new JfcproxySetting[0];
            p.request.setAttribute(jfcWorkflowCacheKey, jfcproxySettingCache);
            return jfcproxySettingCache;
        }
        List<JfcproxySetting> jfcproxySettings = new ArrayList<>(); 
        for (JfcproxySetting jfcproxySetting : jfcproxySettingList) {
            if (StringUtils.isNotBlank(modelname) && StringUtils.isNotBlank(jfcproxySetting.getModelname())) {
                if (!jfcproxySetting.getModelname().equals(modelname)) {
                    continue;
                }
            }
            if (isProxySearch) {
                if (userid.equals(jfcproxySetting.getUserid())) {
                    jfcproxySettings.add(jfcproxySetting);
                }
            } else {
                if (userid.equals(jfcproxySetting.getProxyUserid())) {
                    jfcproxySettings.add(jfcproxySetting);
                }
                if (jfcproxySettings.size() == 0) {
                    JfcproxySetting[] jfcproxySettingProxyGroups = getJfcproxyGroupid(modelname, userid, p);
                    if (jfcproxySettingProxyGroups != null) {
                        jfcproxySettings.addAll(Arrays.asList(jfcproxySettingProxyGroups));
                    }
                }
            }
        }
        jfcproxySettingCache = jfcproxySettings.toArray(new JfcproxySetting[0]);
        p.request.setAttribute(jfcWorkflowCacheKey, jfcproxySettingCache);
        return jfcproxySettingCache;
    }

    public List<JfcproxySetting> getJfcproxySettings(ActionParameter p) {
        @SuppressWarnings("unchecked")
        List<JfcproxySetting> jfcproxySettings = (List<JfcproxySetting>) p.request.getAttribute(JFCWORKFLOW_PROXY_SETTINGS_CACHE);
        if (jfcproxySettings != null) {
           return jfcproxySettings;
        }
        StringBuilder expressionBase = new StringBuilder();
        expressionBase.append("SELECT _jfcproxy_setting ");
        expressionBase.append("  FROM JfcproxySetting as _jfcproxy_setting ");

        SessionConsumer<List<JfcproxySetting>> function
                = new SessionConsumer<List<JfcproxySetting>>() {
            /** {@inheritDoc} **/
            @Override
            public List<JfcproxySetting> apply(org.hibernate.Session session) {
                ScrollableResults results = null;
                List<JfcproxySetting> jfcproxySettings = new ArrayList<JfcproxySetting>();
                try {
                    StringBuilder expression = new StringBuilder();
                    Query query = session.createQuery(expressionBase.toString());
                    query.setFetchSize(100);
                    results = query.scroll();
                    while (results.next()) {
                        jfcproxySettings.add((JfcproxySetting) results.get(0));
                    }
                    p.request.setAttribute(JFCWORKFLOW_PROXY_SETTINGS_CACHE, jfcproxySettings);
                    return jfcproxySettings;
                } catch(Exception e) {
                    logger.error("Exception", e);
                    throw new RuntimeException(e);
                } finally {
                    if (results != null) {
                        results.close();
                    }
                }
            }
        };
        return doTransaction(function, p);
    }

    /**
     * Get proxy group from proxy setting info.
     */
    public JfcproxySetting[] getJfcproxyGroupid(String modelname, String userid, ActionParameter p) {
        int[] groupids = getGroupidByUserid(userid, p);
        if (groupids == null || groupids.length == 0) {
            return null;
        }
        // A proxy is a condition to get a mandator from a proxy group.
        // (:groupid0,:groupid1,...)
        String bindName = "groupid"; // the bind variable name of group.
        HashMap<String, Integer> groupParam = new HashMap<String, Integer>();
        StringBuilder groupQuery = new StringBuilder();
        groupQuery.append(":").append(bindName).append("0");
        groupParam.put(bindName+"0", Integer.valueOf(groupids[0]));
        for (int i=1;i<groupids.length;i++) {
            groupQuery.append(",:").append(bindName).append(i);
            groupParam.put(bindName+i, Integer.valueOf(groupids[i]));
        }
        StringBuilder expressionBase = new StringBuilder();
        expressionBase.append("SELECT _jfcproxy_setting ");
        expressionBase.append("  FROM JfcproxySetting as _jfcproxy_setting ");
        expressionBase.append(" WHERE _jfcproxy_setting.proxyGroupid_ IN (").append(groupQuery.toString()).append(")");

        List<JfcproxySetting> jfcproxySettingList = new ArrayList<JfcproxySetting>();
        SessionConsumer<JfcproxySetting[]> function
                = new SessionConsumer<JfcproxySetting[]>() {
            /** {@inheritDoc} **/
            @Override
            public JfcproxySetting[] apply(org.hibernate.Session session) {
                StringBuilder expression = new StringBuilder();
                expression.append(expressionBase.toString());
                if (modelname != null) {
                    expression.append(" AND (_jfcproxy_setting.modelname_ = :modelname ");
                    expression.append("  OR _jfcproxy_setting.modelname_ IS NULL) ");
                }
                Query query = session.createQuery(expression.toString());
                // Set the group ID which you belong to.
                for (String key : groupParam.keySet()) {
                    query.setInteger(key, groupParam.get(key));
                }

                if (modelname != null) {
                    query.setString("modelname", modelname);
                }
                query.setFetchSize(100);
                try (ScrollableResults results = query.scroll()) {
                    while (results.next()) {
                        jfcproxySettingList.add((JfcproxySetting) results.get(0));
                    }
                    return jfcproxySettingList.toArray(new JfcproxySetting[0]);
                }
            }
        };
        return doTransaction(function, p);
    }

    /**
     * Return the proxy account which is set to the argument.
     */
    public String getProxyByUserId(String modelname, String userid, ActionParameter p) {
        JfcproxySetting[] jfcproxySettings = getJfcproxySetting(modelname, userid, GET_PROXY, p);
        if (jfcproxySettings == null || jfcproxySettings.length == 0) {
            return "";
        }
        for (JfcproxySetting jfcproxySetting : jfcproxySettings) {
            if (StringUtils.isNotBlank(jfcproxySetting.getProxyUserid())) {
                return jfcproxySetting.getProxyUserid();
            } else if (jfcproxySetting.getProxyGroupidAsInteger() != null) {
                return GroupidParticipantHeader + String.valueOf(jfcproxySetting.getProxyGroupid());
            }
        }
        return "";
    }

    /**
     * Return the mandator from proxy.
     * If there is no proxy, return userid (logged on user) as it is.
     */
    public String getUserIdByProxy(String modelname, String userid, ActionParameter p) {
        JfcproxySetting[] jfcproxySettings = getJfcproxySetting(modelname, userid, GET_USER, p);
        if (jfcproxySettings == null || jfcproxySettings.length == 0) {
            return userid;
        }
        return jfcproxySettings[0].getUserid();
    }
  
    /**
     * Return the mandator from proxy.
     * If there is no proxy, return userid (logged on user) as it is.
     */
    public String getMandatorByProxy(String modelname, String modelpkey, String layerId, String userid, ActionParameter p) {
        JfcproxySetting[] jfcproxySettings = getJfcproxySetting(modelname, userid, GET_USER, p);
        if (jfcproxySettings == null || jfcproxySettings.length == 0) {
            return userid;
        }
        $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants = getParticipants(modelname, modelpkey, p);
        for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participant : participants) {
            if (!participant.getLayerId().equals(layerId)) {
                continue;
            }
            String mandator;
            for (JfcproxySetting jfcproxySetting : jfcproxySettings) {
                mandator = jfcproxySetting.getUserid();
                if (mandator.equals(participant.getParticipantName())) {
                    return mandator;
                }
            }
        }
        return userid;
    }

    /**
     * Return the workflow template file name from the participant setting info.
     */
    public String getTemplateFilename($block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants) {
        StringBuilder templateFilename = new StringBuilder();
        String layerId = null;

        for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participant : participants) {
            if (participant.getLayerId().equals(layerId)) {
                // if the same layer ID, go to the next level.
                continue;
            }
            // The node delimiter is "-".
            if (templateFilename.length() > 0) {
                templateFilename.append("-");
            }
            // When the node type is "1", it is a sequential node, in case of "2" it is a concurrency node.
            if (SEQUENCE_NODE_TYPE.equals(participant.getNodeType())) {
                templateFilename.append("s");
            } else {
                templateFilename.append("c");
            }
            layerId = participant.getLayerId();
        }
        return templateFilename.toString();
    }

    /**
     * Return the workflow name from the participant info.
     */
    public String getFlowname($block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants) {
        StringBuilder flowname = new StringBuilder();
        String layerId = null;

        for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participant : participants) {
            if (participant.getLayerId().equals(layerId)) {
                // if the same layer ID, go to the next level.
                continue;
            }
            // The node delimiter is "_".
            if (flowname.length() > 0) {
                flowname.append("_");
            }
            // When the node type is "1", it is a sequential node, in case of "2" it is a concurrency node.
            if (SEQUENCE_NODE_TYPE.equals(participant.getNodeType())) {
                flowname.append("S");
            } else {
                flowname.append("C");
            }
            layerId = participant.getLayerId();
        }
        flowname.append("_Flow");
        return flowname.toString();
    }

    /**
     * Get the instance of workflow service.
     */
    public WorkflowService getWorkflowService(String modelname, String pkey, ActionParameter p) {
        // Get the participant info from workflow definition.
        $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants = getParticipants(modelname, pkey, p);
        if (participants == null || participants.length == 0) {
            logger.error("modelname:["+modelname+"], pkey:["+pkey+"] jfcparticipant_setting data does not exist.");
            throw new WfParticipantNotFoundException();
        }
        // Create rule node map from partipant info.
        Map<String, Object> map = getRuleMap(modelname, pkey, participants, p);
        ApplicationContextFileGenerator acgen = null;
        FileSystemXmlApplicationContext ctx = null;
        try {
            WorkflowSettingUtil workflowSettingUtil = WorkflowSettingUtil.getInstance();

            acgen = new ApplicationContextFileGenerator();
            acgen.setTemplateFilePath(workflowSettingUtil.getFlowDefinitionOutputDirectory()+File.separator);
            acgen.setGenerateFilePath(workflowSettingUtil.getFlowDefinitionOutputDirectory()+File.separator);

            String templateFilename = getTemplateFilename(participants);
            if (!new File(acgen.getGenerateFilePath(),
                    templateFilename + ".xml").exists()) {
                createPatterntTemplateFile(p);
            }
            //String templateFilename = getTemplateFilename(participants);
            //String filename = acgen.generate("s-s", map); // template name
            // Get workflow template file name from participant info and generate workflow configuration file.
            // Even when specifying with an absolute path, "/" is removed and relative path designation is done, 
            // so "file:" was added to the beginning of the file name so that it is treated as an absolute path.
            ctx = new FileSystemXmlApplicationContext("file:" + workflowSettingUtil.getFlowDefinitionOutputDirectory() +
                                                       File.separator + acgen.generate(templateFilename, map));

            WorkflowService wservice = (WorkflowService)ctx.getBean("workflowService");
            wservice.setApplicationContext(ctx);
            wservice.setFlowname(modelname, getFlowname(participants));
            return wservice;
        } catch (Exception e) {
            logger.error("Exception", e);
            throw new RuntimeException(e);
        } finally {
            // Delete workflow definition file.
            acgen.release();
        }
    }

    /**
     * Create pattern template files.
     * @param p {@link ActionParameter}
     * @throws IOException If an I/O error occurs
     */
    private void createPatterntTemplateFile(ActionParameter p)
            throws IOException {
        for (JfcflowSetting jfcflowSetting
                : p.appctx.getBean(JfcflowSettingEntityService.class).find()) {
            JfcflowSettingUtil.setFlowDefinitionFile(jfcflowSetting);
        }
    }

    /**
     * Return if the user is the target of approval.
     */
    public boolean isAdmitUser(String modelname, String pkey, ActionParameter p) {
        String[] currentAdmitUsersAndGroup = getCurrentAdmitUsers(modelname, pkey, p);
        // For users who can select application flows with data created by CSV upload, they treated as approval.
        if (currentAdmitUsersAndGroup != null && currentAdmitUsersAndGroup.length > 0 && JFC_CSV_UPLOAD_DATA.equals(currentAdmitUsersAndGroup[0])) {
            return true;
        }
        String currentNode = getCurrentNode(modelname, pkey, p);
        return isAdmitUser(modelname, pkey, StringUtils.join(currentAdmitUsersAndGroup, ","), currentNode, p);
    }

    public boolean isAdmitUser(String modelname, String pkey, String jfcWorkflowAdmitUser, String jfcWorkflowCurrentNode, ActionParameter p) {
        // If the processing waiting node is NULL, the workflow is terminated.
        if (StringUtils.isBlank(jfcWorkflowCurrentNode)) {
            return false;
        }
        String username = p.user.getUsername();
        String[] currentAdmitUsersAndGroup;
        if (StringUtils.isBlank(jfcWorkflowAdmitUser)) {
            currentAdmitUsersAndGroup = getCurrentAdmitUsersAndGroup(modelname, pkey, p);
        } else {
            currentAdmitUsersAndGroup = jfcWorkflowAdmitUser.split(",");
        }
        if (currentAdmitUsersAndGroup == null || currentAdmitUsersAndGroup.length == 0) {
            return false;
        }
        // For users who can select application flows with data created by CSV upload, they treated as approval.
        if (JFC_CSV_UPLOAD_DATA.equals(currentAdmitUsersAndGroup[0])) {
            return true;
        }
        if (isParticipantNotFound(modelname, pkey, p)) {
        	return false;
        }
        for (String currentAdmitUser : currentAdmitUsersAndGroup) {
            if (!isGroupidParticipant(currentAdmitUser)) {
                // the case of user id.
                String userOrGroup = getUsername(modelname, currentAdmitUser, p);
                if (username.equals(userOrGroup)) {
                    return true;
                } else if (isUserBelongToParticipantGroup(modelname, username, userOrGroup, p)) {
                    return true;
                }
            } else {
                // n the case of group designation, it is confirmed whether the user belongs to the group.
                if (isUserBelongToParticipantGroup(modelname, username, currentAdmitUser, p)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns if the user belongs to the participant group.
     */
    public boolean isUserBelongToParticipantGroup(String modelname, String username, String participantGroup, ActionParameter p) {
    	if (!StringUtils.startsWith(participantGroup, GroupidParticipantHeader)) {
    		return false;
    	}
    	String strJgroupid =
            StringUtils.substringAfter(participantGroup, GroupidParticipantHeader);
        Integer jgroupid = null;
        try {
            jgroupid = Integer.valueOf(strJgroupid);
        } catch(NumberFormatException e) {
        }
        if (jgroupid == null) {
        	return false;
        }
        // When a proxy is set, the group ID to which the mandator belongs is acquired.
        int[] usersGroupIds = getGroupidByUserid(getUserIdByProxy(modelname, username, p), p);
        for (int usersGroupId : usersGroupIds) {
            if (usersGroupId == jgroupid) {
                return true;
            }
        }
        // the group id of the logged-on user.
        usersGroupIds = getGroupidByUserid(username, p);
        for (int usersGroupId : usersGroupIds) {
            if (usersGroupId == jgroupid) {
                return true;
            }
        }
        return false;
    }

    private boolean setJfcworkflowSettings(String modelname, ActionParameter p) {
        // Get the workflow list.
        JfcworkflowSettingP[] _wfs_ps = getJfcworkflowSettings(modelname, p);
        if (_wfs_ps == null || _wfs_ps.length == 0) {
            return false;
        }
        p.request.getSession().setAttribute("jfcworkflowSettings", _wfs_ps);
        return true;
    }

    public JfcworkflowSettingP[] getJfcworkflowSettings(String modelname, ActionParameter p) {
        final String jfcWorkflowCacheKey =
            JFCWORKFLOW_SETTING_CACHE_PREFIX + modelname + "_" + p.user.getUsername() + "_" + Arrays.toString(p.user.getGroupId());
        JfcworkflowSettingP[] jfcworkflowSettings = (JfcworkflowSettingP[]) p.request.getSession().getAttribute(jfcWorkflowCacheKey);
        if (jfcworkflowSettings != null) {
           return jfcworkflowSettings;
        }
        JfcworkflowSettingService jfcworkflowSettingService = 
            (JfcworkflowSettingService)p.appctx.getBean("JfcworkflowSettingService");
        jfcworkflowSettings = jfcworkflowSettingService.getJfcworkflowSettingPArray(modelname, p);
        p.request.getSession().setAttribute(jfcWorkflowCacheKey, jfcworkflowSettings);
        return jfcworkflowSettings;
    }

    public JfcworkflowSettingP[] getJfcworkflowSettingPs(String modelname, Integer flowId, ActionParameter p) {
        final String jfcWorkflowCacheKey = JFCWORKFLOW_SETTING_CACHE_PREFIX + modelname + "_" + flowId;
        JfcworkflowSettingP[] jfcworkflowSettingPs =
            (JfcworkflowSettingP[]) p.request.getSession().getAttribute(jfcWorkflowCacheKey);
        if (jfcworkflowSettingPs != null) {
            return jfcworkflowSettingPs;
        }
        JfcworkflowSettingService jfcworkflowSettingService =
            (JfcworkflowSettingService) p.appctx.getBean("JfcworkflowSettingService");
        jfcworkflowSettingPs = jfcworkflowSettingService.getJfcworkflowSettingPs(modelname, flowId, p);
        p.request.getSession().setAttribute(jfcWorkflowCacheKey, jfcworkflowSettingPs);
        return jfcworkflowSettingPs;
    }

    public JfcworkflowSettingP getJfcworkflowSettingP(String modelname, Integer flowId, ActionParameter p) {
        JfcworkflowSettingP[] jfcworkflowSettingPs = getJfcworkflowSettingPs(modelname, flowId, p);
        if (jfcworkflowSettingPs != null && jfcworkflowSettingPs.length > 0) {
            return jfcworkflowSettingPs[0];
        }
        return null;
    }

    public JfcworkflowSettingP getJfcworkflowSettingP(Integer _flowId, String modelname, String modelpkey, ActionParameter p) {
        Integer flowId = _flowId;
        if (flowId == null) {
            flowId = getFlowId(modelname, modelpkey, p);
        }
        return getJfcworkflowSettingP(modelname, flowId, p);
    }

    public boolean isBackToApplicant(String modelname, Integer flowId, ActionParameter p) {
        JfcworkflowSettingP jfcworkflowSettingP = getJfcworkflowSettingP(modelname, flowId, p);
        if (jfcworkflowSettingP == null) {
            return false;
        }
        RemandType[] remandType = jfcworkflowSettingP.getRemandType();
        if (remandType == null || remandType.length == 0) {
            return false;
        }
        if (remandType[0].getId() == 2) {
            return true;
        }
        return false;
    }

    /**
     * Return the admit user name or group id.
     * For a group, it returns a character string with "jgroupid:" added to the group ID.
     */
    public String[] getCurrentAdmitUsersAndGroup(String modelname, String pkey, ActionParameter p) {
        // If the workflow has ended, there is no approved user.
        if (isWorkflowEndOrReject(modelname, pkey, p)) {
            return null;
        }
        JfcworkstateService jfcworkstateService = 
            (JfcworkstateService) p.appctx.getBean("JfcworkstateService");

        JfcworkstateLp workstateLp = jfcworkstateService.getJfcworkstateLp(modelname, pkey, p);
        $block{jfcpackagename}.model.jfcworkstate_lp.Item[] items = workstateLp.getItem();
        if (items.length == 0) {
            // Save the application flow data to the session if flow event can not be acquired.
            if (!setJfcworkflowSettings(modelname, p)) {
                // When application flow can not be acquired, return null.
                return null;
            }
            String[] admitUsers = { JFC_CSV_UPLOAD_DATA };
            return admitUsers;
        }
        Object o = getModel(modelname, pkey, true, p);
        if (o == null) {
            return null;
        }
        WorkflowService wservice = null;
        try {
            wservice = getWorkflowService(modelname, pkey, p);
            Jfcworkstate startws = jfcworkstateService.getStartJfcworkstate(modelname, pkey, p);
            Integer fid = (startws != null) ? startws.getFlowidAsInteger() : null;
            JfcworkflowSettingP wfs_p = getJfcworkflowSettingP(modelname, fid, p);
            String createuser = items[0].getUsername()[0].getId();
            $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] ps = getParticipants(modelname, pkey, p);
            String flowId = wservice.entry(modelname, pkey, createuser, ps.length, createScript(wfs_p));
            String backId = getLeastRecentlyAdmitLayerid(items);

            for (int i = 1; i < items.length ; i++) {
                logger.debug("flowId:["+flowId+"], username:["+items[i].getUsername()[0].getId()+"], event:["+items[i].getEvent()+"]");
                //System.out.println("flowId:["+flowId+"], username:["+items[i].getUsername()[0].getId()+"], event:["+items[i].getEvent()+"]");
                //wservice.setStatus(flowId, items[i].getUsername()[0].getId(), WorkEvent.valueOf(items[i].getEvent()));
                wservice.redo(flowId, items[i].getUsername()[0].getId(), WorkEvent.valueOf(items[i].getEvent()), modelname, o, backId);
            }
            // Get the admit user from workflow setting.
            String[] admitUsers = wservice.getCurrentAdmitUsers(flowId);
            // Get the proxy user.
            //String[] proxyUsers = wservice.getCurrentProxyUsers(flowId);
            // The list holding approve user and proxy user.
            List<String> admitUsersList = new ArrayList<String>();

            // In case of application node.
            if (APPLICATION_NODE_NAME.equals(wservice.getCurrentNode(flowId))) {
                boolean isApplicationNodeSequence = false;
                boolean isSetGroupidInApplicationNode = false;

                for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participants : getParticipants(modelname, pkey, p)) {
                    // If the layer ID is other than 1 it is not an application node.
                    if (!"1".equals(participants.getLayerId())) {
                        break;
                    }
                    //System.out.println("participant groupid:["+String.valueOf(participants.getParticipantGroupid())+"]");
                    // Check if the node type is a concurrence node.
                    if (SEQUENCE_NODE_TYPE.equals(participants.getNodeType())) {
                        isApplicationNodeSequence = true;
                    }
                    // Confirm that a group is specified on the application node.
                    if (participants.checkParticipantGroupid()) {
                        isSetGroupidInApplicationNode = true;
                        break;
                    }
                }
                // When a group is set up in sequential application node, make data creator available for application.
                if (isApplicationNodeSequence && isSetGroupidInApplicationNode) {
                    admitUsersList.add(createuser);
                    return admitUsersList.toArray(new String[0]);
                }
            }
            // If it is not an application node, return the approval target user.
            if (admitUsers != null && admitUsers.length > 0) {
                admitUsersList.addAll(Arrays.asList(admitUsers));
                return admitUsersList.toArray(new String[0]);
            }
        } catch (WfParticipantNotFoundException e) {
        } catch (Exception e) {
            logger.error("Exception", e);
            throw new RuntimeException(e);
        } finally {
            if (wservice != null) {
                wservice.release();
            }
        }
        return null;
    }

    /**
     * Returns the user name to be approved.
     */
    public String[] getCurrentAdmitUsers(String modelname, String pkey, ActionParameter p) {
        Object o = getModel(modelname, pkey, true, p);
        if (o == null) {
            return null;
        }
        String admitUser = null;
        try {
            Class c = o.getClass();
            Method getAdmitUserMethod = c.getMethod("getJfcWorkflowAdmitUser", (Class[]) null);
            admitUser = (String) getAdmitUserMethod.invoke(o, (Object[]) null);
        } catch(Exception e) {
            logger.error("Exception", e);
            throw new RuntimeException(e);
        }
        if (StringUtils.isNotBlank(admitUser)) {
            return admitUser.split(",");
        }
        // If the approval target person can not be obtained from the workflow model, 
        // re-run the workflow to acquire the approval target person.
        return getCurrentAdmitUsersAndGroup(modelname, pkey, p);
    }

    private String getLeastRecentlyAdmitLayerid($block{jfcpackagename}.model.jfcworkstate_lp.Item[] items) {
        String backId = null;
        if (items == null || items.length == 0) {
            return backId;
        }
        int size = items.length;
        for (int i=size-1; i>=0; i--) {
            WorkEvent event = WorkEvent.valueOf(items[i].getEvent());
            if (event == WorkEvent.Admit || event == WorkEvent.Application) {
                backId = items[i].getProcessedLayerid();
                break;
            }
        }
        return backId;
    }

    public void action(String modelname, String pkey, String username, String event, ActionParameter p) {
        logger.debug(">> action "+modelname+" "+pkey+" "+username+" "+event);
        Object o = getModel(modelname, pkey, true, p);
        if (o == null) {
            return;
        }
        WorkflowService wservice = null;
        try {
            String flowId = null;

            JfcworkstateService jfcworkstateService = 
                (JfcworkstateService) p.appctx.getBean("JfcworkstateService");

            JfcworkstateLp workstateLp = jfcworkstateService.getJfcworkstateLp(modelname, pkey, p);
            if (workstateLp == null) {
                logger.error(">> action "+modelname+":"+pkey+":"+username+":"+event+"workstateLp null");
                return;
            }
            wservice = getWorkflowService(modelname, pkey, p);
            $block{jfcpackagename}.model.jfcworkstate_lp.Item[] items = workstateLp.getItem();
            if (items != null && items.length > 0) {
                Jfcworkstate startws = jfcworkstateService.getStartJfcworkstate(modelname, pkey, p);
                Integer fid = (startws != null) ? startws.getFlowidAsInteger() : null;
                JfcworkflowSettingP wfs_p = getJfcworkflowSettingP(modelname, fid, p);
                $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] ps = getParticipants(modelname, pkey, p);
                flowId = wservice.entry(modelname, pkey, items[0].getUsername()[0].getId(), ps.length, createScript(wfs_p));
                String backId = getLeastRecentlyAdmitLayerid(items);
                String layerId = "";
                String comment = "";
                for (int i = 1; i < items.length ; i++) {
                    logger.debug("flowId:["+flowId+"], username:["+items[i].getUsername()[0].getId()+"], event:["+items[i].getEvent()+"]");
                    //wservice.setStatus(flowId, items[i].getUsername()[0].getId(), WorkEvent.valueOf(items[i].getEvent()));
                    layerId = wservice.getCurrentNode(flowId);
                    comment = items[i].getComment();
                    wservice.redo(flowId, items[i].getUsername()[0].getId(), WorkEvent.valueOf(items[i].getEvent()), modelname, o, backId);
                    wservice.showStatus(flowId);
                }
                // When the flow ends, "End" is registered in the workflow state table and the workflow is ended.
                if (wservice.isFlowEnd(flowId) || WorkEvent.End.toString().equals(event)) {
                    Jfcworkstate jfcworkstate = new Jfcworkstate();
                    jfcworkstate.setFlowid(fid);
                    jfcworkstate.setModelname(modelname);
                    jfcworkstate.setModelpkey(pkey);
                    jfcworkstate.setUsername(p.user.getUsername());
                    jfcworkstate.setEvent(WorkEvent.End.toString());
                    jfcworkstate.setComment(comment);
                    String processedLayerId = null;
                    if (WorkEvent.End.toString().equals(event)) {
                        // BR8220 2014.12.17 -> BR9911 2017.7.13
                        // When the decision button is pushed in the middle of the node, acquire and register the final flow participant ID.
                        processedLayerId = getLastLayerId(modelname, pkey, p);
                    } else {
                        // 2009.09.16
                        // When the workflow ends, the flow participant ID of the approve user is registered in the processed layer ID.
                        processedLayerId = getParticipantId(modelname, pkey, layerId, p);
                    }
                    jfcworkstate.setProcessedLayerid(processedLayerId);
                    jfcworkstateService.insertJfcworkstate(jfcworkstate, p);
                    p.request.setAttribute("__jfc_workflow_end_"+modelname+"_"+pkey, Boolean.TRUE);//2014.08.30
                }
            }
        } catch (WfParticipantNotFoundException e) {
        } catch (Exception e) {
            logger.error("Exception", e);
            throw new RuntimeException(e);
        } finally {
            if (wservice != null) {
                wservice.release();
            }
        }
    }

    /**
     * Return the node name waiting to be processed.
     */
    public String getCurrentNode(String modelname, String pkey, ActionParameter p) {
        // 2009.09.16
        // Fix to return jfcworkstate processing waiting layer ID.
        String currentNode = getCurrentJfcworkstateWaitLayerid(modelname, pkey, p);
        // When the process waiting layer ID can not be obtained from the flow state model, 
        // the flow is re-run for obtaining compatibility with the past data.
        if (currentNode == null) {
            Object o = getModel(modelname, pkey, true, p);
            if (o == null) {
                return null;
            }
            currentNode = getCurrentNodeByRedoWorkflow(modelname, pkey, p);
        }
        return currentNode;
    }

    /**
     * Returns the latest data of the column name specified by the argument from the flow state model.
     */
    public String getCurrentJfcworkstateData(String modelname, String pkey, String columnName, ActionParameter p) {
        Object o = getCurrentJfcworkstateDataAsObject(modelname, pkey, columnName, p);
        if (o != null) {
            return o.toString();
        }
        return null;
    }

    public java.sql.Timestamp getCurrentJfcworkstateDataAsTimestamp(String modelname, String pkey, String columnName, ActionParameter p) {
        Object o = getCurrentJfcworkstateDataAsObject(modelname, pkey, columnName, p);
        if (o != null) {
            if (o instanceof java.sql.Timestamp) {
                return (java.sql.Timestamp)o;
            } else if (o instanceof java.sql.Date) {
                java.sql.Date d = (java.sql.Date)o;
                return new java.sql.Timestamp(d.getTime());
            } else if (o instanceof java.sql.Time) {
                java.sql.Time t = (java.sql.Time)o;
                return new java.sql.Timestamp(t.getTime());
            }
        }
        return null;
    }

    public Jfcworkstate getCurrentJfcworkstate(String modelname, String pkey, ActionParameter p) {
        jp.jasminesoft.jfc.service.JFCEntityService entityService = (jp.jasminesoft.jfc.service.JFCEntityService)p.appctx.getBean("JfcworkstateEntityService");
        JfcworkstateMeta meta = new JfcworkstateMeta();
        DetachedCriteria criteria = DetachedCriteria.forClass(meta.entityClass());
        criteria.eq(meta.modelname, modelname);
        criteria.eq(meta.modelpkey, pkey);
        criteria.addOrder(Order.desc(meta.id.name()));
        List<Jfcworkstate> list = entityService.find(criteria);
        if (list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }

    public String getCurrentJfcworkstateWaitLayerid(String modelname, String pkey, ActionParameter p) {
    	Jfcworkstate jfcworkstate = getCurrentJfcworkstate(modelname, pkey, p);
        if (jfcworkstate == null) {
            return null;
        }
        return jfcworkstate.getWaitLayerid();
    }

    public Object getCurrentJfcworkstateDataAsObject(String modelname, String pkey, String columnName, ActionParameter p) {
    	Jfcworkstate jfcworkstate = getCurrentJfcworkstate(modelname, pkey, p);
    	if (jfcworkstate == null) {
    		return null;
    	}
        try {
            Class c = jfcworkstate.getClass();
            Object ret;
            String methodName;
            if ("processedLayerid_".equals(columnName)) {
            	methodName = "getProcessedLayerid";
            } else if ("insertDate_".equals(columnName)) {
            	methodName = "getInsertDate";
            } else {
            	return null;
            }
            Method getMethod = c.getMethod(methodName, (Class[]) null);
            Object o = getMethod.invoke(jfcworkstate, (Class[]) null);
            return o;
        } catch(RuntimeException e) {
            throw e;
        } catch(NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch(IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch(Exception e) {
            logger.error("Exception", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Re-run workflow and return node name waiting to be processed.
     */
    public String getCurrentNodeByRedoWorkflow(String modelname, String pkey, ActionParameter p) {
        WorkflowService wservice = null;
        try {
            JfcworkstateService jfcworkstateService = 
                (JfcworkstateService) p.appctx.getBean("JfcworkstateService");

            JfcworkstateLp workstateLp = jfcworkstateService.getJfcworkstateLp(modelname, pkey, p);
            $block{jfcpackagename}.model.jfcworkstate_lp.Item[] items = workstateLp.getItem();
            if (items.length == 0) {
                return null;
            }
            wservice = getWorkflowService(modelname, pkey, p);
            Object o = getModel(modelname, pkey, true, p);
            if (o == null) {
                return null;
            }
            String createuser = items[0].getUsername()[0].getId();
            Jfcworkstate startws = jfcworkstateService.getStartJfcworkstate(modelname, pkey, p);
            Integer fid = (startws != null) ? startws.getFlowidAsInteger() : null;
            JfcworkflowSettingP wfs_p = getJfcworkflowSettingP(modelname, fid, p);
            $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] ps = getParticipants(modelname, pkey, p);
            String flowId = wservice.entry(modelname, pkey, createuser, ps.length, createScript(wfs_p));
            String backId = getLeastRecentlyAdmitLayerid(items);

            for (int i = 1; i < items.length ; i++) {
                logger.debug("flowId:["+flowId+"], username:["+items[i].getUsername()[0].getId()+"], event:["+items[i].getEvent()+"]");
                wservice.redo(flowId, items[i].getUsername()[0].getId(), WorkEvent.valueOf(items[i].getEvent()), modelname, o, backId);
            }
            return wservice.getCurrentNode(flowId);
        } catch (WfParticipantNotFoundException e) {        	
        } catch (Exception e) {
            logger.error("Exception", e);
            throw new RuntimeException(e);
        } finally {
            if (wservice != null) {
                wservice.release();
            }
        }
        return null;
    }

    /**
     * Return the processed layer ID.
     */
    public String getProcessedLayerId(String modelname, String pkey, ActionParameter p) {
        return getCurrentJfcworkstateData(modelname, pkey, "processedLayerid_", p);
    }

    /**
     * Return the node name of the participants by layer ID.
     */
    public String getParticipantNodenameFromDB(String modelname, String pkey, String layerId, ActionParameter p) {
        if (StringUtils.isBlank(layerId)) {
            return "";
        }
        List<$block{jfcpackagename}.model.jfcparticipant_setting.Participants> sameLayerParticipants = new ArrayList<$block{jfcpackagename}.model.jfcparticipant_setting.Participants>();
        $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants = getParticipants(modelname, pkey, null, p);
        for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participant : participants) {
            if (layerId.equals(participant.getLayerId())) {
            	sameLayerParticipants.add(participant);
            }
        }
        if (sameLayerParticipants.size() == 0) {
            logger.debug("not found layerId "+layerId);
            return null;
        }
        if (sameLayerParticipants.size() == 1) {
        	return sameLayerParticipants.get(0).getNodeName();
        }
        // In case of concurrence node, compare by flow participant ID.
        Jfcworkstate jfcworkstate = getCurrentJfcworkstate(modelname, pkey, p);
        for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participant : sameLayerParticipants) {
    		String participantId;
    		if (WorkEvent.End.toString().equals(jfcworkstate.getEvent())) {
    			participantId = jfcworkstate.getProcessedLayerid();
    		} else {
    			participantId = jfcworkstate.getParticipantid();
    		}
    		if (participantId == null) {
    			continue;
    		}
            if (participantId.equals(String.valueOf(participant.getParticipantId()))) {
    			return participant.getNodeName();
    		}
        }
        // In the case of remand, the first node name of the concurrence node is returned.
        return sameLayerParticipants.get(0).getNodeName();
    }

    /**
     * Return the node name of the participants by layer ID.
     */
    public String getParticipantNodename(String modelname, String pkey, String layerId, ActionParameter p) {
        // String layerId = getProcessedLayerId(modelname, pkey, p);
        if (getFlowId(modelname, pkey, p) == null) {
            // When flowid is null, data is not registered.
            return "";
        } else if ("0".equals(layerId)) {
            // When the layer ID is 0, new registration is performed.
            String nodename = getParticipantNodenameFromDB(modelname, pkey, layerId, p);
            if (nodename != null) {
                return nodename;
            }
            return JFCUtils.getRValue("__jfc_common.workflow.nodename.start", p.locale);
        }
        String currentWaitLayerId = getCurrentJfcworkstateWaitLayerid(modelname, pkey, p);
        if (currentWaitLayerId == null) {
            // If the waiting layer ID is NULL, acquire the final event which is decisive, rejected, 
            // canceled and set the name.
            String lastEvent = getLastWorkflowEvent(modelname, pkey, p);
            if (WorkEvent.Reject.toString().equals(lastEvent)) {
                return JFCUtils.getRValue("__jfc_common.workflow.nodename.reject", p.locale);
            } else if (WorkEvent.Cancel.toString().equals(lastEvent)) {
                return JFCUtils.getRValue("__jfc_common.workflow.nodename.cancel", p.locale);
            } else if (WorkEvent.End.toString().equals(lastEvent)) {
                String nodename = getParticipantNodenameFromDB(modelname, pkey, layerId, p);
                if (nodename != null) {
                    return nodename;
                }
                return JFCUtils.getRValue("__jfc_common.workflow.End", p.locale);
            } else {
                // Returning the empty character in cases other than rejection, cancellation, settlement.
                return "";
            }
        }
        return getParticipantNodenameFromDB(modelname, pkey, layerId, p);
    }

    /**
     * Get the final event of the workflow.
     */
    public String getLastWorkflowEvent(String modelname, String pkey, ActionParameter p) {
        JfcworkstateService jfcworkstateService = 
            (JfcworkstateService) p.appctx.getBean("JfcworkstateService");

        JfcworkstateLp workstateLp = jfcworkstateService.getJfcworkstateLp(modelname, pkey, p);
        $block{jfcpackagename}.model.jfcworkstate_lp.Item[] items = workstateLp.getItem();
        if (items.length == 0) {
            return null;
        }
        return items[items.length - 1].getEvent();
    }

    public String getParticipantId(String modelname, String pkey, String layerId, ActionParameter p) {
    	return getParticipantId(modelname, pkey, layerId, null, p);
    }

    /**
     * Return the processed participant ID.
     */
    public String getParticipantId(String modelname, String pkey, String layerId, String username, ActionParameter p) {
        // Because there are cases where the logged-on user is a proxy, get the mandator from the proxy model.
        // If the agent is not set up, use the logon user.
    	String userid = p.user.getUsername();
    	if (StringUtils.isNotBlank(username)) {
    		userid = username;
    	}
        String mandator = getMandatorByProxy(modelname, pkey, layerId, userid, p);
        $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants = getParticipants(modelname, pkey, layerId, p);
        for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participant : participants) {
            // When the mandator and the flow participant are equal.
            if (mandator.equals(participant.getParticipantName())) {
                return ((Integer) participant.getParticipantId()).toString();
            }
        }
        // The group ID is checked to see if the logon user belongs to a group.
        // In the case where the proxy is set up, the group to which the mandator belongs is acquired.
        Set<Integer> groupIdSet = new HashSet<Integer>();
        int[] groupids = getGroupidByUserid(mandator, p);
        if (groupids != null && groupids.length > 0) {
            for (int i=0; i<groupids.length; i++) {
                groupIdSet.add(groupids[i]);
            }
            for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participant : participants) {
                if (groupIdSet.contains(participant.getParticipantGroupidAsInteger())) {
                    return ((Integer) participant.getParticipantId()).toString();
                }
            }
        }
        // check script
        for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participant : participants) {
            String s = getAdmitUsersOrGroupFromScript(modelname, pkey, participant.getParticipantScript(), p);
            if (StringUtils.isBlank(s)) {
                continue;
            }
            for (String userOrGroupId : s.split("\\|")) {
                if (mandator.equals(userOrGroupId)) {
                    return ((Integer) participant.getParticipantId()).toString();
                } else if (!isGroupidParticipant(userOrGroupId)) {
                    continue;
                }
                try {
                    String strGroupid = StringUtils.substringAfter(userOrGroupId, GroupidParticipantHeader);
                    Integer groupId = Integer.valueOf(strGroupid);
                    if (groupIdSet.contains(groupId)) {
                        return ((Integer) participant.getParticipantId()).toString();
                    }
                } catch(Exception e) {
                }
            }
        }
        return null;
    }

    /**
     * Return the waiting layer ID.
     */
    public String getWaitLayerid(String modelname, String pkey, String event, ActionParameter p) {
        Object o = getModel(modelname, pkey, true, p);
        if (o == null) {
            return null;
        }
        WorkflowService wservice = null;
        try {
            JfcworkstateService jfcworkstateService = 
                (JfcworkstateService) p.appctx.getBean("JfcworkstateService");

            JfcworkstateLp workstateLp = jfcworkstateService.getJfcworkstateLp(modelname, pkey, p);
            $block{jfcpackagename}.model.jfcworkstate_lp.Item[] items = workstateLp.getItem();
            if (items.length == 0) {
                return null;
            }
            wservice = getWorkflowService(modelname, pkey, p);
            String createuser = items[0].getUsername()[0].getId();
            Jfcworkstate startws = jfcworkstateService.getStartJfcworkstate(modelname, pkey, p);
            Integer fid = (startws != null) ? startws.getFlowidAsInteger() : null;
            JfcworkflowSettingP wfs_p = getJfcworkflowSettingP(modelname, fid, p);
            $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] ps = getParticipants(modelname, pkey, p);
            String flowId = wservice.entry(modelname, pkey, createuser, ps.length, createScript(wfs_p));
            String backId = getLeastRecentlyAdmitLayerid(items);

            for (int i = 1; i < items.length ; i++) {
                logger.debug("flowId:["+flowId+"], username:["+items[i].getUsername()[0].getId()+"], event:["+items[i].getEvent()+"]");
                wservice.redo(flowId, items[i].getUsername()[0].getId(), WorkEvent.valueOf(items[i].getEvent()), modelname, o, backId);
            }
            wservice.redo(flowId, p.user.getUsername(), WorkEvent.valueOf(event), modelname, o, backId);
            return wservice.getCurrentNode(flowId);
        } catch (WfParticipantNotFoundException e) {
        } catch (Exception e) {
            logger.error("Exception", e);
            throw new RuntimeException(e);
        } finally {
            if (wservice != null) {
                wservice.release();
            }
        }
        return null;
    }

    public class ModelnamePrimaryKey {
        private String modelname;
        private String pkey;
        private String createuser;
        private Set<String> currentAdmitUsers;
        private Map<String,Set<String>> groupidUsersMap;

        public ModelnamePrimaryKey(
                String modelname, String pkey, String createuser,
                Map<String,Set<String>> groupidUsersMap)
        {
            this.modelname = modelname;
            this.pkey = pkey;
            this.createuser = createuser;
            this.groupidUsersMap = groupidUsersMap;
        }

        public String getModelname() {
            return modelname;
        }

        public String getPrimaryKey() {
            return pkey;
        }

        public String getCreateuser() {
            return createuser;
        }

        public Set<String> getCurrentAdmitUsers(ActionParameter p) {
            if (currentAdmitUsers != null) {
                return currentAdmitUsers;
            }

            Set<String> _currentAdmitUsers = new HashSet<String>();
            String[] participants = WorkFlowManager.this.getCurrentAdmitUsers(modelname, pkey, p);
            if (participants != null) {
                _currentAdmitUsers.addAll(Arrays.asList(participants));
            }
            currentAdmitUsers = _currentAdmitUsers;
            return _currentAdmitUsers;
        }

        @Override
        public int hashCode() {
            return ((modelname != null) ? modelname.hashCode() : 0) +
                ((pkey != null) ? pkey.hashCode() : 0);
        }

        @Override
        public boolean equals(Object o) {
            if (o == null || !(o instanceof ModelnamePrimaryKey)) {
                return false;
            }
            ModelnamePrimaryKey mpk = (ModelnamePrimaryKey)o;
            return ((modelname == null && mpk.modelname == null) ||
                    (modelname != null && modelname.equals(mpk.modelname))) &&
                   ((pkey == null && mpk.pkey == null) ||
                    (pkey != null && pkey.equals(mpk.pkey)));
        }
    }

    /**
     * Acquires the model name and the primary key of the currently started workflow 
     * created by the specified user.
     *
     * At the same time, it calls getGroupidJfcParticipant.
     *
     * @param c_modelname model name. return all model if it is null.
     * @param c_pkey primary key. return all primary key if it is null.
     * @param c_startuser applicant user. return all applicant user if it is null.
     */
    public List<ModelnamePrimaryKey> getCurrentStartWorkFlow(
            String c_modelname, String c_pkey, String c_startuser)
    {
        Map<String,Set<String>> groupidUsersMap =
            new HashMap<String,Set<String>>();
        List<ModelnamePrimaryKey> list = new ArrayList<ModelnamePrimaryKey>(); 
        return list;
    }

    /**
     * Acquires the model name and the primary key of the workflow 
     * that the specified user currently accepts.
     *
     * At the same time, it calls getGroupidJfcParticipant.
     *
     * @param c_modelname model name. return all model if it is null.
     * @param c_pkey primary key. return all primary key if it is null.
     * @param c_startuser applicant user. return all applicant user if it is null.
     * @param c_admituser admit user. return all admit user if it is null.
     */
    public List<ModelnamePrimaryKey> getCurrentAdmitWorkFlow(
            String c_modelname, String c_pkey, String c_startuser, String c_admituser,
            ActionParameter p)
    {
        // Do not show approval or rejected workflow.
        //boolean isNoOutputEndOrReject = (c_admituser != null);
        boolean isNoOutputEndOrReject = true;

        // Display workflows that have not been started.
        boolean isOutputNoApplication = true;

        return getCurrentAdmitWorkFlow(
                c_modelname, c_pkey, c_startuser, c_admituser,
                isNoOutputEndOrReject, isOutputNoApplication, p);
    }

    public List<ModelnamePrimaryKey> getStartWorkFlow(
            String c_modelname, String c_pkey, String c_startuser, String c_admituser,
            Integer includeStartEvent, ActionParameter p)
    {
        // Do not show approval or rejected workflow.
        boolean isNoOutputEndOrReject = true;

        // Display workflows that have not been started.
        boolean isOutputNoApplication = true;
        if (includeStartEvent != null && includeStartEvent == 2) {
            isOutputNoApplication = false;
        }

        return getCurrentAdmitWorkFlow(
                c_modelname, c_pkey, c_startuser, c_admituser,
                isNoOutputEndOrReject, isOutputNoApplication, p);
    }

    /**
     * Acquires the model name and the primary key of the workflow 
     * that the specified user currently accepts.
     *
     * At the same time, it calls getGroupidJfcParticipant.
     *
     * @param c_modelname model name. return all model if it is null.
     * @param c_pkey primary key. return all primary key if it is null.
     * @param c_startuser applicant user. return all applicant user if it is null.
     * @param c_admituser admit user. return all admit user if it is null.
     */
    public List<ModelnamePrimaryKey> getCurrentAdmitWorkFlowWithoutNoApplication(
            String c_modelname, String c_pkey, String c_startuser, String c_admituser,
            ActionParameter p)
    {
        // Do not show approval or rejected workflow.
        //boolean isNoOutputEndOrReject = (c_admituser != null);
        boolean isNoOutputEndOrReject = true;

        // Display workflows that have not been started.
        boolean isOutputNoApplication = false;

        return getCurrentAdmitWorkFlow(
                c_modelname, c_pkey, c_startuser, c_admituser,
                isNoOutputEndOrReject, isOutputNoApplication, p);
    }

    /**
     * Acquires the model name and the primary key of the workflow 
     * that the specified user currently accepts.
     *
     * At the same time, it calls getGroupidJfcParticipant.
     *
     * @param c_modelname model name. return all model if it is null.
     * @param c_pkey primary key. return all primary key if it is null.
     * @param c_startuser applicant user. return all applicant user if it is null.
     * @param c_admituser admit user. return all admit user if it is null.
     * @param isNoOutputEndOrReject
     * @param isOutputNoApplication
     */
    private List<ModelnamePrimaryKey> getCurrentAdmitWorkFlow(
            String c_modelname, String c_pkey, String c_startuser, String c_admituser, boolean isNoOutputEndOrReject, boolean isOutputNoApplication,
            ActionParameter p)
    {
        Map<String,Set<String>> groupidUsersMap =
            new HashMap<String,Set<String>>();
        List<ModelnamePrimaryKey> list = new ArrayList<ModelnamePrimaryKey>(); 

        // 2009.09.16
        // In order for the logon user to filter the flow included in the flow participant, 
        // add the account name and the group ID to which the logon user belongs as arguments.
        List<ModelnamePrimaryKey> modelname_pkey_list =
            getModelnamePrimaryKeyList(
                   c_modelname, c_pkey, c_startuser, c_admituser, isNoOutputEndOrReject,
                   isOutputNoApplication, groupidUsersMap, p);
        for (ModelnamePrimaryKey mpk : modelname_pkey_list) {
            if (c_admituser == null) {
                list.add(mpk);
                continue;
            }
            Set<String> set = mpk.getCurrentAdmitUsers(p);
            for (String participant : set) {
                if (!isGroupidParticipant(participant)) {
                    // When the flow participant is the user.
                    // BR7945 2014.12.10
                    // Get the proxy user.
                    String username = getUsername(c_modelname, participant, p);
                    if (username.equals(c_admituser) ||
                        isUserBelongToParticipantGroup(mpk.getModelname(), c_admituser, username, p)) {
                        list.add(mpk);
                    }
                } else {
                    // When the flow participant is the group.
                    if (isUserBelongToParticipantGroup(mpk.getModelname(), c_admituser, participant, p)) {
                        list.add(mpk);
                    }
                }
            }
        }
        return list;
    }

    /**
     * Get the model name and primary key.
     */
    private List<ModelnamePrimaryKey> getModelnamePrimaryKeyList(
            String c_modelname, String c_pkey, String c_startuser, String c_admituser,
            boolean isNoOutputEndOrReject, boolean isOutputNoApplication,
            Map<String,Set<String>> groupidUsersMap, ActionParameter p)
    {
        HashMap<String,String> param = new HashMap<String,String>();
        StringBuilder sb = new StringBuilder();
        sb.append("SELECT c.modelname_, c.modelpkey_, c.username_ FROM Jfcworkstate as c"
                 +" WHERE c.event_ = 'Start'");

        // Filter by model name.
        if (c_modelname != null) {
            sb.append(" AND c.modelname_ = :modelname");
            param.put("modelname", c_modelname);
        }

        // Filter by primary key.
        if (c_pkey != null) {
            sb.append(" AND c.modelpkey_ = :modelpkey");
            param.put("modelpkey", c_pkey);
        }

        // Filter by start user.
        if (c_startuser != null) {
            sb.append(" AND c.username_ = :startuser");
            param.put("startuser", c_startuser);
        }

        // Do not show approval or rejected workflow.
        if (isNoOutputEndOrReject) {
            sb.append(" AND NOT EXISTS (SELECT 1 FROM Jfcworkstate d"
                     +" WHERE d.modelname_ = c.modelname_ AND d.modelpkey_ = c.modelpkey_ AND"
                     +" (d.event_ = 'End' OR d.event_ = 'Reject' OR d.event_ = 'Cancel'))");
        }

        // Do not display workflows that have not been started.
        if (!isOutputNoApplication) {
            /*
            // Display only the flow that is being applied. Reapplication will be displayed with remand.
            sb.append(" AND EXISTS (SELECT 1 FROM Jfcworkstate d"
                     +" WHERE d.modelname_ = c.modelname_ AND d.modelpkey_ = c.modelpkey_ AND"
                     +" d.event_ = 'Application')");
            */

            sb.append(" AND NOT EXISTS (");
            sb.append(
                "SELECT 1 " + 
                "  FROM Jfcworkstate as _jfcworkstate " +
                " WHERE _jfcworkstate.modelname_ = c.modelname_ " +
                "   AND _jfcworkstate.modelpkey_ = c.modelpkey_ " +
                "   AND _jfcworkstate.waitLayerid_ = '1' " +
                "   AND _jfcworkstate.id_ = (SELECT max(_jfcworkstate2.id_) " +
                "                                      FROM Jfcworkstate as _jfcworkstate2 " +
                "                                     WHERE _jfcworkstate2.modelname_ = c.modelname_ " +
                "                                       AND _jfcworkstate2.modelpkey_ = c.modelpkey_)");
            sb.append(")");
        }

        // 2009.09.16
        // Filter the flow in which the suspended person is set as a flow participant.
        HashMap<String, Integer> groupParam = new HashMap<String, Integer>();
        final int[] groupids;
        if (c_admituser != null) {
            sb.append(" AND c.flowid_ IN (SELECT _participants.id_ ");
            sb.append("                     FROM $block{jfcpackagename}.model.jfcparticipant_setting.Participants as _participants ");
            sb.append("                    WHERE _participants.participantName_    = :participantName ");
            sb.append("                       OR _participants.participantScript_ IS NOT NULL ");
            param.put("participantName", c_admituser);

            // 2009.11.12
            // If the mandator can obtain it, add the mandator's account as the condition of the flow participant.
            String mandatorUserId = getUserIdByProxy(c_modelname, c_admituser, p);
            if (!c_admituser.equals(mandatorUserId)) {
                sb.append("                       OR _participants.participantName_    = :mandatorUserId ");
                param.put("mandatorUserId", mandatorUserId);
            }

            StringBuilder groupParamName;
            // Get group ID of pending person.
            groupids = getGroupidByUserid(c_admituser, p);
            for (int i=0; i<groupids.length; i++) {
                groupParamName = new StringBuilder();
                groupParamName.append("participantGroupid").append(i);
                sb.append("                   OR _participants.participantGroupid_ = ").append(":").append(groupParamName.toString());
                groupParam.put(groupParamName.toString(), Integer.valueOf(groupids[i]));
            }
            sb.append(")");
        } else {
            groupids = null;
        }

        String expression = sb.toString();
        SessionConsumer<List<ModelnamePrimaryKey>> function
                = new SessionConsumer<List<ModelnamePrimaryKey>>() {
            /** {@inheritDoc} **/
            @Override
            public List<ModelnamePrimaryKey> apply(org.hibernate.Session session) {
                ArrayList<ModelnamePrimaryKey> saa = new ArrayList<ModelnamePrimaryKey>();
                Query query = session.createQuery(expression);
                for (String key : param.keySet()) {
                    query.setString(key, param.get(key));
                }
                // 2009.09.16
                // Set the group ID.
                if (groupids != null && groupids.length > 0) {
                    for (String key : groupParam.keySet()) {
                        query.setInteger(key, groupParam.get(key));
                    }
                }
                query.setFetchSize(100);
                try (ScrollableResults results = query.scroll()) {
                    while (results.next()) {
                        String modelname = (String)results.get(0);
                        String pkey = (String)results.get(1);
                        String createuser = (String)results.get(2);
                        ModelnamePrimaryKey mpk = new ModelnamePrimaryKey(
                                modelname, pkey, createuser, groupidUsersMap);
                        saa.add(mpk);
                    }
                }
                return saa;
            }
        };
        return doTransaction(function, p);
    }

    private Object getModel(String modelname, String pkey, ActionParameter p) {
    	return getModel(modelname, pkey, false, p);
    }
    
    private Object getModel(String modelname, String pkey, boolean lazyFetch, ActionParameter p) {
        StringBuilder modelDataCacheKey = new StringBuilder();
        modelDataCacheKey.append(WorkFlowManager.JFCWORKFLOW_MODELDATA_CACHE_PREFIX).append("_")
            .append(modelname).append("_").append(pkey).append("_lazy_").append(lazyFetch);

        Object modelData = p.request.getAttribute(modelDataCacheKey.toString());
        if (modelData != null) {
        	return modelData;
        }

        // Skip autocalc process.
        StringBuilder skipBeforeShowName = new StringBuilder();
        skipBeforeShowName.append("__jfc_control.skipBeforeShow_").append(modelname).append("_").append(pkey);
        p.request.setAttribute(skipBeforeShowName.toString(), Boolean.TRUE);
        // Retrive the workflow model data.
        try {
            String cappedModelname = StringUtil.capFirst(modelname);
            StringBuilder helperName = new StringBuilder();
            helperName.append(cappedModelname).append("Helper");
            jp.jasminesoft.jfc.app.EntityHelper helper = (jp.jasminesoft.jfc.app.EntityHelper) p.appctx.getBean(helperName.toString());
            StringBuilder serviceName = new StringBuilder();
            serviceName.append(cappedModelname).append("EntityService");
            jp.jasminesoft.jfc.service.JFCEntityService service = (jp.jasminesoft.jfc.service.JFCEntityService) p.appctx.getBean(serviceName.toString());
            // Filter to only the item names necessary when getting the latest data from the workflow model.
            DataBindingContext context = new DataBindingContext();
            context.setTargetItemSet(JFCWORKFLOW_TARGETITEM);
            // BR11285 the cause of "non-threadsafe access to session"
            context.setLazyFetch(lazyFetch);
            modelData = service.findById(helper.getPrimarykey(pkey), context);
            p.request.setAttribute(modelDataCacheKey.toString(), modelData);
            // BR 9995 remove skipBeforeShow Flag.
            p.request.removeAttribute(skipBeforeShowName.toString());
            return modelData;
        } catch (Exception e) {
            logger.error("Exception", e);
        }
        return null;
    }

    /**
     * Returns true if the workflow has been approved or rejected or canceled.
     */
    public boolean isWorkflowEndOrReject(String modelname, String pkey, ActionParameter p) {
        Object o = getModel(modelname, pkey, true, p);
        String createUser = null;
        String admitUser = null;
        String processedLayerId = null;
        String currentNode = null;

        if (o != null) {
            try {
                Class c = o.getClass();
                Method getCreateUserMethod = c.getMethod("getJfcWorkflowCreateUser", (Class[]) null);
                createUser = (String) getCreateUserMethod.invoke(o, (Object[]) null);

                Method getAdmitUserMethod = c.getMethod("getJfcWorkflowAdmitUser", (Class[]) null);
                admitUser = (String) getAdmitUserMethod.invoke(o, (Object[]) null);

                Method getProcessedLayerIdMethod = c.getMethod("getJfcWorkflowProcessedLayerId", (Class[]) null);
                processedLayerId = (String) getProcessedLayerIdMethod.invoke(o, (Object[]) null);

                Method getCurrentNodeMethod = c.getMethod("getJfcWorkflowCurrentNode", (Class[]) null);
                currentNode = (String) getCurrentNodeMethod.invoke(o, (Object[]) null);
            } catch(Exception e) {
                logger.error("Exception", e);
            }

            // If the queuing node name is not empty, it is not terminated.
            if (StringUtils.isNotBlank(currentNode)) {
                return false;
            }
            // if waiting node name and target approver is empty, 
            // and the creator and processed layer ID is not empty, terminate workflow.
            if ((StringUtils.isBlank(currentNode) && StringUtils.isNotBlank(admitUser)) &&
                (StringUtils.isNotBlank(createUser) && StringUtils.isNotBlank(processedLayerId))) {
                return true;
            }
        }

        // If the data of the model can not be acquired or does not match the above conditions, 
        // confirm with the data of the flow event table.
        jp.jasminesoft.jfc.service.JFCEntityService entityService = (jp.jasminesoft.jfc.service.JFCEntityService)p.appctx.getBean("JfcworkstateEntityService");
        JfcworkstateMeta meta = new JfcworkstateMeta();
        DetachedCriteria criteria = DetachedCriteria.forClass(meta.entityClass());
        criteria.add(Restrictions.and(
            Restrictions.eq(meta.modelname.name(), modelname),
            Restrictions.eq(meta.modelpkey.name(), pkey),
            Restrictions.disjunction()
                .add(Restrictions.eq(meta.event.name(), "Cancel"))
                .add(Restrictions.eq(meta.event.name(), "End"))
                .add(Restrictions.eq(meta.event.name(), "Reject"))
        ));
        List<Jfcworkstate> list = entityService.find(criteria);
        if (list.isEmpty()) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Return the flow ID.
     */
    public Integer getFlowId(String modelname, String pkey, ActionParameter p) {
        if (StringUtils.isBlank(modelname) || StringUtils.isBlank(pkey)) {
            return null;
        }
        ServletContext sc = p.request.getServletContext();
        final String jfcWorkflowCacheKey = JFCWORKFLOW_FLOWID_CACHE_PREFIX + modelname + "_" + pkey;
        Integer flowId = (sc != null) ? (Integer) sc.getAttribute(jfcWorkflowCacheKey) : null;
        if (flowId != null) {
           return flowId;
        }
        jp.jasminesoft.jfc.service.JFCEntityService entityService = (jp.jasminesoft.jfc.service.JFCEntityService)p.appctx.getBean("JfcworkstateEntityService");
        JfcworkstateMeta meta = new JfcworkstateMeta();
        DetachedCriteria criteria = DetachedCriteria.forClass(meta.entityClass());
        criteria.eq(meta.modelname, modelname);
        criteria.eq(meta.modelpkey, pkey);
        criteria.eq(meta.event, "Start");
        List<Jfcworkstate> list = entityService.find(criteria);
        if (list.isEmpty()) {
            return null;
        }
        Jfcworkstate jfcworkstate = list.get(0);
        flowId = jfcworkstate.getFlowidAsInteger();
        if (sc != null) {
            sc.setAttribute(jfcWorkflowCacheKey, flowId);
        }
        return flowId;
    }

    /**
     * Return the group ID which the user belongs.
     */
    public int[] getGroupidByUserid(String userid, ActionParameter p) {
		// The group ID to which the userid belongs is cached in session 
		// so as not to execute the read process many times.
		final String key_getGroupidByUserid = "WorkFlowManager.getGroupidByUserid_" + userid;
		int[] usersGroupIds = (int[]) p.request.getSession().getAttribute(key_getGroupidByUserid);
		if (usersGroupIds != null) {
			return usersGroupIds;
		}

		SessionConsumer<int[]> function = new SessionConsumer<int[]>() {
            /** {@inheritDoc} **/
            @Override
            public int[] apply(org.hibernate.Session session) {
                // BR11496 quoteid の有無を制御するため named-query を利用。 
                @SuppressWarnings("unchecked")
                List<Number> list = session.getNamedQuery("getGroupidByUserid")
                        .setString("userid", userid).list();
                int[] jgroupids = ArrayUtils.toPrimitive(list.stream()
                    .map(id -> Integer.valueOf(id.intValue()))
                    .toArray(Integer[]::new));
                p.request.getSession().setAttribute(key_getGroupidByUserid, jgroupids);
                return jgroupids;
            }
        };
        return doTransaction(function, p);
    }

    /**
     * Return the array of participant user.
     */
    public String[] getParticipantUsersArray(String modelname, Set<String> participants, ActionParameter p) {
        Set<String> participantUsers = new TreeSet<String>();
        for (String participant : participants) {
            if (isGroupidParticipant(participant)) {
                continue;
            }
            // BR7945 2014.12.12
            // Considering the proxy.
            String username = getUsername(modelname, participant, p);
            if (isGroupidParticipant(username)) {
                continue;
            }
            participantUsers.add(username);
        }
        return participantUsers.toArray(new String[0]);
    }

    /**
     * Return the array of participant group ID.
     */
    public Integer[] getParticipantGroupArray(String modelname, Set<String> participants, ActionParameter p) {
        Set<Integer> participantGroup = new TreeSet<Integer>();
        for (String participant : participants) {
            Integer groupid = null;
            if (isGroupidParticipant(participant)) {
                groupid = getGroupid(participant)[0];
            } else {
                // BR7945 2014.12.12
                // Considering the proxy.
                String username = getUsername(modelname, participant, p);
                if (isGroupidParticipant(username)) {
                    groupid = getGroupid(username)[0];
                }
            }
            if (groupid != null) {
                participantGroup.add(groupid.intValue());
            }
        }
        return participantGroup.toArray(new Integer[0]);
    }

    private Map<String,String> createScript(JfcworkflowSettingP wfs_p) {
        ScriptSetting[] ss_ary = (wfs_p != null) ? wfs_p.getScriptSetting() : null;
        if (ss_ary == null) {
            return new HashMap<String,String>();
        }
        int fid = 1;
        Map<String,StringBuilder> map = new HashMap<String,StringBuilder>();
        Map<String,String> fmap = new HashMap<String,String>();
        for (ScriptSetting ss : ss_ary) {
            Beginnode beginnode = ss.getBeginnode();
            Condition cond = ss.getCondition();
            Endnode endnode = ss.getEndnode();
            StringBuilder sb = map.get(beginnode.getContent());
            StringBuilder funcname = new StringBuilder();
            funcname.append("cond").append(fid++).append("(").append(wfs_p.getModelname()[0].getId()).append(")");
            StringBuilder fcontent = new StringBuilder();
            String condition = cond.getContent();
            if (StringUtils.isBlank(condition)) {
                condition = "return " + endnode.getContent() + ";";
            }
            if (sb == null) {
                sb = new StringBuilder("if (");
                sb.append(funcname);
                sb.append(") {");
                fcontent.append("function ").append(funcname).append(" {");
                fcontent.append(condition);
                fcontent.append("}");
            } else {
                sb.append(" else");
                if (StringUtils.isNotBlank(cond.getContent())) {
                    sb.append(" if (");
                    sb.append(funcname);
                    sb.append(") {");
                    fcontent.append("function ").append(funcname).append(" {");
                    fcontent.append(condition);
                    fcontent.append("}");
                } else {
                    sb.append(" {");
                }
            }
            sb.append("return ").append(endnode.getContent()).append(";}");
            map.put(beginnode.getContent(), sb); 
            if (fcontent.length() > 0) {
                String code = fmap.get(beginnode.getContent());
                if (code == null) {
                    code = fcontent.toString();
                } else {
                    code = fcontent.insert(0, code).toString();
                }
                fmap.put(beginnode.getContent(), code);
            }
        }
        Map<String,String> retmap = new HashMap<String,String>();
        for (Map.Entry<String,StringBuilder> entry : map.entrySet()) {
            String code = fmap.get(entry.getKey());
            if (code != null) {
                entry.getValue().append("$$$FUNCTION$$$").append(code);
            }
            retmap.put(entry.getKey(), entry.getValue().toString());
        }
        //System.out.println(retmap);
        return retmap;
    }

    /**
     * Return the final flow participant ID.
     */
    public String getLastParticipantId(String modelname, String pkey, ActionParameter p) {
        $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants = getParticipants(modelname, pkey, p);
        if (participants == null || participants.length == 0) {
            return null;
        }
        return ((Integer) (participants[participants.length - 1]).getParticipantId()).toString();
    }

    public String getLastLayerId(String modelname, String pkey, ActionParameter p) {
        $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants = getParticipants(modelname, pkey, p);
        if (participants == null || participants.length == 0) {
            return null;
        }
        return (participants[participants.length - 1]).getLayerId();
    }

    /**
     * Returns whether the logged-on user belongs to the flow participant or group of the application node.
     */
    public boolean isApplicant(String modelname, String pkey, ActionParameter p) {
        String mandator = getMandatorByProxy(modelname, pkey, CANCELABLE_NODE_NAME, p.user.getUsername(), p);
        for ($block{jfcpackagename}.model.jfcparticipant_setting.Participants participants : getParticipants(modelname, pkey, APPLICATION_NODE_NAME, p)) {
            // When the user (proxy) is the application node of the flow participant.
            String participant = participants.getParticipantName();
            if (mandator.equals(participant)) {
                return true;
            }
            // When the user (proxy) belongs to the group of the application node.
            Integer groupId = participants.getParticipantGroupidAsInteger();
            if (groupId == null) {
                continue;
            }
            // In the case of group designation, confirm whether the user belongs to the group.
            int[] groupIds = getGroupidByUserid(mandator, p);
            for (int i=0; i<groupIds.length; i++) {
                if (groupId == groupIds[i]) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns whether cancellation is possible.
     */
    public boolean isCancelable(String modelname, String pkey, ActionParameter p) {
        String currentNode = getCurrentNode(modelname, pkey, p);
        return isCancelable(modelname, pkey, currentNode, p);
    }

    public boolean isCancelable(String modelname, String pkey, String jfcWorkflowCurrentNode, ActionParameter p) {
        // If the waiting node is not NULL (the workflow is terminated) or 
        // the cancelable node (the node immediately after the application (layer ID is 2)) is not cancelable.
        if (StringUtils.isBlank(jfcWorkflowCurrentNode)) {
            return false;
        }
        if (isParticipantNotFound(modelname, pkey, p)) {
        	return false;
        }
        String username = p.user.getUsername();
        if (username == null) {
            return false;
        }
        // Get the juser object of applicant.
        Juser applicant = getApplicantJuser(modelname, pkey, p);
        if (applicant == null) {
            return false;
        }
        // If the logon user is not the applicant, it is impossible to cancel.
        if (!username.equals(applicant.getUserid())) {
            return false;
        }
        String lastEvent = getLastWorkflowEvent(modelname, pkey, p);
        if (APPLICATION_NODE_NAME.equals(jfcWorkflowCurrentNode) &&
            (WorkEvent.Back.toString().equals(lastEvent) || WorkEvent.Rewind.toString().equals(lastEvent))) {
            return true;
        }
        if (!CANCELABLE_NODE_NAME.equals(jfcWorkflowCurrentNode)) {
            return false;
        }
        return true;
    }
    
    public boolean isParticipantNotFound(String modelname, String pkey, ActionParameter p) {
        $block{jfcpackagename}.model.jfcparticipant_setting.Participants[] participants = getParticipants(modelname, pkey, p);
        if (participants == null || participants.length == 0) {
        	return true;
        }
        return false;
    }

    public Juser getApplicantJuser(String modelname, String modelpkey, ActionParameter p) {
    	return getApplicantJuser(modelname, modelpkey, true, p);
    }

    public Juser getApplicantJuser(String modelname, String modelpkey, boolean lazyFetch, ActionParameter p) {
		final String jfcWorkflowApplicantCacheKey =
            JFCWORKFLOW_APPLICANT_CACHE_PREFIX + modelname + "_" + modelpkey + "_" + lazyFetch;
        // Get the juser object of applicant.
        Juser applicant = (Juser) p.request.getAttribute(jfcWorkflowApplicantCacheKey);
        if (applicant != null) {
        	return applicant;
        }
        jp.jasminesoft.jfc.service.JFCEntityService entityService = (jp.jasminesoft.jfc.service.JFCEntityService) p.appctx.getBean("JfcworkstateEntityService");
        JfcworkstateMeta meta = new JfcworkstateMeta();
        DetachedCriteria criteria = DetachedCriteria.forClass(meta.entityClass());
        criteria.eq(meta.modelname, modelname);
        criteria.eq(meta.modelpkey, modelpkey);
        criteria.eq(meta.event, WorkEvent.Application.toString());
        List<Jfcworkstate> list = entityService.find(criteria);
        if (list.isEmpty()) {
            return applicant;
        }
        Jfcworkstate jfcworkstate = list.get(0);
        final String userid = jfcworkstate.getUsername();
        jp.jasminesoft.jfc.app.EntityHelper helper =
            (jp.jasminesoft.jfc.app.EntityHelper) p.appctx.getBean("JuserHelper");
        jp.jasminesoft.jfc.service.JFCEntityService juserEntityService =
            (jp.jasminesoft.jfc.service.JFCEntityService) p.appctx.getBean("JuserEntityService");
        DataBindingContext context = new DataBindingContext();
        context.setLazyFetch(lazyFetch);
        applicant = (Juser) juserEntityService.findById(helper.getPrimarykey(userid), context);
        p.request.setAttribute(jfcWorkflowApplicantCacheKey, applicant);

        return applicant;
    }

    public String getAdmitUsersOrGroupFromScript(String modelname, String modelpkey, String script, ActionParameter p) {
        return executeScript(modelname, modelpkey, script, p);
    }

    public String executeScript(String modelname, String modelpkey, String script, ActionParameter p) {
        if (StringUtils.isEmpty(script)) return "";
        try {
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = null;
            if ("nashorn".equals(engineName)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Use Nashorn as JavaScript engine.");
                }
                engine = manager.getEngineByName("JavaScript");
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Use "+engineName+" as JavaScript engine.");
                }
                engine = manager.getEngineByName(engineName);
                try {
                    engine.eval("load('nashorn:mozilla_compat.js')");
                } catch (ScriptException e) {
                    logger.warn("mozilla_compat.js is not loaded.", e);
                }
            }
            if (engine == null) {
                logger.warn("No JavaScript Engine is found.");
                return "";
            }
            Map<String,String> keyAndTypeMap = new LinkedHashMap<>();
            engine.put("p", p);
            keyAndTypeMap.put("p", p.getClass().getName());
            engine.put("modelname", modelname);
            keyAndTypeMap.put("modelname", "java.lang.String");
	    engine.put("scriptLogger", logger);// BR 13939, 2024.2.21
	    keyAndTypeMap.put("scriptLogger", logger.getClass().getName());
            try {
                if (p.appctx != null) {
                    engine.put("errorManager", p.appctx.getBean(JFCErrorManager.class));// BR 11833, 2020.11.18
                    keyAndTypeMap.put("errorManager", JFCErrorManager.class.getName());
                    org.hibernate.Session session = 
                        ((org.hibernate.SessionFactory)p.appctx.getBean("sessionFactory")).getCurrentSession();
                    if (session != null) {
                        engine.put("session", session);// BR 10409, 2018.6.29 @since R8.0.5
                        keyAndTypeMap.put("session", session.getClass().getName());
                    }
                }
            } catch (org.hibernate.HibernateException e) {
                logger.debug(e.getMessage());
            }
            Object obj_p = null;
            Object obj_s = null;
            if (StringUtils.isNotBlank(modelname)) {
                obj_p = p.request.getAttribute(modelname + "_p");
                if (StringUtils.isNotBlank(modelpkey)) {
                    obj_s = getModel(modelname, modelpkey, true, p);// BR 13926, 2024.1.31
                }
            }
            engine.put(modelname + "_p", obj_p);
            if (obj_p != null) {
                keyAndTypeMap.put(modelname + "_p", obj_p.getClass().getName());
            }
            engine.put(modelname, obj_s);
            if (obj_s != null) {
                keyAndTypeMap.put(modelname, obj_s.getClass().getName());
            }
            Object pkey = getPrimaryKeyAsString(obj_s, p);// BR 14269, 2025.4.4
            engine.put("modelpkey", pkey);
            if (pkey != null) {
                keyAndTypeMap.put("modelpkey", pkey.getClass().getName());
            }
            Object obj = p.request.getAttribute("jfcworkstate");
            engine.put("jfcworkstate", obj);
            if (obj != null) {
                keyAndTypeMap.put("jfcworkstate", obj.getClass().getName());
            }
            // Get the juser object of applicant.
            Juser applicant = getApplicantJuser(modelname, modelpkey, false, p);
            engine.put("applicant", applicant);
            if (applicant != null) {
                keyAndTypeMap.put("applicant", applicant.getClass().getName());
            }
            Object $applicant = ElementUtils.getUserElement(p.appctx, applicant);
            engine.put("$applicant", $applicant);
            keyAndTypeMap.put("$applicant", $applicant.getClass().getName());
            Object $rootGroup = ElementUtils.rootGroup(p.appctx);
            engine.put("$rootGroup", $rootGroup);
            keyAndTypeMap.put("$rootGroup", $rootGroup.getClass().getName());
            Object $allUsers = ElementUtils.allUsers(p.appctx);
            engine.put("$allUsers", $allUsers);
            keyAndTypeMap.put("$allUsers", $allUsers.getClass().getName());
            WorkFlowManager wman = WorkFlowManager.getInstance(p);
            engine.put("wman", wman);
            keyAndTypeMap.put("wman", wman.getClass().getName());

            engine.put("__status", ScriptCodeRunner.createStatusString(keyAndTypeMap));// 2021.9.16 @since R9.0.3

            StringBuilder sb = new StringBuilder();
            sb.append("function process() {");
            sb.append("var stdout = java.lang.System.out;");
            sb.append("var JFCUtils = Java.type(\"jp.jasminesoft.jfc.JFCUtils\");");
            sb.append("var DetachedCriteria = Java.type(\"jp.jasminesoft.jfc.hibernate.DetachedCriteria\");");
            sb.append("var RelationMeta = Java.type(\"jp.jasminesoft.jfc.meta.RelationMeta\");");
            sb.append("var JuserMeta = Java.type(\"jp.jasminesoft.jfc.model.juser.JuserMeta\");");
            sb.append("var ExcelFunction = Java.type(\"jp.jasminesoft.util.ExcelFunction\");");
            sb.append("var WorkFlowManager = Java.type(\"$block{jfcpackagename}.app.WorkFlowManager\");");
            sb.append("var JfcmodelMeta = Java.type(\"$block{jfcpackagename}.model.jfcmodel.JfcmodelMeta\");");
            sb.append("var By = Java.type(\"jp.jasminesoft.jfc.core.group.By\");");
            sb.append("var BusinessLogicException = Java.type(\"jp.jasminesoft.jfc.core.exception.BusinessLogicException\");");
            sb.append(script).append("}");

            // BR 12249, 2021.6.27
            String wagbyjsPath = "classpath:systemScripts/wagby.js";
            if ("graal.js".equals(engineName)) {
                // CAUTION: GraalJS では load(classpath:xxx.js) が使えないので、
                // 絶対パス指定でファイルを読み込ませる。
                wagbyjsPath = p.appctx.getBean(ServletContext.class)
                    .getRealPath("/WEB-INF/classes/systemScripts/wagby.js");
                // Windows のファイルセパレータ "\" はエスケープしておく。
                wagbyjsPath = wagbyjsPath.replace("\\", "\\\\");
            }
            sb.append("load(\"").append(wagbyjsPath).append("\");");

            // 絶対パスを取得。
            String myfunctionJsPath
                    = p.appctx.getBean(javax.servlet.ServletContext.class)
                            .getRealPath("WEB-INF/script/myfunction.js");
            if (new File(myfunctionJsPath).exists()) {
                sb.append("load(\"wagby:myfunction.js\");");
            }
            engine.eval(sb.toString());
            Invocable inv = (Invocable)engine;
            Object status = inv.invokeFunction("process");
            if (status != null && status instanceof String) {
            	return (String) status;
            }
        } catch (ScriptException e) {
            int index = ExceptionUtils.indexOfType(e, BusinessLogicException.class);
            if (index != -1) {
                // 例外チェーンに BusinessLogicException が含まれている
                // 場合は文法エラーではなく、スクリプト内で意図的に発生
                // させたエラーとして扱う。
                Throwable throwable = ExceptionUtils.getThrowables(e)[index];
                throw new RuntimeException(throwable.getMessage(), throwable);
            }
            logger.debug(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        } catch (Exception e) {
            logger.error("error.",e);
            throw new RuntimeException(e.getMessage(), e);
        }
        return "";
    }

    /**
     * Get the primary key.
     *
     * @param <E> 
     * @param <PK>
     * @param entity store model
     * @param p ActionParameter
     * @return the primary key.
     */
    @SuppressWarnings("unchecked")
    private <S extends ContainerBase<S>, PK extends Serializable>
            PK getPrimaryKey(Object entity, ActionParameter p) {
        return StoreModelUtils.primaryKey((S) entity, p);
    }

    /**
     * Get the primary key as string value.
     *
     * @param <E> 
     * @param <PK>
     * @param entity store model
     * @param p ActionParameter
     * @return the primary key as string value.
     */
    @SuppressWarnings("unchecked")
    private <S extends ContainerBase<S>, PK extends Serializable>
            String getPrimaryKeyAsString(Object entity, ActionParameter p) {
        return StoreModelUtils.primaryKeyAsString((S) entity, p);// BR 14269, 2025.4.4
    }
    
    /**
     * Returns whether workflow was rejected.
     */
    public boolean isReject(String modelname, String pkey, ActionParameter p) {
        String lastEvent = getLastWorkflowEvent(modelname, pkey, p);
        if (WorkEvent.Reject.toString().equals(lastEvent)) {
            return true;
        }
        return false;
    }

    public String getCreateUser(String modelname, String pkey, ActionParameter p) {
        String createUser = null;
        if (StringUtils.isBlank(pkey)) {
            return createUser;
        }
        Object o = getModel(modelname, pkey, true, p);
        if (o == null) {
            return createUser;
        }
        try {
            Class c = o.getClass();
            Method getCreateUserMethod = c.getMethod("getJfcWorkflowCreateUser", (Class[]) null);
            createUser = (String) getCreateUserMethod.invoke(o, (Object[]) null);
        } catch(Exception e) {
            logger.error("Exception", e);
        }
        return createUser;
    }

    /**
     * Returns whether delete is possible.
     */
    public boolean isDeletable(String modelname, String pkey, ActionParameter p) {
        if (JFCUtils.hasPrincipal(p.user)) {
            return true;
        }
        String username = p.user.getUsername();
        if (username == null) {
            return false;
        }
        if (!username.equals(getCreateUser(modelname, pkey, p))) {
            return false;
        }
        if (isAdmitUser(modelname, pkey, p) || isReject(modelname, pkey, p)) {
            return true;
        }
        return false;
    }

    public List<Jfcworkstate> getJfcworkstateList(String modelname, String pkey, ActionParameter p) {
    	if (StringUtils.isBlank(modelname) || StringUtils.isBlank(pkey) || p == null) {
        	return new ArrayList<Jfcworkstate>();
    	}
    	JfcworkstateService jfcworkstateService = 
    			(JfcworkstateService) p.appctx.getBean("JfcworkstateService");
    	return jfcworkstateService.getJfcworkstateList(modelname, pkey, p);
    }

    public void resetFlowEvent(String modelname, String pkey, ActionParameter p) {
    	if (StringUtils.isBlank(modelname) || StringUtils.isBlank(pkey) || p == null) {
    		return;
    	}
    	List<Jfcworkstate> jfcworkstateList = getJfcworkstateList(modelname, pkey, p);
        if (jfcworkstateList == null || jfcworkstateList.size() == 0) {
            return;
        }
        @SuppressWarnings("unchecked")
		JFCEntityService<Jfcworkstate, Integer> entityService =
            (JFCEntityService<Jfcworkstate, Integer>) p.appctx.getBean("JfcworkstateEntityService");
        for (int i = jfcworkstateList.size() - 1; i > 0; i--) {
        	Jfcworkstate jfcworkstate = jfcworkstateList.get(i);
            entityService.delete(jfcworkstate.getId(), true);
        }
    }

    public void hardResetFlowEvent(String modelname, String pkey, ActionParameter p) {
    	if (StringUtils.isBlank(modelname) || StringUtils.isBlank(pkey) || p == null) {
    		return;
    	}
    	List<Jfcworkstate> jfcworkstateList = getJfcworkstateList(modelname, pkey, p);
        if (jfcworkstateList == null || jfcworkstateList.size() == 0) {
            return;
        }
        @SuppressWarnings("unchecked")
		JFCEntityService<Jfcworkstate, Integer> entityService =
            (JFCEntityService<Jfcworkstate, Integer>) p.appctx.getBean("JfcworkstateEntityService");
        for (int i = jfcworkstateList.size() - 1; i >= 0; i--) {
            Jfcworkstate jfcworkstate = jfcworkstateList.get(i);
            entityService.delete(jfcworkstate.getId(), true);
        }
        JfcworkstateService jfcworkstateService = (JfcworkstateService) p.appctx.getBean("JfcworkstateService");
        jfcworkstateService.updateWorkflowModelitemStart(modelname, pkey, p);
        clearAllJfcworkflowCache(p);
    }

    public String getLastProcessUserId(String modelname, String pkey, ActionParameter p) {
    	if (StringUtils.isBlank(modelname) || StringUtils.isBlank(pkey) || p == null) {
    		return null;
    	}
    	List<Jfcworkstate> jfcworkstateList = getJfcworkstateList(modelname, pkey, p);
        if (jfcworkstateList == null || jfcworkstateList.size() == 0) {
            return null;
        }
        return (jfcworkstateList.get(jfcworkstateList.size() - 1)).getUsername();
    }

    public String getApplicantUserId(String modelname, String pkey, ActionParameter p) {
    	Set<String> latestEvents = new HashSet<>();
    	latestEvents.add(WorkEvent.Application.toString());
    	Jfcworkstate jfcworkstate = getJfcworkstateOfLatestEvent(modelname, pkey, latestEvents, p);
    	if (jfcworkstate != null) {
    		return jfcworkstate.getUsername();
    	}
    	return null;
    }

    public Jfcworkstate getJfcworkstateOfLatestBackEvent(String modelname, String pkey, ActionParameter p) {
    	Set<String> latestEvents = new HashSet<>();
    	latestEvents.add(WorkEvent.Back.toString());
    	latestEvents.add(WorkEvent.Rewind.toString());
    	Jfcworkstate jfcworkstate = getJfcworkstateOfLatestEvent(modelname, pkey, latestEvents, p);
    	if (jfcworkstate != null) {
    		return jfcworkstate;
    	}
    	return null;
    }

    public String getUserIdOfLatestBackEvent(String modelname, String pkey, ActionParameter p) {
    	Jfcworkstate jfcworkstate = getJfcworkstateOfLatestBackEvent(modelname, pkey, p);
    	if (jfcworkstate != null) {
    		return jfcworkstate.getUsername();
    	}
    	return null;
    }

    public Jfcworkstate getJfcworkstateOfLatestEvent(String modelname, String pkey, Set<String> latestEvents, ActionParameter p) {
    	if (StringUtils.isBlank(modelname) || StringUtils.isBlank(pkey) ||
    			latestEvents == null || p == null) {
    		return null;
    	}
    	List<Jfcworkstate> jfcworkstateList = getJfcworkstateList(modelname, pkey, p);
        if (jfcworkstateList == null || jfcworkstateList.size() == 0) {
            return null;
        }
        for (int i = jfcworkstateList.size() - 1; i >= 0; i--) {
        	Jfcworkstate jfcworkstate = jfcworkstateList.get(i);
        	if (latestEvents.contains(jfcworkstate.getEvent())) {
        		return jfcworkstate;
        	}
        }
    	return null;
    }

    public Jfcworkstate getJfcworkstateOfLatestEvent(String modelname, String pkey, String latestEvent, ActionParameter p) {
    	if (StringUtils.isBlank(modelname) || StringUtils.isBlank(pkey) || StringUtils.isBlank(latestEvent)) {
    		return null;
    	}
    	Set<String> latestEvents = new HashSet<String>();
    	latestEvents.add(latestEvent);
    	Jfcworkstate jfcworkstate = getJfcworkstateOfLatestEvent(modelname, pkey, latestEvents, p);
        if (jfcworkstate != null) {
            return jfcworkstate;
        }
    	return null;
    }

    public Jfcworkstate do_insert(ActionParameter p, String[] targetitems, JFCErrorManager errorManager) 
        throws IOException, ServletException
    {
        JfcworkstateP jfcworkstate_p = (JfcworkstateP)(p.o);
        Jfcworkstate jfcworkstate = (Jfcworkstate)p.appMap.get("jfcworkstate_preserve");
        if (jfcworkstate == null) {
            jfcworkstate = new Jfcworkstate();
            (($block{jfcpackagename}.app.jfcworkstate.JfcworkstateHelper)p.appctx.getBean("JfcworkstateHelper")).initialize(jfcworkstate, p);
        }

        Set targetSet = (targetitems != null) ? new HashSet(Arrays.asList(targetitems)) : new HashSet();

        (($block{jfcpackagename}.app.jfcworkstate.JfcworkstatePFilterHelper)p.appctx.getBean("JfcworkstatePFilterHelper")).filter(jfcworkstate_p, targetSet, p);

        jfcworkstate = 
            (($block{jfcpackagename}.app.jfcworkstate.JfcworkstatePHelper)p.appctx.getBean("JfcworkstatePHelper")).p2s(jfcworkstate_p, p, jfcworkstate, targetSet);

        // In the case of an input check error, in order to return to the show screen of the workflow model, 
        // obtain the model name and the primary key.
        //String returnModelname = jfcworkstate.getModelname();
        p.request.setAttribute("jfc_workflow_myModelId", jfcworkstate.getModelname());
        p.request.setAttribute("jfc_workflow_mainModelId", jfcworkstate.getMainModelname());
        String mainModelname = jfcworkstate.getMainModelname();
        if (StringUtils.isNotBlank(mainModelname)) {
            jfcworkstate.setModelname(mainModelname);
        }
        String modelpkey = jfcworkstate.getModelpkey();

        (($block{jfcpackagename}.app.jfcworkstate.JfcworkstatePInputCheckHelper)p.appctx.getBean("JfcworkstatePInputCheckHelper")).input_check(jfcworkstate, jfcworkstate_p, p, errorManager, targetSet);
        if (p.errors.sizeJfcerror() > 0) {
            JfcworkstateP n_jfcworkstate_p = 
                (($block{jfcpackagename}.app.jfcworkstate.JfcworkstatePHelper)p.appctx.getBean("JfcworkstatePHelper")).s2p(jfcworkstate, p, $block{jfcpackagename}.app.jfcworkstate.JfcworkstatePHelper.UPDATE, jfcworkstate_p);
            p.request.setAttribute("id", modelpkey);
            p.request.setAttribute("jfcworkstate_p", n_jfcworkstate_p);
            p.appMap.put("jfcworkstate_p_preserve", n_jfcworkstate_p);

            return jfcworkstate;
        }

        if (! do_insert(p, errorManager, jfcworkstate)) {
            return jfcworkstate;
        }

        // Clear the comment before returning to the show screen of the workflow model 
        // with normal termination.
        jfcworkstate_p.setComment(null);
        p.request.setAttribute("jfcworkstate_p", jfcworkstate_p);
        p.appMap.put("jfcworkstate_p_preserve", jfcworkstate_p);

        return jfcworkstate;
    }

    // [BR 11445] アップロード更新にてワークフローの申請を行うスクリプトを書けるようにする
    // アップロード更新時にワークフローの申請を行う場合、モデルロックされている状態でデータを1件ロックしようとするとロックエラーとなるため、これを回避するためにp.request.setAttribute(AVOID_LOCK_KEY, "true")にてロックを行わないようにしている。
    // 誤った使い方を行うと、データ更新中にワークフロー申請が行えてしまい、データの矛盾が発生する可能性があるため、注意が必要。
    public final static String AVOID_LOCK_KEY = "WorkFlowManager_do_insert_avoidlock";

    private boolean do_insert(ActionParameter p, JFCErrorManager errorManager, Jfcworkstate jfcworkstate) 
        throws IOException, ServletException
    {
        String modelname = jfcworkstate.getModelname();
        String modelpkey = jfcworkstate.getModelpkey();
        LockUtils.initAppMap(p);

        boolean islock = true;
        Object isavoidlockobj = p.request.getAttribute(AVOID_LOCK_KEY);
        if (isavoidlockobj != null && isavoidlockobj instanceof String
                && Boolean.valueOf((String) isavoidlockobj)) {
            islock = false;
        }

        try {
            if (islock) {
            LockUtils.lock(modelname, modelpkey, p);
            }
            return __do_insert(p, errorManager, jfcworkstate);
        } catch (PessimisticLockException e) {
            // ロック失敗
            jp.jasminesoft.jfc.error.Jfcerror _error = 
                errorManager.getJfcerror("error.dbaccess.lock", p.locale);
            _error.setContent(e.getMessage());
            p.errors.addJfcerror(_error);
            return false;
        } finally {
            if (islock) {
            LockUtils.release(modelname, modelpkey, p);
            }
        }
    }

    private boolean __do_insert(ActionParameter p, JFCErrorManager errorManager, Jfcworkstate jfcworkstate) 
        throws IOException, ServletException
    {
        String modelpkey = jfcworkstate.getModelpkey();

        // Check if the logged-on user can approve or cancel.
        WorkFlowManager wman = WorkFlowManager.getInstance(p);
        if (!(wman.isAdmitUser(jfcworkstate.getModelname(), modelpkey, p) ||
                wman.isCancelable(jfcworkstate.getModelname(), modelpkey, p))) {
            // If it is not an approverable user, do not do anything and display the show screen.
            p.request.setAttribute("id", modelpkey);
            jp.jasminesoft.jfc.error.Jfcerror _error = 
                    errorManager.getJfcerror("error.workflow.not_approverable_user", p.locale);
            p.errors.addJfcerror(_error);
            return false;
        }

        JfcworkstateService jfcworkstateService =
            (JfcworkstateService) p.appctx.getBean("JfcworkstateService");
        boolean isEndEventSubmit = false;
        try {
            // BR8220 2014.12.17
            // When the decision button is pressed in the middle of the node, 
            // change the flow event to "approval" and register.
            if (WorkEvent.End.toString().equals(jfcworkstate.getEvent())) {
                jfcworkstate.setEvent(WorkEvent.Admit.toString());
                isEndEventSubmit = true;
            }
            jfcworkstateService.setJfcworkstateParameter(jfcworkstate, p);
        } catch (Exception e) {
            String msg = e.getMessage();
            if (JfcworkstateService.WORKFLOW_ALREADYAPPLY_ERROR_CODE.equals(msg)) {
                // An error message is displayed to the effect that another user has already made an application.
                p.request.setAttribute("jfcworkflow_isApplicationNode", Boolean.FALSE);
                jp.jasminesoft.jfc.error.Jfcerror _error = 
                    errorManager.getJfcerror("error.workflow.alreadyApply", p.locale);
                p.errors.addJfcerror(_error);
            } else {
                logger.error("modelname:["+jfcworkstate.getModelname()+"], modelpkey:["+modelpkey+"], exception: ", e);
                Object[] ep = { e.getMessage() };
                p.errors.addJfcerror
                  (errorManager.getJfcerror("jfcworkstate.error.termination.insert", ep, p.locale));
            }

            // Back to the show screen.
            return false;
        }

        try {
            jfcworkstateService.insertJfcworkstate(jfcworkstate, p);
            p.request.setAttribute("jfcworkstate", jfcworkstate);
            p.appMap.put("jfcworkstate_preserve", jfcworkstate);

            FilenameList filenamelist = (FilenameList)p.pageMap.get("__jfc_filenamelist");
            if (filenamelist != null) {
              List<String> filelist = (($block{jfcpackagename}.app.jfcworkstate.JfcworkstateHelper)p.appctx.getBean("JfcworkstateHelper")).getFileList(jfcworkstate, p);
              filenamelist.deleteUnsavedFilenameList(filelist);
            }

            String desc = JFCUtils.getRValue("__jfc_tablename.JfcworkstateModelId", p.locale);
            p.errors.addJfcinfo(errorManager.getJfcinfo("jfcworkstate.success.normal.termination.insert", new Object[] { desc }, p.locale));
            p.request.setAttribute("id", modelpkey);

            // BR8220 2014.12.17
            // When the decision button is pushed in the middle of the node, change the flow event 
            // changed to "approval" to "decision" and register.
            if (isEndEventSubmit) {
                jfcworkstate.setEvent(WorkEvent.End.toString());
            }
            jfcworkstateService.processJfcworkstate(jfcworkstate, p);
        } catch(Exception e) {
            logger.error("modelname:["+jfcworkstate.getModelname()+"], modelpkey:["+modelpkey+"], exception: ", e);
            Object[] ep = { e.getMessage() };
            p.errors.addJfcerror
              (errorManager.getJfcerror("jfcworkstate.error.termination.insert", ep, p.locale));
            JfcworkstateP jfcworkstate_p = 
                (($block{jfcpackagename}.app.jfcworkstate.JfcworkstatePHelper)p.appctx.getBean("JfcworkstatePHelper")).s2p(jfcworkstate, p, $block{jfcpackagename}.app.jfcworkstate.JfcworkstatePHelper.UPDATE);
            p.request.setAttribute("jfcworkstate_p", jfcworkstate_p);
            p.appMap.put("jfcworkstate_p_preserve", jfcworkstate_p);
            return false;
        } finally {
            // FIXME: is this process is useless ? 
            // Remove the key for double press prevention.
            p.pageMap.remove("pkey");
        }

        return true;
    }

    public String do_userDefinedAll(Jfcsuspendworkstate jfcsuspendworkstate, ActionParameter p) {
        String modelname = jfcsuspendworkstate.getModelname();
        String modelpkey = jfcsuspendworkstate.getModelpkey();
        LockUtils.initAppMap(p);
        try {
            LockUtils.lock(modelname, modelpkey, p);
            return __do_userDefinedAll(p, jfcsuspendworkstate);
        } catch (PessimisticLockException e) {
            // ロック失敗
            String modelnamedesc =
                JFCAppTablename.getWorkflowModelNames(p.locale).get(modelname);
            JFCErrorManager errorManager = p.appctx.getBean(JFCErrorManager.class);
            Object[] ep = {
                modelnamedesc, modelpkey
            };
            jp.jasminesoft.jfc.error.Jfcwarn _warn = 
                errorManager.getJfcwarn("error.workflow.jfcsuspendworkstate_lockfailed", ep, p.locale);
            p.errors.addJfcwarn(_warn);
            return _warn.getContent() + e.getMessage();
        } finally {
            LockUtils.release(modelname, modelpkey, p);
        }
    }

    private String __do_userDefinedAll(
            ActionParameter p, Jfcsuspendworkstate jfcsuspendworkstate)
    {
        String modelname = jfcsuspendworkstate.getModelname();
        String pkey = jfcsuspendworkstate.getModelpkey();
        String comment = p.request.getParameter("comment");

        JfcworkstateService jfcworkstateService = 
            (JfcworkstateService) p.appctx.getBean("JfcworkstateService");
        JfcworkstateLp jfcworkstate_lp = 
            jfcworkstateService.getJfcworkstateLp(modelname, JFCUtils.decodePrimaryKey(pkey), p);

        boolean isBack = false;
        $block{jfcpackagename}.model.jfcworkstate_lp.Item[] items = jfcworkstate_lp.getItem();
        if (items != null && items.length > 0) {
            $block{jfcpackagename}.model.jfcworkstate_lp.Item lastitem = items[items.length-1];
            String event = lastitem.getEvent();
            if (WorkEvent.Back.toString().equals(event)) {
                isBack = true;
            }
        }

        WorkFlowManager wfman = WorkFlowManager.getInstance(p);
        if (!wfman.isAdmitUser(modelname, pkey, p)) {
            return "not admit data "+jfcsuspendworkstate;
        }

        try {
            if (items.length == 1 || isBack == true) {
                if (p.action.endsWith("Admit")) {
                    insertJfcworkstate(jfcsuspendworkstate, WorkEvent.Application, comment, p);
                } else {
                    return "not "+p.action+" data "+jfcsuspendworkstate;
                }
            } else if (items.length > 1) {
                if (p.action.endsWith("Admit")) {
                    insertJfcworkstate(jfcsuspendworkstate, WorkEvent.Admit, comment, p);
                } else if (p.action.endsWith("Back")) {
                    insertJfcworkstate(jfcsuspendworkstate, WorkEvent.Back, comment, p);
                } else if (p.action.endsWith("Reject")) {
                    insertJfcworkstate(jfcsuspendworkstate, WorkEvent.Reject, comment, p);
                }
            } else {
                return "not start data "+jfcsuspendworkstate;
            }
        } catch (SecurityException e) {
            logger.error("not permit insert jfcworkstate", e);
            return "not permit insert jfcworkstate";
        } catch (IllegalStateException e) {
            logger.error("illegal data", e);
            return "illegal data";
        }

        // 2013.05.12
        p.request.setAttribute("jfcsuspendworkstate", jfcsuspendworkstate);
        p.request.setAttribute("jfcsuspendworkstate_comment", comment);
        new ScriptCodeRunner("jfcsuspendworkstate", "jfcsuspendworkstate_comment").process("ShowListJfcsuspendworkstate_jfcworkstate", "insert", p);

        return null;
    }

    private void insertJfcworkstate(
            Jfcsuspendworkstate jfcsuspendworkstate, WorkEvent event,
            String comment, ActionParameter p)
        throws IllegalStateException
    {
        String modelname = jfcsuspendworkstate.getModelname();
        String modelpkey = jfcsuspendworkstate.getModelpkey();
        JfcworkstateHelper shelper =
            ((JfcworkstateHelper)p.appctx.getBean("JfcworkstateHelper"));

        Jfcworkstate jfcworkstate = new Jfcworkstate();
        shelper.initialize(jfcworkstate, p);
        jfcworkstate.setModelname(modelname);
        jfcworkstate.setModelpkey(modelpkey);
        jfcworkstate.setEvent(event.toString());
        jfcworkstate.setComment(comment);

        JfcworkstateService jfcworkstateService =
            (JfcworkstateService) p.appctx.getBean("JfcworkstateService");
        try {
            jfcworkstateService.setJfcworkstateParameter(jfcworkstate, p);
        } catch (IllegalStateException e) {
            logger.debug("occur Exception", e);
            return;
        }
        jfcworkstateService.insertJfcworkstate(jfcworkstate, p);
        jfcworkstateService.processJfcworkstate(jfcworkstate, p);
    }
    
    public void skipToBackEventLayerId(String modelname, String modelpkey, ActionParameter p) {
    	skipToBackEventLayerId(modelname, modelpkey, "", p, null);
    }

    public void skipToBackEventLayerId(String modelname, String modelpkey, String comment, ActionParameter p) {
    	skipToBackEventLayerId(modelname, modelpkey, comment, p, null);
    }

    public void skipToBackEventLayerId(String modelname, String modelpkey, String comment, ActionParameter p, WorkFlowMail wfm) {
    	if (StringUtils.isBlank(modelname) || StringUtils.isBlank(modelpkey) || p == null) {
    		return;
    	}
        List<Jfcworkstate> redoJfcworkstates = getRedoJfcworkstates(modelname, modelpkey, p);
        if (redoJfcworkstates == null || redoJfcworkstates.isEmpty()) {
        	return;
        }
        String redoComment = "";
        if (StringUtils.isNotBlank(comment)) {
        	redoComment = comment;
        }
    	p.pageMap.put("__jfc_control.skipUpdateuser", Boolean.TRUE);
    	for (Jfcworkstate redoJfcworkstate : redoJfcworkstates) {
    		redoJfcworkstate.setComment(redoComment);
    		insertJfcworkstate(redoJfcworkstate, false, p);
    		if (wfm != null) {
    			wfm.send(redoJfcworkstate.getUsername());
    		}
    	}
		updateWorkflowModelitem(redoJfcworkstates.get(redoJfcworkstates.size() - 1), p);
    	p.pageMap.remove("__jfc_control.skipUpdateuser");
    }

    public List<Jfcworkstate> getRedoJfcworkstates(String modelname, String pkey, ActionParameter p) {
    	List<Jfcworkstate> jfcworkstateList = getJfcworkstateList(modelname, pkey, p);
        if (jfcworkstateList == null || jfcworkstateList.size() < 4) {
            return null;
        }
        // 再申請のデータを削除
        jfcworkstateList.remove(jfcworkstateList.size() - 1);
        Jfcworkstate jfcworkstate = jfcworkstateList.get(jfcworkstateList.size() - 1);
    	if (!WorkEvent.Rewind.toString().equals(jfcworkstate.getEvent())) {
    		return null;
    	}
        // 差し戻しのデータを削除
        jfcworkstateList.remove(jfcworkstateList.size() - 1);
        // 申請データまで遡って再処理用のフローイベントデータを取得する
        List<Jfcworkstate> redoJfcworkstates = new ArrayList<>();
        for (int i = jfcworkstateList.size() - 1; i > 0; i--) {
        	jfcworkstate = jfcworkstateList.get(i);
        	if (WorkEvent.Application.toString().equals(jfcworkstate.getEvent())) {
        		break;
        	}
        	redoJfcworkstates.add(jfcworkstate);
        }
        Collections.reverse(redoJfcworkstates);
        return redoJfcworkstates;
    }

    public String getLogonUserOrApplicant(String modelname, String modelpkey, ActionParameter p) {
    	String applicantUserId = getApplicantUserId(modelname, modelpkey, p);
    	if (StringUtils.isNotBlank(applicantUserId)) {
    		return applicantUserId;
    	}
    	return p.user.getUsername();
    }

    public void insertJfcworkstate(Jfcworkstate jfcworkstate, boolean isUpdateWorkflowModelitem, ActionParameter p)
        throws IllegalStateException
    {
        JfcworkstateService jfcworkstateService =
            (JfcworkstateService) p.appctx.getBean("JfcworkstateService");
        jfcworkstateService.insertJfcworkstate(jfcworkstate, isUpdateWorkflowModelitem, p);
    }
    
    public void updateWorkflowModelitem(Jfcworkstate jfcworkstate, ActionParameter p) {
        JfcworkstateService jfcworkstateService =
                (JfcworkstateService) p.appctx.getBean("JfcworkstateService");
            jfcworkstateService.updateWorkflowModelitem(jfcworkstate, p);
    }

}
