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

com.centurylink.mdw.service.rest.Processes Maven / Gradle / Ivy

There is a newer version: 6.1.39
Show newest version
/*
 * Copyright (C) 2017 CenturyLink, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.centurylink.mdw.service.rest;

import com.centurylink.mdw.app.ApplicationContext;
import com.centurylink.mdw.common.service.Query;
import com.centurylink.mdw.common.service.ServiceException;
import com.centurylink.mdw.common.service.types.StatusMessage;
import com.centurylink.mdw.constant.OwnerType;
import com.centurylink.mdw.dataaccess.DataAccessException;
import com.centurylink.mdw.dataaccess.DatabaseAccess;
import com.centurylink.mdw.model.*;
import com.centurylink.mdw.model.Value.Display;
import com.centurylink.mdw.model.listener.Listener;
import com.centurylink.mdw.model.report.Hotspot;
import com.centurylink.mdw.model.report.Insight;
import com.centurylink.mdw.model.report.Timepoint;
import com.centurylink.mdw.model.user.Role;
import com.centurylink.mdw.model.user.UserAction.Entity;
import com.centurylink.mdw.model.variable.Variable;
import com.centurylink.mdw.model.workflow.Process;
import com.centurylink.mdw.model.workflow.*;
import com.centurylink.mdw.service.data.process.HierarchyCache;
import com.centurylink.mdw.service.data.process.ProcessCache;
import com.centurylink.mdw.services.DesignServices;
import com.centurylink.mdw.services.ServiceLocator;
import com.centurylink.mdw.services.WorkflowServices;
import com.centurylink.mdw.services.rest.JsonRestService;
import com.centurylink.mdw.util.DateHelper;
import com.centurylink.mdw.util.JsonUtil;
import com.centurylink.mdw.util.log.ActivityLog;
import com.centurylink.mdw.util.log.ActivityLogLine;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import javax.ws.rs.Path;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.*;

@Path("/Processes")
@Api("Workflow process instances and values")
public class Processes extends JsonRestService implements JsonExportable {

    @Override
    protected Entity getEntity(String path, Object content, Map headers) {
        return Entity.ProcessInstance;
    }

    @Override
    public List getRoles(String path) {
        List roles = super.getRoles(path);
        roles.add(Role.PROCESS_EXECUTION);
        return roles;
    }

    private WorkflowServices getWorkflowServices() {
        return ServiceLocator.getWorkflowServices();
    }
    private DesignServices getDesignServices() {
        return ServiceLocator.getDesignServices();
    }

    /**
     * Retrieve process instance(s).
     */
    @Override
    @Path("/{instanceId}/{subData}/{subId}")
    @ApiOperation(value="Retrieve a process or process values, query many processes, or perform throughput queries",
        notes="If instanceId and special are not present, returns a page of processes that meet query criteria. "
          + "If {special} is 'run', then {subData} must be procDefId and an empty ProcessRun is returned. "
          + "If {subData} is 'values', and then {subId} can be varName or expression (otherwise all populated values are returned). "
          + "If {subData} is 'summary' then a only summary-level process info is returned.",
        response=ProcessInstance.class, responseContainer="List")
    public JSONObject get(String path, Map headers)
    throws ServiceException, JSONException {
        WorkflowServices workflowServices = ServiceLocator.getWorkflowServices();
        Query query = getQuery(path, headers);
        try {
            String segOne = getSegment(path, 1);
            if (segOne != null) {
                try {
                    long id = Long.parseLong(segOne);
                    String segTwo = getSegment(path, 2);
                    if ("values".equalsIgnoreCase(segTwo)) {
                        String varName = getSegment(path, 3);
                        if (varName != null) {
                            // individual value
                            Value value = workflowServices.getProcessValue(id, varName);
                            return value.getJson();
                        }
                        else {
                            // all values
                            Map values = workflowServices.getProcessValues(id, query.getBooleanFilter("includeEmpty"));
                            JSONObject valuesJson = new JsonObject();
                            for (String name : values.keySet()) {
                                valuesJson.put(name, values.get(name).getJson());
                            }
                            return valuesJson;
                        }
                    }
                    else if ("summary".equals(segTwo)) {
                        return getSummary(id);
                    }
                    else if ("log".equals(segTwo)) {
                        ActivityLog log = null;
                        Long[] activityInstanceIds = query.getLongArrayFilter("activityInstanceIds");
                        if (activityInstanceIds != null) {
                            log =  getWorkflowServices().getProcessLog(id, activityInstanceIds);
                        }
                        else {
                            boolean withActivities = query.getBooleanFilter("withActivities");
                            log =  getWorkflowServices().getProcessLog(id, withActivities);
                        }
                        if (log == null)
                            throw new ServiceException(ServiceException.NOT_FOUND, "Log not found for process: " + id);
                        return log.getJson();
                    }
                    else {
                        JSONObject json = getProcess(id).getJson();
                        json.put("retrieveDate", DateHelper.serviceDateToString(DatabaseAccess.getDbDate()));
                        return json;
                    }
                }
                catch (NumberFormatException ex) {
                    // path must be special
                    if (segOne.equals("definitions")) {
                        return getDefinitions(query).getJson();
                    }
                    else if (segOne.equals("run")) {
                        String[] segments = getSegments(path);
                        if (segments.length != 3 && segments.length != 4)
                            throw new ServiceException(ServiceException.BAD_REQUEST, "Missing path segment {subData} (procDefId|assetPath)");
                        if (segments.length == 3) {
                            try {
                                return getProcessRun(Long.parseLong(segments[2]), getAuthUser(headers)).getJson();
                            }
                            catch (NumberFormatException nfe) {
                                throw new ServiceException(ServiceException.BAD_REQUEST, "Bad definitionId: " + segments[2]);
                            }
                        }
                        else {
                            return getProcessRun(segments[2] + '/' + segments[3], getAuthUser(headers)).getJson();
                        }
                    }
                    else if (segOne.equals("tops")) {
                        return getTops(query).getJson();
                    }
                    else if (segOne.equals("breakdown")) {
                        return getBreakdown(query).getJson();
                    }
                    else if (segOne.equals("insights")) {
                        JsonList jsonList = getInsights(query);
                        JSONObject json = jsonList.getJson();
                        String trend = query.getFilter("trend");
                        if ("completionTime".equals(trend)) {
                            List timepoints = workflowServices.getProcessTrend(query);
                            json.put("trend", new JsonList<>(timepoints, "trend").getJson().getJSONArray("trend"));
                        }
                        return json;
                    }
                    else if (segOne.equals("hotspots")) {
                        return getHotspots(query).getJson();
                    }
                    else {
                        throw new ServiceException(ServiceException.BAD_REQUEST, "Unsupported path segment: " + segOne);
                    }
                }
            }
            else {
                long triggerId = query.getLongFilter("triggerId");
                if (triggerId > 0) {
                    // retrieve instance by trigger -- just send summary
                    return getSummaryJson(workflowServices.getProcessForTrigger(triggerId));
                }
                else {
                    long callHierarchyFor = query.getLongFilter("callHierarchyFor");
                    if (callHierarchyFor != -1) {
                        Linked linkedInstance = ServiceLocator.getWorkflowServices().getCallHierearchy(callHierarchyFor);
                        return linkedInstance.getJson(1);
                    }
                    else {
                        // general process instance list query
                        ProcessList processList = workflowServices.getProcesses(query);
                        if (query.getLongFilter("activityInstanceId") > 0) {
                            // retrieving summary for activity instance
                            if (processList.getCount() == 0)
                                throw new ServiceException(ServiceException.NOT_FOUND, "Process instance not found: " + query);
                            else if (OwnerType.MAIN_PROCESS_INSTANCE.equals(processList.getProcesses().get(0).getOwner())) {
                                return getSummaryJson(workflowServices.getProcess(processList.getProcesses().get(0).getOwnerId(), true));
                            }
                            return getSummaryJson(processList.getProcesses().get(0));
                        }
                        else {
                            return processList.getJson();
                        }
                    }
                }
            }
        }
        catch (ServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new ServiceException(ServiceException.INTERNAL_ERROR, ex.getMessage(), ex);
        }
    }

    /**
     * Returns main process instance if embedded instance.
     */
    @Path("/{instanceId}/summary")
    public JSONObject getSummary(Long instanceId) throws ServiceException {
        ProcessInstance process = getWorkflowServices().getProcess(instanceId);
        if (process.isEmbedded())
            process = getWorkflowServices().getProcess(process.getOwnerId());
        return getSummaryJson(process);
    }

    protected JSONObject getSummaryJson(ProcessInstance process) {
        JSONObject summary = new JsonObject();
        summary.put("id", process.getId());
        summary.put("name", process.getProcessName());
        summary.put("packageName", process.getPackageName());
        summary.put("version", process.getProcessVersion());
        summary.put("masterRequestId", process.getMasterRequestId());
        summary.put("definitionId", process.getProcessId());
        summary.put("status", process.getStatus());
        summary.put("template", process.getTemplate());
        if (process.getTemplate() != null) {
            summary.put("templatePackage", process.getTemplatePackage());
            summary.put("templateVersion", process.getTemplateVersion());
            Process latestTemplate = ProcessCache.getProcess(process.getTemplatePackage() + "/" + process.getTemplate());
            // If null it means it is archived but was renamed or removed from current assets
            if (latestTemplate == null || !latestTemplate.getId().equals(process.getProcessId()))
                summary.put("archived", true);
            if (HierarchyCache.hasMilestones(latestTemplate.getId()))
                summary.put("hasMilestones", true);
        }
        else {
            Process latest = ProcessCache.getProcess(process.getPackageName() + "/" + process.getProcessName());
            // If null it means it is archived but was renamed or removed from current assets
            if (latest == null || !latest.getId().equals(process.getProcessId()))
                summary.put("archived", true);
            if (latest != null && HierarchyCache.hasMilestones(latest.getId()))
                summary.put("hasMilestones", true);
        }
        return summary;
    }

    @Override
    @Path("/{instanceId}/values")
    @ApiOperation(value="Update value(s) for a process instance",
        notes="Values are created or updated based on the passed JSON object.",
        response=StatusMessage.class)
    @ApiImplicitParams({
        @ApiImplicitParam(name="Values", paramType="body", dataType="java.lang.Object")})
    public JSONObject put(String path, JSONObject content, Map headers)
            throws ServiceException, JSONException {
        String id = getSegment(path, 1);
        if (id == null)
            throw new ServiceException(HTTP_400_BAD_REQUEST, "Missing path segment: {instanceId}");
        try {
            Long instanceId = Long.parseLong(id);
            if ("values".equals(getSegment(path, 2))) {
                Map values = JsonUtil.getMap(content);
                WorkflowServices workflowServices = ServiceLocator.getWorkflowServices();
                for (String varName : values.keySet()) {
                    workflowServices.setVariable(instanceId, varName, values.get(varName));
                }
                return null;
            }
            else {
                throw new ServiceException(HTTP_400_BAD_REQUEST, "Missing path segment: values");
            }
        }
        catch (NumberFormatException ex) {
            throw new ServiceException(HTTP_400_BAD_REQUEST, "Invalid instance id: " + id);
        }
        catch (ServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new ServiceException(ex.getMessage(), ex);
        }
    }

    @Override
    @Path("/run/{definitionId|processPath}")
    @ApiOperation(value="Run a process", response=ProcessRun.class)
    @ApiImplicitParams({
        @ApiImplicitParam(name="Values", paramType="body", dataType="com.centurylink.mdw.model.workflow.ProcessRun")})
    public JSONObject post(String path, JSONObject content, Map headers)
            throws ServiceException, JSONException {
        try {
            String[] segments = getSegments(path);
            if (segments.length > 1 && segments[1].equals("run")) {
                WorkflowServices workflowServices = ServiceLocator.getWorkflowServices();
                ProcessRun run = new ProcessRun(content);
                if (headers.get("genmasterrequestid") != null) {
                    run.setMasterRequestId(getAuthUser(headers) + "-" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()));
                }
                if (run.getMasterRequestId() == null || run.getMasterRequestId().isEmpty())
                    throw new ServiceException(ServiceException.BAD_REQUEST, "Missing master request id");
                if (ServiceLocator.getRequestServices().getMasterRequest(run.getMasterRequestId()) != null)
                    throw new ServiceException(ServiceException.BAD_REQUEST, "Master request ID: " + run.getMasterRequestId() + " already exists");

                if (segments.length !=3 && segments.length != 4)
                    throw new ServiceException(ServiceException.BAD_REQUEST, "Bad path: " + path);

                Process proc;
                if (segments.length == 3) {
                    String defId = segments[2];
                    try {
                        Long definitionId = Long.parseLong(defId);
                        if (!definitionId.equals(run.getDefinitionId()))
                            throw new ServiceException(ServiceException.BAD_REQUEST, "Path/body mismatch for definitionId: " + definitionId + "/" + run.getDefinitionId());
                        proc = ProcessCache.getProcess(definitionId);
                        if (proc == null)
                            throw new ServiceException(ServiceException.NOT_FOUND, "Process not found: " + definitionId);
                    }
                    catch (NumberFormatException ex) {
                        throw new ServiceException(ServiceException.BAD_REQUEST, "Bad definitionId: " + defId);
                    }
                }
                else {
                    String procPath = segments[2] + "/" + segments[3];
                    proc = ProcessCache.getProcess(procPath);
                    if (proc == null)
                        throw new ServiceException(ServiceException.NOT_FOUND, "Process not found: " + procPath);
                }
                run.setDefinitionId(proc.getId());
                String validationError = "";
                Map inputValues = getInputValues(proc);
                for (String inputVarName : inputValues.keySet()) {
                    Value inputVar = inputValues.get(inputVarName);
                    Display display = inputVar.getDisplay();
                    boolean populated = run.getValueNames().contains(inputVarName);
                    if (display == Display.ReadOnly && populated) {
                        validationError += (validationError.isEmpty() ? "" : ", ") + "ReadOnly: "
                                + (inputVar.getLabel() == null ? inputVar.getName() : inputVar.getLabel());
                    }
                    else if (display == Display.Required && !populated) {
                        validationError += (validationError.isEmpty() ? "" : ", ") + "Required: "
                                + (inputVar.getLabel() == null ? inputVar.getName() : inputVar.getLabel());
                    }
                }
                if (!validationError.isEmpty())
                    throw new ServiceException(ServiceException.BAD_REQUEST, validationError);
                run = workflowServices.runProcess(run);
                headers.put("Location", ApplicationContext.getServicesUrl() + "/services/Processes/" + run.getInstanceId());
                headers.put(Listener.METAINFO_HTTP_STATUS_CODE, String.valueOf(Status.CREATED.getCode()));
                return run.getJson();
            }
            else {
                throw new ServiceException(ServiceException.BAD_REQUEST, "Missing path segment: run");
            }
        }
        catch (ServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new ServiceException(ex.getMessage(), ex);
        }
    }

    @Override
    @Path("/{instanceId}")
    @ApiOperation(value="Cancel a process instance")
    public JSONObject delete(String path, JSONObject content, Map headers) throws ServiceException {
        String processInstanceSegment = getSegment(path, 1);
        if (processInstanceSegment == null)
            throw new ServiceException(ServiceException.BAD_REQUEST, "Missing path segment: instanceId");
        try {
            Long processInstanceId = Long.parseLong(processInstanceSegment);
            ServiceLocator.getEventServices().cancelProcess(processInstanceId);
            headers.put(Listener.METAINFO_HTTP_STATUS_CODE, String.valueOf(Status.ACCEPTED.getCode()));
            return null;
        } catch (NumberFormatException ex) {
            throw new ServiceException(ServiceException.BAD_REQUEST, "Invalid instanceId: " + processInstanceSegment);
        } catch (DataAccessException ex) {
            throw new ServiceException(ServiceException.INTERNAL_ERROR, ex);
        }
    }

    @Override
    public Jsonable toJsonable(Query query, JSONObject json) throws JSONException {
        String path = query.getPath();
        try {
            if (json.has(ProcessList.PROCESS_INSTANCES)) {
                return new ProcessList(ProcessList.PROCESS_INSTANCES, json);
            }
            else if ("Processes/breakdown".equals(path)) {
                return new JsonListMap<>(json, ProcessAggregate.class);
            }
            else if (path != null && path.startsWith("Processes/") && path.endsWith("/log")) {
                JSONObject listObj = new JSONObject();
                listObj.put("Process Log", json.getJSONArray("logLines"));
                return new JsonList<>(listObj, ActivityLogLine.class);
            }
            else {
                throw new JSONException("Unsupported export type for query: " + query);
            }
        }
        catch (ParseException ex) {
            throw new JSONException(ex);
        }
    }

    @Override
    public String getExportName() { return "Processes"; }

    private Map getVariables(Map params) {
        Map variables = new HashMap<>();
        for (String key : params.keySet()) {
            if (!processParams.contains(key) && !standardParams.contains(key)) {
                variables.put(key, params.get(key));
            }
        }
        return variables.isEmpty() ? null : variables;
    }

    private Map getCriteria(Map params) {
        Map criteria = new HashMap<>();
        for (String key : params.keySet()) {
            if (processParams.contains(key))
                criteria.put(key, params.get(key));
        }
        return criteria.isEmpty() ? null : criteria;
    }

    private static final List processParams = Arrays.asList(
            "processId",
            "processIdList",
            "processName",
            "id",
            "ownerId",
            "owner",
            "masterRequestId",
            "masterRequestIdIgnoreCase",
            "statusCode",
            "startDateFrom",
            "startDatefrom",
            "startDateTo",
            "startDateto",
            "endDateFrom",
            "endDatefrom",
            "endDateTo",
            "endDateto"
    );

    private static final List standardParams = Arrays.asList(
            "pageIndex",
            "pageSize",
            "orderBy",
            "mdw-app",
            "format"
    );

    private Map getInputValues(Process process) {
        Map inputVals = new HashMap<>();
        for (Variable var : process.getVariables()) {
            if (var.isInput()) {
                inputVals.put(var.getName(), var.toValue());
            }
        }
        return inputVals;
    }

    @Path("/definitions")
    public JsonArray getDefinitions(Query query) throws ServiceException {
        List processVOs = getDesignServices().getProcessDefinitions(query);
        JSONArray jsonProcesses = new JSONArray();
        for (Process processVO : processVOs) {
            JSONObject jsonProcess = new JsonObject();
            jsonProcess.put("packageName", processVO.getPackageName());
            jsonProcess.put("processId", processVO.getId());
            jsonProcess.put("name", processVO.getName());
            jsonProcess.put("version", processVO.getVersionString());
            jsonProcesses.put(jsonProcess);
        }
        return new JsonArray(jsonProcesses);
    }

    @Path("/run/{definitionId}")
    public ProcessRun getProcessRun(Long definitionId, String authUser) throws ServiceException {
        try {
            Process definition = getDesignServices().getProcessDefinition(definitionId);
            if (definition == null)
                throw new ServiceException(ServiceException.NOT_FOUND, "Process definition not found: " + definitionId);
            return getProcessRun(definition, authUser);
        } catch (IOException ex) {
            throw new ServiceException(ServiceException.INTERNAL_ERROR, "Error loading process " + definitionId, ex);
        }
    }

    @Path("/run/{package}/{process}")
    public ProcessRun getProcessRun(String assetPath, String authUser) throws ServiceException {
        Process definition = getDesignServices().getProcessDefinition(assetPath, null);
        if (definition == null)
            throw new ServiceException(ServiceException.NOT_FOUND, "Process definition not found: " + assetPath);
        return getProcessRun(definition, authUser);
    }

    private ProcessRun getProcessRun(Process processDefinition, String user) {
        ProcessRun processRun = new ProcessRun();
        processRun.setDefinitionId(processDefinition.getId());
        processRun.setMasterRequestId(user + "-" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()));
        processRun.setValues(getInputValues(processDefinition));
        return processRun;
    }

    @Path("/{instanceId}")
    public ProcessInstance getProcess(Long instanceId) throws ServiceException {
        return getWorkflowServices().getProcess(instanceId, true);
    }

    @Path("/tops")
    public JsonArray getTops(Query query) throws ServiceException {
        List list = getWorkflowServices().getTopProcesses(query);
        JSONArray processArray = new JSONArray();
        for (ProcessAggregate processAggregate : list) {
            processArray.put(processAggregate.getJson());
        }
        return new JsonArray(processArray);
    }

    @Path("/breakdown")
    public JsonListMap getBreakdown(Query query) throws ServiceException {
        TreeMap> instMap = getWorkflowServices().getProcessBreakdown(query);
        LinkedHashMap> listMap = new LinkedHashMap<>();
        for (Instant instant : instMap.keySet()) {
            List processAggregates = instMap.get(instant);
            listMap.put(Query.getString(instant), processAggregates);
        }

        return new JsonListMap<>(listMap);
    }

    @Path("/insights")
    public JsonList getInsights(Query query) throws ServiceException {
        List processInsights = getWorkflowServices().getProcessInsights(query);
        JsonList jsonList = new JsonList<>(processInsights, "insights");
        jsonList.setTotal(processInsights.size());
        return jsonList;
    }

    @Path("/hotspots")
    public JsonList getHotspots(Query query) throws ServiceException {
        List processHotspots = getWorkflowServices().getProcessHotspots(query);
        JsonList jsonList = new JsonList<>(processHotspots, "hotspots");
        jsonList.setTotal(processHotspots.size());
        return jsonList;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy