/* * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you 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 org.wso2.carbon.identity.workflow.mgt; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.xpath.AXIOMXPath; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jaxen.JaxenException; import org.wso2.carbon.base.MultitenantConstants; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.identity.workflow.mgt.bean.Parameter; import org.wso2.carbon.identity.workflow.mgt.bean.Workflow; import org.wso2.carbon.identity.workflow.mgt.bean.WorkflowAssociation; import org.wso2.carbon.identity.workflow.mgt.dao.RequestEntityRelationshipDAO; import org.wso2.carbon.identity.workflow.mgt.dao.WorkflowDAO; import org.wso2.carbon.identity.workflow.mgt.dao.WorkflowRequestAssociationDAO; import org.wso2.carbon.identity.workflow.mgt.dao.WorkflowRequestDAO; import org.wso2.carbon.identity.workflow.mgt.dto.WorkflowRequest; import org.wso2.carbon.identity.workflow.mgt.exception.InternalWorkflowException; import org.wso2.carbon.identity.workflow.mgt.exception.WorkflowException; import org.wso2.carbon.identity.workflow.mgt.extension.WorkflowRequestHandler; import org.wso2.carbon.identity.workflow.mgt.internal.WorkflowServiceDataHolder; import org.wso2.carbon.identity.workflow.mgt.listener.WorkflowExecutorManagerListener; import org.wso2.carbon.identity.workflow.mgt.util.ExecutorResultState; import org.wso2.carbon.identity.workflow.mgt.util.WorkflowRequestBuilder; import org.wso2.carbon.identity.workflow.mgt.util.WorkflowRequestStatus; import org.wso2.carbon.identity.workflow.mgt.workflow.AbstractWorkflow; import org.wso2.carbon.user.api.UserStoreException; import java.util.List; import java.util.Map; import java.util.UUID; public class WorkFlowExecutorManager { private static WorkFlowExecutorManager instance = new WorkFlowExecutorManager(); private static Log log = LogFactory.getLog(WorkFlowExecutorManager.class); private WorkFlowExecutorManager() { } public static WorkFlowExecutorManager getInstance() { return instance; } /** * Called when initiate a request that can be engaged with a workflow. Here it determine if operation has engaged * with a workflow or not. If workflows engaged this will deploy communicate with relevant workflow engine and * return false which will stop continuation of operation. Otherwise this will return true. * * @param workFlowRequest Workflow request object with request attributes. * @return * @throws WorkflowException */ public WorkflowExecutorResult executeWorkflow(WorkflowRequest workFlowRequest) throws WorkflowException { WorkflowRequestAssociationDAO workflowRequestAssociationDAO = new WorkflowRequestAssociationDAO(); List<WorkflowExecutorManagerListener> workflowListenerList = WorkflowServiceDataHolder.getInstance().getExecutorListenerList(); for (WorkflowExecutorManagerListener workflowListener : workflowListenerList) { if (workflowListener.isEnable()) { workflowListener.doPreExecuteWorkflow(workFlowRequest); } } if (StringUtils.isBlank(workFlowRequest.getUuid())) { workFlowRequest.setUuid(UUID.randomUUID().toString()); } OMElement xmlRequest = WorkflowRequestBuilder.buildXMLRequest(workFlowRequest); WorkflowRequestAssociationDAO requestAssociationDAO = new WorkflowRequestAssociationDAO(); WorkflowDAO workflowDAO = new WorkflowDAO(); List<WorkflowAssociation> associations = requestAssociationDAO.getWorkflowAssociationsForRequest(workFlowRequest.getEventType(), workFlowRequest .getTenantId()); if (CollectionUtils.isEmpty(associations)) { return new WorkflowExecutorResult(ExecutorResultState.NO_ASSOCIATION); } boolean workflowEngaged = false; boolean requestSaved = false; for (WorkflowAssociation association : associations) { try { AXIOMXPath axiomxPath = new AXIOMXPath(association.getAssociationCondition()); if (axiomxPath.booleanValueOf(xmlRequest)) { workflowEngaged = true; if (!requestSaved) { WorkflowRequestDAO requestDAO = new WorkflowRequestDAO(); int tenant = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); String currentUser = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername(); requestDAO.addWorkflowEntry(workFlowRequest, currentUser, tenant); requestSaved = true; } String relationshipId = UUID.randomUUID().toString(); WorkflowRequest requestToSend = workFlowRequest.clone(); requestToSend.setUuid(relationshipId); Workflow workflow = workflowDAO.getWorkflow(association.getWorkflowId()); AbstractWorkflow templateImplementation = WorkflowServiceDataHolder.getInstance() .getWorkflowImpls().get(workflow.getTemplateId()).get(workflow.getWorkflowImplId()); List<Parameter> parameterList = workflowDAO.getWorkflowParams(association.getWorkflowId()); templateImplementation.execute(requestToSend, parameterList); workflowRequestAssociationDAO.addNewRelationship(relationshipId, association.getWorkflowId(), workFlowRequest .getUuid(), WorkflowRequestStatus.PENDING .toString(), workFlowRequest.getTenantId()); } } catch (JaxenException e) { String errorMsg = "Error when executing the xpath expression:" + association.getAssociationCondition() + " , on " + xmlRequest; log.error(errorMsg, e); return new WorkflowExecutorResult(ExecutorResultState.FAILED, errorMsg); } catch (CloneNotSupportedException e) { String errorMsg = "Error while cloning workflowRequest object at executor manager."; log.error(errorMsg, e); return new WorkflowExecutorResult(ExecutorResultState.FAILED, errorMsg); } } if (!workflowEngaged) { //handleCallback(workFlowRequest, WorkflowRequestStatus.SKIPPED.toString(), null, ""); return new WorkflowExecutorResult(ExecutorResultState.CONDITION_FAILED); } WorkflowExecutorResult finalResult = new WorkflowExecutorResult(ExecutorResultState.STARTED_ASSOCIATION); for (WorkflowExecutorManagerListener workflowListener : workflowListenerList) { if (workflowListener.isEnable()) { workflowListener.doPostExecuteWorkflow(workFlowRequest, finalResult); } } return finalResult; } private void handleCallback(WorkflowRequest request, String status, Map<String, Object> additionalParams, String requestWorkflowId) throws WorkflowException { WorkflowRequestAssociationDAO workflowRequestAssociationDAO = new WorkflowRequestAssociationDAO(); if (request != null) { WorkflowRequestDAO workflowRequestDAO = new WorkflowRequestDAO(); String requestId = request.getUuid(); workflowRequestAssociationDAO.updateStatusOfRelationship(requestWorkflowId, status); workflowRequestDAO.updateLastUpdatedTimeOfRequest(requestId); if (StringUtils.isNotBlank(requestWorkflowId) && WorkflowRequestStatus.DELETED.toString().equals (workflowRequestDAO.retrieveStatusOfWorkflow(request.getUuid()))) { log.info("Callback received for request " + requestId + " which is already deleted by user. "); return; } if (status.equals(WorkflowRequestStatus.APPROVED.toString()) && !isAllWorkflowsCompleted (workflowRequestAssociationDAO, requestId)) { return; } String eventId = request.getEventType(); WorkflowRequestHandler requestHandler = WorkflowServiceDataHolder.getInstance().getRequestHandler(eventId); if (requestHandler == null) { throw new InternalWorkflowException("No request handlers registered for the id: " + eventId); } if (request.getTenantId() == MultitenantConstants.INVALID_TENANT_ID) { throw new InternalWorkflowException( "Invalid tenant id for request " + eventId + " with id" + requestId); } PrivilegedCarbonContext.startTenantFlow(); PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext .getThreadLocalCarbonContext(); try { String tenantDomain = WorkflowServiceDataHolder.getInstance().getRealmService().getTenantManager() .getDomain(request.getTenantId()); carbonContext.setTenantId(request.getTenantId()); carbonContext.setTenantDomain(tenantDomain); requestHandler.onWorkflowCompletion(status, request, additionalParams); updateDBAtWorkflowCompletion(requestId, status); } catch (WorkflowException e) { updateDBAtWorkflowCompletion(requestId, WorkflowRequestStatus.FAILED.toString()); throw e; } catch (UserStoreException e) { updateDBAtWorkflowCompletion(requestId, WorkflowRequestStatus.FAILED.toString()); throw new InternalWorkflowException("Error when getting tenant domain for tenant id " + request .getTenantId()); } finally { PrivilegedCarbonContext.endTenantFlow(); } } } private boolean isAllWorkflowsCompleted(WorkflowRequestAssociationDAO workflowRequestAssociationDAO, String requestId) throws InternalWorkflowException { List<String> statesOfRequest = workflowRequestAssociationDAO.getWorkflowStatesOfRequest(requestId); for (int i = 0; i < statesOfRequest.size(); i++) { if (!statesOfRequest.get(i).equals(WorkflowRequestStatus.APPROVED.toString())) { return false; } } return true; } /** * Called when callback received for a pending operation. * * @param uuid Unique ID of request * @param status Status of approval/disapproval * @param additionalParams Additional parameters required to execute operation. * @throws WorkflowException */ public void handleCallback(String uuid, String status, Map<String, Object> additionalParams) throws WorkflowException { List<WorkflowExecutorManagerListener> workflowListenerList = WorkflowServiceDataHolder.getInstance().getExecutorListenerList(); for (WorkflowExecutorManagerListener workflowListener : workflowListenerList) { if (workflowListener.isEnable()) { workflowListener.doPreHandleCallback(uuid, status, additionalParams); } } WorkflowRequestAssociationDAO workflowRequestAssociationDAO = new WorkflowRequestAssociationDAO(); String requestId = workflowRequestAssociationDAO.getRequestIdOfRelationship(uuid); WorkflowRequestDAO requestDAO = new WorkflowRequestDAO(); WorkflowRequest request = requestDAO.retrieveWorkflow(requestId); handleCallback(request, status, additionalParams, uuid); for (WorkflowExecutorManagerListener workflowListener : workflowListenerList) { if (workflowListener.isEnable()) { workflowListener.doPostHandleCallback(uuid, status, additionalParams); } } } /** * Update the state and delete relationships of request at workflow completion. * * @param requestId * @param status * @throws InternalWorkflowException */ private void updateDBAtWorkflowCompletion(String requestId, String status) throws InternalWorkflowException { RequestEntityRelationshipDAO requestEntityRelationshipDAO = new RequestEntityRelationshipDAO(); WorkflowRequestDAO workflowRequestDAO = new WorkflowRequestDAO(); requestEntityRelationshipDAO.deleteRelationshipsOfRequest(requestId); workflowRequestDAO.updateStatusOfRequest(requestId, status); } }