package ee.telekom.workflow.web.console; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.collections.comparators.NullComparator; import org.apache.commons.collections.comparators.ReverseComparator; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import ee.telekom.workflow.core.common.UnexpectedStatusException; import ee.telekom.workflow.core.common.WorkflowEngineConfiguration; import ee.telekom.workflow.core.workflowinstance.WorkflowInstanceStatus; import ee.telekom.workflow.facade.WorkflowEngineFacade; import ee.telekom.workflow.facade.model.WorkflowInstanceFacadeStatus; import ee.telekom.workflow.facade.model.WorkflowInstanceState; import ee.telekom.workflow.web.console.form.SearchWorkflowInstancesForm; import ee.telekom.workflow.web.console.helper.MessageHelper; import ee.telekom.workflow.web.console.model.DataTable; import ee.telekom.workflow.web.console.model.DataTableColumnMapper; import ee.telekom.workflow.web.console.model.WorkflowInstanceSearchModel; /** * Controller for workflow instances search and result view */ @Controller @RequestMapping("/console") @SessionAttributes("instanceSearchForm") public class WorkflowInstancesListController{ @Autowired private WorkflowEngineConfiguration configuration; @Autowired private WorkflowEngineFacade facade; @Autowired private MessageHelper messageHelper; @RequestMapping(value = "/workflow/instances", method = RequestMethod.GET) public String searchInstancesView( Model model, HttpServletRequest request, @ModelAttribute("instanceSearchForm") SearchWorkflowInstancesForm form ){ request.getSession().removeAttribute( "instancesSearchResult" ); form = createFormOnGetRequest( request, form ); if( form.hasId() ){ validateAndConvertIdsToRefNums( form, model ); } else{ form.setRefNum( null ); } removeBlankLabels( form.getLabel1() ); removeBlankLabels( form.getLabel2() ); if( !model.containsAttribute( "error" ) ){ if( form.hasSearchCriteria() ){ request.getSession().setAttribute( "instancesSearchResult", createModels( facade.findWorkflowInstances( form ) ) ); } else{ model.addAttribute( "warning", "workflow.search.message.empty.filter" ); } } model.addAttribute( "instanceSearchForm", form ); model.addAttribute( "graphNames", facade.getDeployedWorkflowNames() ); model.addAttribute( "workflowStatuses", WorkflowInstanceFacadeStatus.values() ); return "console/workflow/instances"; } private SearchWorkflowInstancesForm createFormOnGetRequest( HttpServletRequest request, SearchWorkflowInstancesForm form ){ String workflowName = request.getParameter( "workflowName" ); String status = request.getParameter( "status" ); if( workflowName != null ){ form = new SearchWorkflowInstancesForm(); List<String> workflowNames = new ArrayList<>(); workflowNames.add( workflowName ); form.setWorkflowName( workflowNames ); if( status != null ){ form.setStatus( Collections.unmodifiableList( Arrays.asList( WorkflowInstanceFacadeStatus.valueOf( status ) ) ) ); } } return form; } private void validateAndConvertIdsToRefNums( SearchWorkflowInstancesForm form, Model model ){ List<Long> refNums = new ArrayList<>(); for( String num : form.getId() ){ String value = num.trim(); if( value.length() > 0 && StringUtils.isNumeric( value ) ){ refNums.add( Long.valueOf( value ) ); } else{ model.addAttribute( "error", "workflow.search.error.invalid.id" ); } } form.setRefNum( refNums ); } private List<WorkflowInstanceSearchModel> createModels( List<WorkflowInstanceState> instances ){ List<WorkflowInstanceSearchModel> searchResult = new ArrayList<>(); List<Long> refNums = new ArrayList<>(); for( WorkflowInstanceState woin : instances ){ refNums.add( woin.getRefNum() ); } Map<Long, Date> nextTimerDueDate = facade.getNextActiveTimerDueDates( refNums ); Set<Long> hasActiveHumanTask = facade.getWorkflowInstancesWithActiveHumanTask( refNums ); for( WorkflowInstanceState instance : instances ){ long refNum = instance.getRefNum(); searchResult.add( createModel( instance, nextTimerDueDate.get( refNum ), hasActiveHumanTask.contains( refNum ) ) ); } return searchResult; } private WorkflowInstanceSearchModel createModel( WorkflowInstanceState woin, Date nextTimerDueDate, boolean hasActiveHumanTask ){ WorkflowInstanceSearchModel model = new WorkflowInstanceSearchModel(); model.setRefNum( woin.getRefNum() ); model.setWorkflowNameWithVersion( woin.getWorkflowName() + ":" + messageHelper.getVersionText( woin.getWorkflowVersion() ) ); model.setLabel1( woin.getLabel1() ); model.setLabel2( woin.getLabel2() ); model.setDateCreated( woin.getDateCreated() ); model.setNextTimerDueDate( nextTimerDueDate ); model.setHasActiveHumanTask( messageHelper.getHasActiveHumanTaskText( hasActiveHumanTask ) ); model.setDisplayStatus( messageHelper.getStatusText( WorkflowInstanceStatus.valueOf( woin.getStatus() ) ) ); model.setStatus( woin.getStatus() ); return model; } @RequestMapping(value = "/workflow/instances", method = RequestMethod.POST) public String searchInstancesAction( @ModelAttribute("instanceSearchForm") SearchWorkflowInstancesForm form, RedirectAttributes model ){ model.addFlashAttribute( "instanceSearchForm", form ); return "redirect:" + configuration.getConsoleMappingPrefix() + "/console/workflow/instances"; } @PreAuthorize("hasRole('ROLE_TWE_ADMIN')") @RequestMapping(value = "/workflow/instances/action", method = RequestMethod.POST) public String abortInstances( @RequestParam String action, @ModelAttribute("refNums") List<Long> refNums, RedirectAttributes model ){ switch( action ) { case "abort": invokeAction( refNums, model, new WorkflowInstanceActionInvoker(){ @Override void invoke( Long refNum ){ facade.abortWorkflowInstance( refNum ); } } ); break; case "suspend": invokeAction( refNums, model, new WorkflowInstanceActionInvoker(){ @Override void invoke( Long refNum ){ facade.suspendWorkflowInstance( refNum ); } } ); break; case "resume": invokeAction( refNums, model, new WorkflowInstanceActionInvoker(){ @Override void invoke( Long refNum ){ facade.resumeWorkflowInstance( refNum ); } } ); break; case "retry": invokeAction( refNums, model, new WorkflowInstanceActionInvoker(){ @Override void invoke( Long refNum ){ facade.retryWorkflowInstance( refNum ); } } ); break; } return "redirect:" + configuration.getConsoleMappingPrefix() + "/console/workflow/instances"; } private void invokeAction( List<Long> refNums, RedirectAttributes model, WorkflowInstanceActionInvoker handler ){ for( Long refNum : refNums ){ try{ handler.invoke( refNum ); } catch( UnexpectedStatusException e ){ model.addFlashAttribute( "actionError", "workflow.instances.action.error.unexpectedstatus" ); } } model.addFlashAttribute( "actionMessage", "workflow.instances.action.success" ); } @RequestMapping(method = RequestMethod.POST, value = "/workflow/instances/search", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<DataTable> searchInstancesAjax( Model model, @ModelAttribute("instanceSearchForm") SearchWorkflowInstancesForm form, HttpServletRequest request ){ @SuppressWarnings("unchecked") List<WorkflowInstanceSearchModel> searchResult = (List<WorkflowInstanceSearchModel>)request.getSession().getAttribute( "instancesSearchResult" ); List<WorkflowInstanceSearchModel> page = new ArrayList<>(); if( searchResult != null && searchResult.size() > 0 ){ Integer column = Integer.valueOf( request.getParameter( "order[0][column]" ) ); String direction = request.getParameter( "order[0][dir]" ); List<WorkflowInstanceSearchModel> sortedSearchResult = sortSearchResult( searchResult, column, direction ); int pageStart = Math.min( form.getStart(), Math.max( searchResult.size() - 1, 0 ) ); int pageEnd = Math.min( form.getStart() + form.getLength(), searchResult.size() ); page = sortedSearchResult.subList( pageStart, pageEnd ); } return new ResponseEntity<>( createDataTable( request, searchResult, page ), HttpStatus.OK ); } private DataTable createDataTable( HttpServletRequest request, List<WorkflowInstanceSearchModel> searchResult, List<WorkflowInstanceSearchModel> page ){ DataTable dataTable = new DataTable(); dataTable.setDraw( Integer.valueOf( request.getParameter( "draw" ) ) ); if( searchResult != null ){ dataTable.setRecordsTotal( searchResult.size() ); dataTable.setRecordsFiltered( searchResult.size() ); } dataTable.setData( page ); return dataTable; } protected List<WorkflowInstanceSearchModel> sortSearchResult( List<WorkflowInstanceSearchModel> result, int column, String direction ){ List<WorkflowInstanceSearchModel> unorderedSource = new ArrayList<>( result ); String fieldName = DataTableColumnMapper.from( column ).getFieldName(); BeanComparator<WorkflowInstanceSearchModel> beanComparator; if( "asc".equalsIgnoreCase( direction ) ){ beanComparator = new BeanComparator<>( fieldName, new NullComparator() ); } else{ beanComparator = new BeanComparator<>( fieldName, new ReverseComparator( new NullComparator() ) ); } Collections.sort( unorderedSource, beanComparator ); return unorderedSource; } @ModelAttribute("instanceSearchForm") private SearchWorkflowInstancesForm instanceSearchForm(){ return new SearchWorkflowInstancesForm(); } private static void removeBlankLabels( List<String> labels ){ if( labels != null ){ Iterator<String> iterator = labels.iterator(); while( iterator.hasNext() ){ String label = iterator.next(); if( StringUtils.isBlank( label ) ){ iterator.remove(); } } } } private static abstract class WorkflowInstanceActionInvoker{ abstract void invoke( Long refNum ); } }