/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * Copyright (c) 2013, MPL CodeInside http://codeinside.ru */ package ru.codeinside.gses.webui.supervisor; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.util.ObjectProperty; import com.vaadin.data.util.PropertysetItem; import com.vaadin.terminal.ThemeResource; import com.vaadin.ui.Button; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Table; import com.vaadin.ui.TextArea; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; import commons.Exceptions; import org.activiti.engine.ManagementService; import org.activiti.engine.ProcessEngine; import org.activiti.engine.runtime.Job; import org.activiti.engine.runtime.JobQuery; import org.activiti.engine.runtime.ProcessInstance; import ru.codeinside.adm.AdminService; import ru.codeinside.adm.database.Bid; import ru.codeinside.adm.database.Procedure; import ru.codeinside.gses.lazyquerycontainer.LazyQueryContainer; import ru.codeinside.gses.service.F1; import ru.codeinside.gses.service.F3; import ru.codeinside.gses.service.Fn; import ru.codeinside.gses.webui.Flash; import ru.codeinside.gses.webui.executor.ExecutorFactory; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import static ru.codeinside.gses.webui.utils.Components.stringProperty; final public class ExceptionsPanel extends VerticalLayout { final Persistence persistence = new Persistence(); final LazyQueryContainer container = persistence.createContainer(); final ErrorBlock errorBlock = new ErrorBlock(); final Table table = new Table(null, container); final PersistenceFilter filter = new PersistenceFilter(table, persistence); public ExceptionsPanel() { setSizeFull(); setMargin(true); setSpacing(true); addComponent(filter); table.setDescription("Процессы, приостановленные из-за ошибок"); table.setSizeFull(); table.setColumnHeaders(new String[]{ "Заявка", "Дата подачи заявки", "Процедура", "Версия", "Процесс", "Ветвь", "Ошибка" }); table.setColumnIcon("eid", new ThemeResource("icon/branch.png")); table.setSelectable(true); table.setNullSelectionAllowed(true); table.setImmediate(true); table.addListener(new JobClickListener()); // table.setColumnExpandRatio("e", 0.1f); addComponent(table); setExpandRatio(table, 0.3f); addComponent(errorBlock); setExpandRatio(errorBlock, 0.7f); } final private class JobClickListener implements Property.ValueChangeListener { @Override public void valueChange(Property.ValueChangeEvent event) { final Table table = (Table) event.getProperty(); final Object index = table.getValue(); String jobId = null; if (index != null) { final Item item = table.getItem(index); jobId = Fn.getValue(item, "jid", String.class); } errorBlock.setJobInfo(persistence.getSingle(jobId)); } } final static class JobInfo implements Serializable { final String jobId; final String executionId; final String definitionId; final String exception; JobInfo(final String jobId, final String executionId, final String definitionId, final String exception) { this.jobId = jobId; this.executionId = executionId; this.definitionId = definitionId; this.exception = exception; } JobInfo copy() { return new JobInfo(jobId, executionId, definitionId, null); } } final static class Persistence extends SimpleQuery implements FilterablePersistence { private String processInstanceFilter; public Persistence() { super(false, 10); addProperty("bid", Long.class, null, true, false); addProperty("startDate", String.class, null, true, false); addProperty("name", String.class, null, true, false); addProperty("ver", String.class, null, true, false); addProperty("pid", String.class, null, true, false); addProperty("eid", String.class, null, true, false); addProperty("e", String.class, null, true, false); } @Override public void setProcessInstanceFilter(String processInstanceFilter) { this.processInstanceFilter = processInstanceFilter; } static JobQuery createQuery(final ProcessEngine engine, final String processInstanceFilter) { JobQuery jobQuery = engine .getManagementService() .createJobQuery() .withException() .withRetriesLeft(); if (processInstanceFilter != null) { return jobQuery.processInstanceId(processInstanceFilter); } return jobQuery; // переопределённое поведение - поиск retries==0 } static Job getFailedJob(final ProcessEngine engine, String jobId) { return createQuery(engine, null) .jobId(jobId) .singleResult(); } @Override public int size() { return Fn.withEngine(new Count(), processInstanceFilter).intValue(); } @Override public List<Item> loadItems(final int startIndex, final int count) { return Fn.withEngine(new Items(), processInstanceFilter, startIndex, count); } public JobInfo getSingle(String jobId) { if (jobId == null) { return null; } return Fn.withEngine(new Single(), jobId); } public void restartJob(String jobId) { if (jobId != null) { Fn.withEngine(new Restart(), jobId); } } public void executeJob(String jobId) { if (jobId != null) { Fn.withEngine(new Execute(), jobId); } } // --- functions --- final private static class Restart implements F1<Boolean, String> { @Override public Boolean apply(final ProcessEngine engine, final String jobId) { final Job job = getFailedJob(engine, jobId); if (job == null) { return false; } engine.getManagementService().setJobRetries(jobId, 1); return true; } } final private static class Count implements F1<Long, String> { @Override public Long apply(final ProcessEngine engine, String processInstanceFilter) { return createQuery(engine, processInstanceFilter).count(); } } final private static class Single implements F1<JobInfo, String> { @Override public JobInfo apply(final ProcessEngine processEngine, final String jobId) { final Job job = getFailedJob(processEngine, jobId); if (job == null) { return null; } final ProcessInstance processInstance = processEngine.getRuntimeService().createProcessInstanceQuery().processInstanceId(job.getProcessInstanceId()).singleResult(); final String exception = processEngine.getManagementService().getJobExceptionStacktrace(jobId); return new JobInfo(job.getId(), job.getExecutionId(), processInstance.getProcessDefinitionId(), exception); } } final private static class Execute implements F1<Boolean, String> { @Override public Boolean apply(final ProcessEngine engine, final String jobId) { final Job job = getFailedJob(engine, jobId); if (job == null) { return false; } final ManagementService managementService = engine.getManagementService(); managementService.setJobRetries(jobId, 1); managementService.executeJob(jobId); return true; } } final private static class Items implements F3<List<Item>, String, Integer, Integer> { @Override public List<Item> apply(final ProcessEngine engine, final String processInstanceFilter, final Integer startIndex, final Integer count) { final AdminService adminService = Flash.flash().getAdminService(); final List<Job> jobs = createQuery(engine, processInstanceFilter).listPage(startIndex, count); final List<Item> items = new ArrayList<Item>(jobs.size()); for (final Job job : jobs) { final PropertysetItem item = new PropertysetItem(); item.addItemProperty("jid", stringProperty(job.getId())); if (!job.getExecutionId().equals(job.getProcessInstanceId())) { item.addItemProperty("eid", stringProperty(job.getExecutionId())); } item.addItemProperty("pid", stringProperty(job.getProcessInstanceId())); item.addItemProperty("e", stringProperty(job.getExceptionMessage())); final Bid bid = adminService.getBidByProcessInstanceId(job.getProcessInstanceId()); if (bid != null) { item.addItemProperty("bid", new ObjectProperty<Long>(bid.getId())); item.addItemProperty("startDate", stringProperty(ExecutorFactory.formatter.format(bid.getDateCreated()))); final Procedure procedure = bid.getProcedure(); if (procedure != null) { if (bid.getTag().isEmpty()) { item.addItemProperty("name", stringProperty(procedure.getName())); } else { item.addItemProperty("name", stringProperty(bid.getTag() + " - " + procedure.getName())); } item.addItemProperty("ver", stringProperty(procedure.getVersion())); } } items.add(item); } return items; } } } final class ErrorBlock extends HorizontalLayout { final TextArea textArea = new TextArea("Стек ошибки:"); final Button restart = new Button("Возобновить"); final Button execute = new Button("Выполнить"); final VerticalLayout diagramLayout = new VerticalLayout(); JobInfo info; ErrorBlock() { setSizeFull(); setSpacing(true); restart.addListener(new RestartListener()); restart.setDescription("Возобновить исполнение процесса в фоновом режиме"); execute.addListener(new ExecuteListener()); execute.setDescription("Выполнить процесс немедленно"); textArea.setSizeFull(); textArea.setStyleName("small"); diagramLayout.setCaption("Схема приостановленного процеса:"); diagramLayout.setSizeFull(); final VerticalLayout wrapper = new VerticalLayout(); wrapper.setSizeUndefined(); wrapper.setSpacing(true); wrapper.setWidth(110, UNITS_PIXELS); wrapper.addComponent(restart); wrapper.addComponent(execute); final VerticalLayout buttonsLayout = new VerticalLayout(); buttonsLayout.addComponent(wrapper); buttonsLayout.setSizeFull(); buttonsLayout.setWidth(110, UNITS_PIXELS); addComponent(buttonsLayout); addComponent(diagramLayout); addComponent(textArea); setExpandRatio(diagramLayout, 0.42f); setExpandRatio(textArea, 0.42f); setJobInfo(null); } public void setJobInfo(final JobInfo jobInfo) { final boolean enabled = jobInfo != null; info = enabled ? jobInfo.copy() : null; textArea.setReadOnly(false); textArea.setValue(enabled ? jobInfo.exception : ""); textArea.setReadOnly(true); textArea.setEnabled(enabled); restart.setEnabled(enabled); execute.setEnabled(enabled); diagramLayout.setEnabled(enabled); diagramLayout.removeAllComponents(); if (enabled) { diagramLayout.addComponent(new DiagramPanel(jobInfo.definitionId, jobInfo.executionId)); } } void refresh() { container.refresh(); table.setValue(null); } final private class RestartListener implements Button.ClickListener { @Override public void buttonClick(Button.ClickEvent event) { final String id = info.jobId; setJobInfo(null); persistence.restartJob(id); refresh(); } } final private class ExecuteListener implements Button.ClickListener { @Override public void buttonClick(Button.ClickEvent event) { final String id = info.jobId; final String branch = "Маршрут " + info.definitionId + ", ветвь " + info.executionId; setJobInfo(null); try { persistence.executeJob(id); } catch (RuntimeException e) { getWindow().addWindow(new StackTraceDialog(branch + " - ошибка исполнения", e)); } refresh(); } } } final static class StackTraceDialog extends Window { public StackTraceDialog(final String caption, final Throwable cause) { super(caption); setWidth(66, UNITS_PERCENTAGE); setHeight(66, UNITS_PERCENTAGE); setBorder(BORDER_MINIMAL); setModal(true); setClosable(true); getContent().setSizeFull(); final TextArea stackTrace = new TextArea("Стек ошибки:"); stackTrace.setValue(Exceptions.trimToCauseString(cause)); stackTrace.setReadOnly(true); stackTrace.setSizeFull(); stackTrace.setStyleName("small"); addComponent(stackTrace); } } }