package ee.telekom.workflow.executor; import java.lang.invoke.MethodHandles; import java.util.Collection; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import ee.telekom.workflow.core.archive.ArchiveService; import ee.telekom.workflow.core.common.UnexpectedStatusException; import ee.telekom.workflow.core.notification.ExceptionNotificationService; import ee.telekom.workflow.core.workflowinstance.WorkflowInstance; import ee.telekom.workflow.core.workflowinstance.WorkflowInstanceService; import ee.telekom.workflow.core.workflowinstance.WorkflowInstanceStatus; import ee.telekom.workflow.core.workitem.WorkItem; import ee.telekom.workflow.core.workitem.WorkItemService; import ee.telekom.workflow.core.workitem.WorkItemType; import ee.telekom.workflow.executor.marshall.GraphInstanceRepository; import ee.telekom.workflow.executor.marshall.Marshaller; import ee.telekom.workflow.executor.marshall.TokenState; import ee.telekom.workflow.executor.plugin.WorkflowEnginePlugin; import ee.telekom.workflow.graph.Environment; import ee.telekom.workflow.graph.GraphInstance; import ee.telekom.workflow.graph.GraphWorkItem; import ee.telekom.workflow.graph.WorkflowException; import ee.telekom.workflow.util.CallUtil; @Component public class WorkflowExecutorImpl implements WorkflowExecutor{ private static final Logger log = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() ); @Autowired private WorkflowInstanceService workflowInstanceService; @Autowired private WorkItemService workItemService; @Autowired private ArchiveService archiveService; @Autowired private GraphInstanceRepository graphInstanceRepository; @Autowired private GraphEngineFactory engineFactory; @Autowired private WorkflowEnginePlugin plugin; @Autowired private PlatformTransactionManager platformTransactionManager; @Autowired private ExceptionNotificationService exceptionNotificationService; /* * A few words on the treatment of UnexpectedStatusException's. * * In general, these exception should only occur as consequence of a * concurrent status transition. A concurrent status transition may be * caused by a user requesting a process to abort. For that reason, the * given database changes are rolled rolled back and the process execution * is unlocked (so that it may be polled for the aborting logic). No entry * is added to the error logging tables. */ @Override public void startWorkflow( long woinRefNum ){ log.info( "Starting" ); TransactionStatus status = null; try{ workflowInstanceService.markStarting( woinRefNum ); status = begin(); WorkflowInstance woin = workflowInstanceService.find( woinRefNum ); Environment env = Marshaller.deserializeEnv( woin.getAttributes() ); GraphInstance graphInstance = engineFactory.getSingletonInstance().start( woin.getWorkflowName(), woin.getWorkflowVersion(), env, woinRefNum ); graphInstanceRepository.save( graphInstance, WorkflowInstanceStatus.EXECUTED ); commit( status, "Started" ); } catch( UnexpectedStatusException e ){ // See above for a reasoning on why these exceptions are caught separately. log.warn( e.getMessage() ); if( status != null ){ rollback( status ); } workflowInstanceService.unlock( woinRefNum ); log.warn( "Unlocked", e ); } catch( Exception e ){ log.warn( "Handling error", e ); rollback( status ); try{ workflowInstanceService.handleStartingError( woinRefNum, e ); } catch( Exception e2 ){ log.error( "Handling error failed.", e2 ); } exceptionNotificationService.handleException( e ); } } @Override public void abortWorkflow( long woinRefNum ){ log.info( "Aborting" ); TransactionStatus status = null; try{ workflowInstanceService.markAborting( woinRefNum ); status = begin(); WorkflowInstance woin = workflowInstanceService.find( woinRefNum ); if( woin.getState() == null ){ // Abort a workflow instance that has not started previously (at least not successfully) woin.setHistory( "abort|aborted|" ); workflowInstanceService.updateHistory( woin.getRefNum(), woin.getHistory() ); workflowInstanceService.markAborted( woin.getRefNum() ); workflowInstanceService.unlock( woin.getRefNum() ); archiveService.archive( woin.getRefNum() ); } else if( engineFactory.getGraph( woin.getWorkflowName(), woin.getWorkflowVersion() ) != null ){ // Abort a workflow instance that has been started and which is associated to an existing graph GraphInstance graphInstance = graphInstanceRepository.load( woin.getRefNum() ); engineFactory.getSingletonInstance().abort( graphInstance ); graphInstanceRepository.save( graphInstance, WorkflowInstanceStatus.ABORTED ); } else{ // Abort a workflow instance that has been started and which is associated to a graph that is no longer // existing (e.g. because the particular graph version has been removed) List<WorkItem> woits = workItemService.findActiveByWoinRefNum( woin.getRefNum() ); for( WorkItem woit : woits ){ if( WorkItemType.HUMAN_TASK.equals( woit.getType() ) ){ plugin.onHumanTaskCancelled( woin, woit ); } workItemService.markCancelled( woit.getRefNum() ); } Collection<TokenState> tokenStates = Marshaller.deserializeTokenStates( woin.getState() ); for( TokenState tokenState : tokenStates ){ tokenState.setActive( false ); } woin.setState( Marshaller.serializeTokenStates( tokenStates ) ); workflowInstanceService.updateState( woin.getRefNum(), woin.getState() ); woin.setHistory( woin.getHistory() + "abort|aborted|" ); workflowInstanceService.updateHistory( woin.getRefNum(), woin.getHistory() ); workflowInstanceService.markAborted( woin.getRefNum() ); workflowInstanceService.unlock( woin.getRefNum() ); archiveService.archive( woin.getRefNum() ); } commit( status, "Aborted" ); } catch( Exception e ){ log.warn( "Handling error", e ); rollback( status ); try{ workflowInstanceService.handleAbortingError( woinRefNum, e ); } catch( Exception e2 ){ log.error( "Handling error failed.", e2 ); } exceptionNotificationService.handleException( e ); } } @Override public void completeWorkItem( long woinRefNum, long woitRefNum ){ log.info( "Completing" ); TransactionStatus status = null; try{ workflowInstanceService.assertIsExecuting(woinRefNum); workItemService.markCompleting( woitRefNum ); status = begin(); GraphInstance graphInstance = graphInstanceRepository.load( woinRefNum ); GraphWorkItem graphWorkItem = findGraphWorkItem( graphInstance, woitRefNum ); engineFactory.getSingletonInstance().complete( graphWorkItem ); graphInstanceRepository.save( graphInstance, WorkflowInstanceStatus.EXECUTED ); commit( status, "Completed" ); } catch( UnexpectedStatusException e ){ // See above for a reasoning on why these exceptions are caught // separately. log.warn( e.getMessage() ); if( status != null ){ rollback( status ); } workflowInstanceService.unlock( woinRefNum ); log.warn( "Unlocked", e ); } catch( Exception e ){ log.warn( "Handling error", e ); rollback( status ); try{ workItemService.handleCompletingError( woinRefNum, woitRefNum, e ); } catch( Exception e2 ){ log.error( "Handling error failed.", e2 ); } exceptionNotificationService.handleException( e ); } } @Override public void executeTask( long woinRefNum, long woitRefNum ){ log.info( "Executing" ); TransactionStatus status = null; try{ workflowInstanceService.assertIsExecuting(woinRefNum); workItemService.markExecuting( woitRefNum ); status = begin(); WorkItem woit = workItemService.find( woitRefNum ); Object target = engineFactory.getSingletonInstance().getBeanResolver().getBean( woit.getBean() ); Object[] arguments = Marshaller.deserializeTaskArguments( woit.getArguments() ); Object returnValue = CallUtil.call( target, woit.getMethod(), arguments ); String result = Marshaller.serializeResult( returnValue ); workItemService.markExecutedAndSaveResult( woitRefNum, result ); workflowInstanceService.unlock( woinRefNum ); commit( status, "Executed" ); } catch( UnexpectedStatusException e ){ log.warn( e.getMessage() ); if( status != null ){ rollback( status ); } workflowInstanceService.unlock( woinRefNum ); log.warn( "Unlocked", e ); } catch( Exception e ){ log.warn( "Handling error", e ); rollback( status ); try{ workItemService.handleExecutingError( woinRefNum, woitRefNum, e ); } catch( Exception e2 ){ log.error( "Handling error failed.", e2 ); } exceptionNotificationService.handleException( e ); } } private TransactionStatus begin(){ TransactionDefinition definition = new DefaultTransactionDefinition( TransactionDefinition.PROPAGATION_REQUIRES_NEW ); return platformTransactionManager.getTransaction( definition ); } private void commit( TransactionStatus status, String logMessage ){ platformTransactionManager.commit( status ); log.info( logMessage ); } private void rollback( TransactionStatus status ){ if( status != null ){ log.info( "Trying to roll back" ); try{ platformTransactionManager.rollback(status); log.info( "Rolled back" ); } catch( Exception e ){ log.error( "Failed to roll back transaction", e ); } } else{ log.warn( "Cannot roll back, because transaction status is null" ); } } private GraphWorkItem findGraphWorkItem( GraphInstance graphInstance, long externalId ){ for( GraphWorkItem graphWorkItem : graphInstance.getWorkItems() ){ if( graphWorkItem.getExternalId() == externalId ){ return graphWorkItem; } } throw new WorkflowException( "Unknown work item with external id " + externalId ); } }