All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cristalise.kernel.lifecycle.instance.stateMachine.Transition Maven / Gradle / Ivy

/**
 * This file is part of the CRISTAL-iSE kernel.
 * Copyright (c) 2001-2015 The CRISTAL Consortium. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * http://www.fsf.org/licensing/licenses/lgpl.html
 */
package org.cristalise.kernel.lifecycle.instance.stateMachine;

import static org.cristalise.kernel.security.BuiltInAuthc.ADMIN_ROLE;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.cristalise.kernel.common.AccessRightsException;
import org.cristalise.kernel.common.InvalidDataException;
import org.cristalise.kernel.common.ObjectNotFoundException;
import org.cristalise.kernel.common.PersistencyException;
import org.cristalise.kernel.lifecycle.instance.Activity;
import org.cristalise.kernel.lookup.AgentPath;
import org.cristalise.kernel.lookup.RolePath;
import org.cristalise.kernel.persistency.outcome.Schema;
import org.cristalise.kernel.process.Gateway;
import org.cristalise.kernel.querying.Query;
import org.cristalise.kernel.scripting.Script;
import org.cristalise.kernel.utils.CastorHashMap;
import org.cristalise.kernel.utils.LocalObjectLoader;
import org.cristalise.kernel.utils.Logger;
import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class Transition {

    int    id;
    String name;

    int    originStateId = -1;
    int    targetStateId = -1;
    State  originState;
    State  targetState;
    String reservation;

    boolean errorHandler = false;

    /**
     * The name of the Boolean property that enables/disables this transition e.g.'Skippable'.
     * If no property name is specified the Transition is enabled
     */
    String enabledProp;
    /**
     * Whether the activity must be active for this transition to be available (activation property)
     */
    boolean requiresActive = true;
    /**
     * Whether the target state is a finishing state (activation property)
     */
    boolean finishing;
    boolean reinitializes  = false;
    /**
     * Overrides permission specified in Activity
     */
    String roleOverride;

    TransitionOutcome outcome;
    TransitionScript  script;
    TransitionQuery   query;

    public Transition() {}

    public Transition(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public Transition(int id, String name, int originStateId, int targetStateId) {
        this(id, name);
        this.originStateId = originStateId;
        this.targetStateId = targetStateId;
    }

    public void setTargetState(State targetState) {
        this.targetState = targetState;
        finishing = targetState.finished;
    }

    public boolean reinitializes() {
        return reinitializes;
    }

    public void setReinitializes(boolean reinit) {
        if (finishing) throw new RuntimeException("Transition cannot be both reinitializing and finishing");
        reinitializes = reinit;
    }

    public String getRoleOverride(CastorHashMap actProps) {
        return resolveValue(roleOverride, actProps);
    }

    protected boolean resolveStates(HashMap states) {
        boolean allFound = true;

        if (states.keySet().contains(originStateId)) {
            setOriginState(states.get(originStateId));
            originState.addPossibleTransition(this);
        }
        else allFound = false;

        if (states.keySet().contains(targetStateId)) setTargetState(states.get(targetStateId));
        else allFound = false;

        return allFound;
    }

    public String getPerformingRole(Activity act, AgentPath agent) throws ObjectNotFoundException, AccessRightsException {
        // check available
        if (!isEnabled(act))
            throw new AccessRightsException("Trans:" + toString() + " is disabled by the '" + enabledProp + "' property.");

        // check active
        if (isRequiresActive() && !act.getActive())
            throw new AccessRightsException("Activity must be active to perform trans:"+ toString());

        String overridingRole = getRoleOverride(act.getProperties());
        boolean override = overridingRole != null;
        boolean isOwner = false, isOwned = true;

        // Check agent name
        String agentName = act.getCurrentAgentName();
        if (StringUtils.isNotBlank(agentName)) {
            if (agent.getAgentName().equals(agentName)) isOwner = true;
        }
        else isOwned = false;

        List roles = new ArrayList();
        // determine transition role
        if (override) {
            roles.add(Gateway.getLookup().getRolePath(overridingRole));
        }
        else {
            String actRole = act.getCurrentAgentRole();
            if (StringUtils.isNotBlank(actRole)) {
                for (String role: actRole.split(",")) {
                    roles.add(Gateway.getLookup().getRolePath(role.trim()));
                }
            }
        }

        // Decide the access
        if (isOwned && !override && !isOwner)
            throw new AccessRightsException("Agent '" + agent.getAgentName() + "' cannot perform this trans:"+toString()+" because the activity '" + act.getName() + "' is currently owned by " + agentName);

        if (roles.size() != 0) {
            RolePath matchingRole = agent.getFirstMatchingRole(roles);

            if (matchingRole != null) 
                return matchingRole.getName();
            else if (agent.hasRole(ADMIN_ROLE.getName())) 
                return ADMIN_ROLE.getName();
            else
                throw new AccessRightsException("Agent '" + agent.getAgentName() + "' does not hold a suitable role '" + act.getCurrentAgentRole() + "' for the activity " + act.getName());
        }
        else return null;
    }

    public String getReservation(Activity act, AgentPath agent) {
        if (StringUtils.isBlank(reservation)) reservation = targetState.finished ? "clear" : "set";

        String reservedAgent = act.getCurrentAgentName();

        if (reservation.equals("set"))        reservedAgent = agent.getAgentName();
        else if (reservation.equals("clear")) reservedAgent = "";

        return reservedAgent;
    }

    private static String resolveValue(String key, CastorHashMap props) {
        if (StringUtils.isEmpty(key)) return null;

        String result = key;
        Pattern propField = Pattern.compile("\\$\\{(.+?)\\}");
        Matcher propMatcher = propField.matcher(result);

        while (propMatcher.find()) {
            String propName = propMatcher.group(1);
            Object propValue = props.get(propName);
            String propValString = propValue == null ? "" : propValue.toString();
            result = result.replace("${" + propName + "}", propValString);
        }
        Logger.msg(8, "Transition.resolveValue() - returning key '" + key + "' as '" + result + "'");
        return result;
    }

    /**
     * Computes if the Transition is enabled or not. The default value is true: if the enabledProp is empty
     * or the Activity property specified in enabledProp is undefined or its value is null.
     *
     * @param act the activity of the actual StateMachine/Transition
     * @return weather the Transition is enabled or not
     * @throws ObjectNotFoundException Objects were not found while evaluating properties
     */
    public boolean isEnabled(Activity act) throws ObjectNotFoundException {
        if (StringUtils.isBlank(enabledProp)) return true;

        Logger.msg(5, "Transition.isEnabled() - trans:" + getName()+" enabledProp:"+enabledProp);

        try {
            Object propValue = act.evaluateProperty(null, enabledProp, null);

            if(propValue == null) return false;
            else                  return new Boolean(propValue.toString());
        }
        catch ( InvalidDataException | PersistencyException e) {
            Logger.error(e);
            throw new ObjectNotFoundException(e.getMessage());
        }
    }

    public boolean hasOutcome(CastorHashMap actProps) {
        if (outcome == null || actProps == null) return false;

        if (StringUtils.isEmpty( resolveValue(outcome.schemaName,    actProps)) ) return false;
        if (StringUtils.isEmpty( resolveValue(outcome.schemaVersion, actProps)) ) return false;

        return true;
    }

    public boolean hasScript(CastorHashMap actProps) {
        if (script == null || actProps == null) return false;

        if (StringUtils.isEmpty( resolveValue(script.scriptName,    actProps)) ) return false;
        if (StringUtils.isEmpty( resolveValue(script.scriptVersion, actProps)) ) return false;

        return true;
    }

    public boolean hasQuery(CastorHashMap actProps) {
        if (query == null || actProps == null) return false;

        if (StringUtils.isEmpty( resolveValue(query.name,    actProps)) ) return false;
        if (StringUtils.isEmpty( resolveValue(query.version, actProps)) ) return false;

        return true;
    }

    public Schema getSchema(CastorHashMap actProps) throws InvalidDataException, ObjectNotFoundException {
        if (hasOutcome(actProps)) {
            try {
                return LocalObjectLoader.getSchema(
                        resolveValue(outcome.schemaName, actProps),
                        Integer.parseInt(resolveValue(outcome.schemaVersion, actProps)));
            }
            catch (NumberFormatException ex) {
                throw new InvalidDataException("Bad schema version number: " + outcome.schemaVersion + " (" + resolveValue(outcome.schemaVersion, actProps) + ")");
            }
        }
        else return null;
    }

    public Script getScript(CastorHashMap actProps) throws ObjectNotFoundException, InvalidDataException {
        if (hasScript(actProps)) {
            try {
                return LocalObjectLoader.getScript(
                        resolveValue(script.scriptName, actProps),
                        Integer.parseInt(resolveValue(script.scriptVersion, actProps)));
            }
            catch (NumberFormatException ex) {
                throw new InvalidDataException("Bad script version number: " + script.scriptVersion + " (" + resolveValue(script.scriptVersion, actProps) + ")");
            }
        }
        else return null;
    }

    public Query getQuery(CastorHashMap actProps) throws ObjectNotFoundException, InvalidDataException {
        if (hasQuery(actProps)) {
            try {
                return LocalObjectLoader.getQuery(
                        resolveValue(query.name, actProps),
                        Integer.parseInt(resolveValue(query.version, actProps)));
            }
            catch (NumberFormatException ex) {
                throw new InvalidDataException("Bad query version number: " + query.version + " (" + resolveValue(query.version, actProps) + ")");
            }
        }
        else return null;
    }

    @Deprecated
    public String getScriptName(CastorHashMap actProps) {
        try {
            return getScript(actProps).getName();
        }
        catch (Exception e) {
            return null;
        }
    }

    @Deprecated
    public int getScriptVersion(CastorHashMap actProps) throws InvalidDataException {
        try {
            return getScript(actProps).getVersion();
        }
        catch (Exception e) {
            return -1;
        }
    }

    @Override
    public String toString() {
        return getName()+"[id:"+getId()+"]";
    };

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        return result;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other)                  return true;
        if (other == null)                  return false;
        if (getClass() != other.getClass()) return false;
        if (id != ((Transition)other).id)   return false;

        return true;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy