package ee.telekom.workflow.core.workitem;
import java.lang.invoke.MethodHandles;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ee.telekom.workflow.core.common.UnexpectedStatusException;
import ee.telekom.workflow.core.workflowinstance.WorkflowInstanceService;
import ee.telekom.workflow.graph.WorkItemStatus;
import ee.telekom.workflow.util.NoStackTraceException;
@Service
@Transactional
public class WorkItemServiceImpl implements WorkItemService{
private static final Logger log = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() );
@Autowired
private WorkflowInstanceService workflowInstanceService;
@Autowired
private WorkItemDao dao;
@Override
public WorkItem find( long refNum ){
return dao.findByRefNum( refNum );
}
@Override
public List<WorkItem> findActiveByWoinRefNum( long woinRefNum ){
return dao.findActiveByWoinRefNum( woinRefNum );
}
@Override
public WorkItem findActiveByWoinRefNumAndTokenId( long woinRefNum, int tokenId ){
return dao.findActiveByWoinRefNumAndTokenId( woinRefNum, tokenId );
}
@Override
public void markExecuting( long refNum ){
updateStatus( refNum, WorkItemStatus.EXECUTING, WorkItemStatus.NEW );
}
@Override
public void markExecutedAndSaveResult( long refNum, String result ){
updateStatusAndResult( refNum, WorkItemStatus.EXECUTED, WorkItemStatus.EXECUTING, result );
}
@Override
public void markCompleting( long refNum ){
// signals, tasks and human tasks have an execution step. timer's don't. therefore we allow transitions from NEW and EXECUTED.
Collection<WorkItemStatus> expectedStatuses = Arrays.asList(
WorkItemStatus.NEW,
WorkItemStatus.EXECUTED );
updateStatus( refNum, WorkItemStatus.COMPLETING, expectedStatuses );
}
@Override
public void markCompleted( long refNum ){
updateStatus( refNum, WorkItemStatus.COMPLETED, WorkItemStatus.COMPLETING );
}
@Override
public void markCancelled( long refNum ){
updateStatus( refNum, WorkItemStatus.CANCELLED, Arrays.asList( WorkItemStatus.NEW, WorkItemStatus.EXECUTED ) );
}
@Override
public void handleExecutingError( long woinRefNum, long woitRefNum, Exception exception ){
workflowInstanceService.handleCompleteError( woinRefNum, woitRefNum, exception );
Collection<WorkItemStatus> expectedStatuses = Arrays.asList(
WorkItemStatus.NEW,
WorkItemStatus.EXECUTING,
WorkItemStatus.EXECUTED );
updateStatus( woitRefNum, WorkItemStatus.EXECUTING_ERROR, expectedStatuses );
}
@Override
public void handleCompletingError( long woinRefNum, long woitRefNum, Exception exception ){
workflowInstanceService.handleCompleteError( woinRefNum, woitRefNum, exception );
Collection<WorkItemStatus> expectedStatuses = Arrays.asList(
WorkItemStatus.EXECUTED,
WorkItemStatus.COMPLETING,
WorkItemStatus.COMPLETED );
updateStatus( woitRefNum, WorkItemStatus.COMPLETING_ERROR, expectedStatuses );
}
@Override
public void rewindAfterError( long refNum ) throws UnexpectedStatusException{
WorkItem woit = find( refNum );
if( WorkItemStatus.EXECUTING_ERROR.equals( woit.getStatus() ) ){
markNewAfterExecutingError( refNum );
}
else if( WorkItemStatus.COMPLETING_ERROR.equals( woit.getStatus() ) ){
if( WorkItemType.TIMER.equals( woit.getType() ) ){
markNewAfterCompletingError( refNum );
}
else{
markExecutedAfterCompletingError( refNum );
}
}
else{
throw new UnexpectedStatusException( Arrays.asList(
WorkItemStatus.EXECUTING_ERROR,
WorkItemStatus.COMPLETING_ERROR ) );
}
}
private void markNewAfterExecutingError( long woitRefNum ) throws UnexpectedStatusException{
updateStatus( woitRefNum, WorkItemStatus.NEW, WorkItemStatus.EXECUTING_ERROR );
}
private void markNewAfterCompletingError( long woitRefNum ) throws UnexpectedStatusException{
updateStatus( woitRefNum, WorkItemStatus.NEW, WorkItemStatus.COMPLETING_ERROR );
}
private void markExecutedAfterCompletingError( long woitRefNum ) throws UnexpectedStatusException{
updateStatus( woitRefNum, WorkItemStatus.EXECUTED, WorkItemStatus.COMPLETING_ERROR );
}
@Override
public void recoverExecuting( String nodeName ) throws UnexpectedStatusException{
int recovered = 0;
int notRecovered = 0;
Collection<WorkItem> workItems = dao.findByNodeNameAndStatus( nodeName, WorkItemStatus.EXECUTING );
for( WorkItem woit : workItems ){
if( !WorkItemType.TASK.equals( woit.getType() ) ){
updateStatus( woit.getRefNum(), WorkItemStatus.NEW, WorkItemStatus.EXECUTING );
workflowInstanceService.unlock( woit.getWoinRefNum() );
recovered++;
}
else{
Exception exception = new NoStackTraceException(
"Automatic recovery is not possible for not tasks that are associated to a failed node! Recover manually!" );
handleExecutingError( woit.getWoinRefNum(), woit.getRefNum(), exception );
notRecovered++;
}
}
log.info( "Recoved/Not recovered {}/{} executing work items for node {}", recovered, notRecovered, nodeName );
}
@Override
public void recoverCompleting( String nodeName ) throws UnexpectedStatusException{
Collection<WorkItem> workItems = dao.findByNodeNameAndStatus( nodeName, WorkItemStatus.COMPLETING );
for( WorkItem woit : workItems ){
updateStatus( woit.getRefNum(), WorkItemStatus.EXECUTED, WorkItemStatus.COMPLETING );
workflowInstanceService.unlock( woit.getWoinRefNum() );
}
log.info( "Recoved {} completing work items for node {}", workItems.size(), nodeName );
}
private void updateStatus( long refNum, WorkItemStatus newStatus, WorkItemStatus expectedStatus ) throws UnexpectedStatusException{
updateStatus( refNum, newStatus, Collections.singletonList( expectedStatus ) );
}
private void updateStatus( long refNum, WorkItemStatus newStatus, Collection<WorkItemStatus> expectedStatuses ) throws UnexpectedStatusException{
boolean updatedFailed = !dao.updateStatus( refNum, newStatus, expectedStatuses );
if( updatedFailed ){
throw new UnexpectedStatusException( expectedStatuses );
}
else{
log.info( "Updated the status of workflow item {} to {}", refNum, newStatus );
}
}
private void updateStatusAndResult( long refNum, WorkItemStatus newStatus, WorkItemStatus expectedStatus, String result ) throws UnexpectedStatusException{
boolean updatedFailed = !dao.updateStatusAndResult( refNum, newStatus, expectedStatus, result );
if( updatedFailed ){
throw new UnexpectedStatusException( expectedStatus );
}
else{
log.info( "Updated the status of workflow item {} to {} and submitted result", refNum, newStatus );
}
}
}