package org.aperteworkflow.view.impl.history; import com.vaadin.Application; import com.vaadin.data.Container; import com.vaadin.data.Container.Filter; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.util.IndexedContainer; import com.vaadin.data.util.filter.Or; import com.vaadin.data.util.filter.SimpleStringFilter; import com.vaadin.event.FieldEvents.TextChangeListener; import com.vaadin.event.ItemClickEvent; import com.vaadin.event.ItemClickEvent.ItemClickListener; import com.vaadin.event.LayoutEvents.LayoutClickEvent; import com.vaadin.event.LayoutEvents.LayoutClickListener; import com.vaadin.event.ShortcutAction.KeyCode; import com.vaadin.terminal.Sizeable; import com.vaadin.ui.*; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.Table.ColumnGenerator; import org.aperteworkflow.ui.view.ViewCallback; import org.aperteworkflow.ui.view.ViewRenderer; import org.aperteworkflow.util.vaadin.GenericVaadinPortlet2BpmApplication; import org.aperteworkflow.util.vaadin.text.TextValueChangeListener; import org.aperteworkflow.util.view.AbstractListPane; import pl.net.bluesoft.rnd.processtool.ProcessToolContext; import pl.net.bluesoft.rnd.processtool.bpm.ProcessToolBpmSession; import pl.net.bluesoft.rnd.processtool.model.*; import pl.net.bluesoft.rnd.processtool.model.config.ProcessDefinitionConfig; import pl.net.bluesoft.rnd.processtool.model.config.ProcessStateConfiguration; import org.aperteworkflow.util.vaadin.VaadinUtility; import org.aperteworkflow.util.vaadin.ui.OrderedLayoutFactory; import org.aperteworkflow.view.impl.history.DateRangeField.DateRange; import org.aperteworkflow.view.impl.history.DateRangeField.DateRangeChangedEvent; import org.aperteworkflow.view.impl.history.DateRangeField.DateRangeListener; import org.aperteworkflow.util.vaadin.ui.table.LocalizedPagedTable; import org.aperteworkflow.util.vaadin.ui.table.LocalizedPagedTable.PageChangeListener; import org.aperteworkflow.util.vaadin.ui.table.LocalizedPagedTable.PagedTableChangeEvent; import pl.net.bluesoft.rnd.util.i18n.I18NSource; import pl.net.bluesoft.util.lang.Formats; import pl.net.bluesoft.util.lang.Strings; import java.text.DateFormat; import java.text.MessageFormat; import java.util.*; /** * @author amichalak@bluesoft.net.pl * @author tlipski@bluesoft.net.pl */ public class HistoryListPane extends AbstractListPane implements DateRangeListener, ViewRenderer { private static final String HISTORY_SUPERUSER_ROLE_NAME = "AWF_HISTORY_SUPERUSER"; @Override public String getViewId() { return HistoryListPane.class.getName(); } @Override public Component render(Map<String, ?> viewData) { return this; } @Override public void setViewCallback(ViewCallback viewCallback) { this.viewCallback = viewCallback; } private static final String HISTORY_SUPERUSER_CONFIG_KEY = "history.superuser.roles"; private static final String[] allPropertyNames = new String[] { "log.entryDate", "def.desc", "pi.externalKey", "pi.internalId", "state.desc", "log.eventName", "log.actionName", "state.name", "log.author", "log.substitutedBy" }; private static final String[] processDetailsPropertyNames = new String[] { "def.desc", "def.cmnt", "pi.externalKey", "pi.internalId", "pi.status", "pi.createDate", "pi.creator", "pi.currentTasks" }; private static final String[] taskDetailsPropertyNames = new String[] { "log.entryDate", "state.desc", "log.eventName", "log.actionName", "log.author", "log.substitutedBy" }; private ViewCallback viewCallback; private TextField searchField; private String filterExpression = ""; private ProcessToolBpmSession bpmSession; private DirectIndexedContainer logContainer; private IndexedContainer processDetailsContainer; private IndexedContainer taskDetailsContainer; private VerticalLayout detailsView; private LocalizedPagedTable table; private DateRangeField dateRangeField; private HistorySelection historySelection; private boolean isHistorySuperuser = false; private boolean isBaseUserOnly = true; private int taskCount = 0; private class DirectIndexedContainer extends IndexedContainer { public Item getItemDirectly(Object itemId) { return getUnfilteredItem(itemId); } public Collection<?> getItemIdsDirectly() { return Collections.unmodifiableCollection(getAllItemIds()); } } public void setUp(Application app) { super.setUp(app, I18NSource.ThreadUtil.getThreadI18nSource(), "activity.task.history"); application = app; messageSource = I18NSource.ThreadUtil.getThreadI18nSource(); init(); } @Override public Object getViewData() { return getData(); } @Override public String getTitle() { return I18NSource.ThreadUtil.getLocalizedMessage("activity.task.history"); } @Override public void handleDisplayAction() { if (application instanceof GenericVaadinPortlet2BpmApplication) ((GenericVaadinPortlet2BpmApplication)application).setShowExitWarning(false); VaadinUtility.unregisterClosingWarning(application.getMainWindow()); setData(Collections.singletonMap("historySelection", new HistorySelection())); } @Override public void setBpmSession(ProcessToolBpmSession bpmSession) { this.bpmSession = bpmSession; } public HistorySelection getHistorySelection() { return historySelection != null ? historySelection : (historySelection = new HistorySelection()); } public void setHistorySelection(HistorySelection historySelection) { this.historySelection = historySelection; } @Override public HistoryListPane init() { super.init(); processDetailsContainer = createProcessDetailsContainer(); taskDetailsContainer = createTaskDetailsContainer(); setupHistorySuperuserFlag(); dateRangeField = new DateRangeField(messageSource); dateRangeField.setOptionGroupLayoutFactory(new OrderedLayoutFactory<VerticalLayout>() { @Override public VerticalLayout create() { return new VerticalLayout(); } }); dateRangeField.setDateStepperBaseValue(0); dateRangeField.setDateStepperMaxValue(31); VerticalLayout marginPanel = new VerticalLayout(); marginPanel.addComponent(new Label(getMessagePrefixed("help.short"), Label.CONTENT_XHTML)); marginPanel.setMargin(true); marginPanel.setWidth("100%"); marginPanel.addComponent(createSearchBox()); if (isHistorySuperuser) { final CheckBox cb = VaadinUtility.checkBox(null); cb.setWidth("12px"); cb.setValue(isBaseUserOnly = getHistorySelection().isOnlyBaseUser()); cb.addListener(new ValueChangeListener() { @Override public void valueChange(ValueChangeEvent event) { isBaseUserOnly = cb.booleanValue(); filterResults(); } }); Label l = new Label(getMessagePrefixed("base.user.only"), Label.CONTENT_XHTML); HorizontalLayout hl = VaadinUtility.horizontalLayout(Alignment.MIDDLE_LEFT, cb, l); hl.setMargin(false, true, false, false); hl.addListener(new LayoutClickListener() { @Override public void layoutClick(LayoutClickEvent event) { cb.setValue(!cb.booleanValue()); } }); marginPanel.addComponent(hl); } table = VaadinUtility.pagedTable(logContainer = createLogContainer(), allPropertyNames, createColumnHeaders(allPropertyNames), null, new ItemClickListener() { @Override public void itemClick(ItemClickEvent event) { showDetailedView((ProcessInstanceLog) event.getItemId()); } }); table.setPageLength(getHistorySelection().getPageLength()); table.addListener(new PageChangeListener() { @Override public void pageChanged(PagedTableChangeEvent event) { getHistorySelection().setPageLength(table.getPageLength()); } }); table.setMultiSelect(false); table.addListener(new ValueChangeListener() { @Override public void valueChange(ValueChangeEvent event) { showDetailedView((ProcessInstanceLog) event.getProperty().getValue()); } }); table.setSortAscending(false); VerticalLayout tableLayout = VaadinUtility.verticalLayout(dateRangeField, VaadinUtility.hr(), createTableControls(), table, createTableControls()); tableLayout.setMargin(true, false, false, false); marginPanel.addComponent(tableLayout); addComponent(marginPanel); String historyFilterExpression = getHistorySelection().getFilterExpression(); if (getHistorySelection().getDateRange() != null) { dateRangeField.setValue(getHistorySelection().getDateRange()); } refreshData(); ProcessInstanceLog selectedItem = getHistorySelection().getSelectedItemId(); if (selectedItem != null) { ProcessInstanceLog log = null; for (Object itemId : logContainer.getItemIdsDirectly()) { log = (ProcessInstanceLog) itemId; if (log.getId().equals(selectedItem.getId())) { break; } } getHistorySelection().setSelectedItemId(log); table.setValue(log); } searchField.setValue(Strings.hasText(historyFilterExpression) ? historyFilterExpression : ""); filterResults(); dateRangeField.addListener(this); return this; } private void setupHistorySuperuserFlag() { isHistorySuperuser = false; if (bpmSession != null) { UserData user = (UserData) application.getUser(); if (user.containsRole(HISTORY_SUPERUSER_ROLE_NAME)) { isHistorySuperuser = true; } else { ProcessToolContext ctx = ProcessToolContext.Util.getThreadProcessToolContext(); String config = ctx.getSetting(HISTORY_SUPERUSER_CONFIG_KEY); if (Strings.hasText(config)) { for (String roleName : config.split(",")) { if (user.containsRole(roleName)) { isHistorySuperuser = true; break; } } } } } } private HorizontalLayout createTableControls() { return table.createControls(messageSource); } private DirectIndexedContainer createLogContainer() { DirectIndexedContainer container = new DirectIndexedContainer(); for (String propertyName : allPropertyNames) { container.addContainerProperty(propertyName, String.class, ""); } return container; } private IndexedContainer createProcessDetailsContainer() { return createSimpleContainer("title", processDetailsPropertyNames); } private IndexedContainer createTaskDetailsContainer() { return createSimpleContainer("title", taskDetailsPropertyNames); } private IndexedContainer createSimpleContainer(String propertyId, String[] propertyNames) { IndexedContainer container = new IndexedContainer(); container.addContainerProperty(propertyId, String.class, ""); for (String propertyName : propertyNames) { Item item = container.addItem(propertyName); item.getItemProperty(propertyId).setValue(getMessagePrefixed(propertyName)); } return container; } private String[] createColumnHeaders(String[] propertyNames) { String[] headers = new String[propertyNames.length]; for (int i = 0; i < propertyNames.length; ++i) { headers[i] = getMessagePrefixed(propertyNames[i]); } return headers; } private HorizontalLayout createSearchBox() { final TextField field = new TextField(); field.addStyleName("search"); field.setInputPrompt(getMessage("search.prompt")); field.setWidth("100%"); field.setTextChangeEventMode(AbstractTextField.TextChangeEventMode.LAZY); field.setTextChangeTimeout(2000); TextValueChangeListener listener = new TextValueChangeListener() { @Override public void handleTextChange(String changedText) { filterExpression = Strings.hasText(changedText) ? changedText : ""; filterResults(); } }; field.addListener((ValueChangeListener) listener); field.addListener((TextChangeListener) listener); searchField = field; Button searchButton = VaadinUtility.button(getMessage("search.advanced.search"), null, "default small", new ClickListener() { @Override public void buttonClick(ClickEvent event) { String changedText = (String) field.getValue(); filterExpression = Strings.hasText(changedText) ? changedText : ""; filterResults(); } }); searchButton.setClickShortcut(KeyCode.ENTER); return VaadinUtility.horizontalLayout(field, searchButton); } private void filterResults() { logContainer.removeAllContainerFilters(); if (Strings.hasText(filterExpression)) { List<Filter> filters = new ArrayList<Filter>() {{ for (Object propertyId : logContainer.getContainerPropertyIds()) { add(new SimpleStringFilter(propertyId, filterExpression, true, false)); } }}; logContainer.addContainerFilter(new Or(filters.toArray(new Filter[filters.size()]))); } if (isHistorySuperuser && isBaseUserOnly) { UserData user = (UserData) application.getUser(); logContainer.addContainerFilter(new Or( new SimpleStringFilter("log.author", user.getRealName(), false, true), new SimpleStringFilter("log.substitutedBy", user.getRealName(), false, true))); } getHistorySelection().setOnlyBaseUser(isBaseUserOnly); getHistorySelection().setFilterExpression(filterExpression); updatePageControls(); } @Override public void onEvent(DateRangeChangedEvent dateRangeChangedEvent) { getHistorySelection().setDateRange((DateRange) dateRangeField.getValue()); refreshData(); } @Override public void refreshData() { logContainer.removeAllItems(); searchField.setValue(""); table.setValue(null); if (bpmSession != null) { ProcessToolContext ctx = ProcessToolContext.Util.getThreadProcessToolContext(); UserData user = isHistorySuperuser ? null : bpmSession.getUser(ctx); Collection<ProcessInstanceLog> logs = ctx.getProcessInstanceDAO().getUserHistory(user, dateRangeField.getStartDate(), dateRangeField.getEndDate()); DateFormat dateFormat = VaadinUtility.fullDateFormat(); for (ProcessInstanceLog log : logs) { Item item = logContainer.addItem(log); item.getItemProperty("log.entryDate").setValue(dateFormat.format(log.getEntryDate().getTime())); item.getItemProperty("log.eventName").setValue(getMessage(log.getEventI18NKey())); item.getItemProperty("log.actionName").setValue(getMessage(log.getAdditionalInfo())); ProcessInstance pi = log.getProcessInstance(); if (pi != null) { item.getItemProperty("def.desc").setValue(getMessage(pi.getDefinition().getDescription())); item.getItemProperty("pi.externalKey").setValue(getExternalKey(pi)); item.getItemProperty("pi.internalId").setValue(pi.getInternalId()); } ProcessStateConfiguration state = log.getState(); if (state != null) { item.getItemProperty("state.desc").setValue(getMessage(state.getDescription())); item.getItemProperty("state.name").setValue(getMessage(state.getName())); } String authorName = log.getUser().getRealName(); item.getItemProperty("log.author").setValue(authorName); UserData userSubstitute = log.getUserSubstitute(); String userSubstituteName = userSubstitute != null ? userSubstitute.getRealName() : ""; item.getItemProperty("log.substitutedBy").setValue(userSubstituteName); } logContainer.sort(new Object[] {"log.entryDate"}, new boolean[] {false}); table.sort(); } updatePageControls(); } private void updatePageControls() { table.setCurrentPage(0); table.firePagedChangedEvent(); } private void showDetailedView(final ProcessInstanceLog log) { hideDetailedView(); getHistorySelection().setSelectedItemId(log); if (log == null) { return; } detailsView = new VerticalLayout(); detailsView.setWidth("100%"); detailsView.setMargin(false, true, false, true); detailsView.setSpacing(true); detailsView.addComponent(VaadinUtility.hr()); addComponent(detailsView); Table taskTable = attachTitledTable(detailsView, "log.details", taskDetailsContainer, new Object[] {"title", "component"}, new HashMap<String, ColumnGenerator>() {{ put("component", new ColumnGenerator() { @Override public Object generateCell(Table source, Object itemId, Object columnId) { Item item = logContainer.getItemDirectly(log); Property property = item.getItemProperty(itemId); if (property != null && property.getValue() != null) { String value = (String) property.getValue(); if (Strings.hasText(value) && ("log.substitutedBy".equals(itemId) || "log.author".equals(itemId))) { return VaadinUtility.labelWithIcon(getImage("/img/user_standard.png"), value, null, getMessagePrefixed(itemId.toString())); } return VaadinUtility.boldLabel(value); } return null; } }); }}); // being stupid is its own reward taskTable.setHeight(22 * (taskDetailsPropertyNames.length - 2) + 50, Sizeable.UNITS_PIXELS); Collection<Button> taskButtons = createTaskButtons(log); HorizontalLayout buttons = VaadinUtility.horizontalLayout(Alignment.MIDDLE_LEFT, taskButtons.toArray(new Component[taskButtons.size()])); detailsView.addComponent(buttons); Table processTable = attachTitledTable(detailsView, "pi.details", processDetailsContainer, new Object[] {"title", "component"}, new HashMap<String, ColumnGenerator>() {{ put("component", new ColumnGenerator() { private DateFormat dateFormat = VaadinUtility.simpleDateFormat(); @Override public Object generateCell(Table source, Object itemId, Object columnId) { ProcessInstance pi = log.getProcessInstance(); ProcessDefinitionConfig def = pi.getDefinition(); if ("def.desc".equals(itemId)) { return VaadinUtility.boldLabel(getMessage(Formats.nvl(def.getDescription()))); } else if ("def.cmnt".equals(itemId)) { return VaadinUtility.boldLabel(getMessage(Formats.nvl(def.getComment()))); } else if ("pi.externalKey".equals(itemId)) { return VaadinUtility.boldLabel(getMessage(Formats.nvl(getExternalKey(pi)))); } else if ("pi.internalId".equals(itemId)) { return VaadinUtility.boldLabel(getMessage(pi.getInternalId())); } else if ("pi.status".equals(itemId)) { return VaadinUtility.boldLabel(getProcessStatus(pi)); } else if ("pi.createDate".equals(itemId)) { return VaadinUtility.boldLabel(dateFormat.format(pi.getCreateDate())); } else if ("pi.creator".equals(itemId)) { return VaadinUtility.boldLabel(pi.getCreator() != null ? pi.getCreator().getRealName() : ""); } else if ("pi.currentTasks".equals(itemId)) { return getProcessCurrentTasksView(pi); } return null; } }); }}); // i say no to drugs, but they don’t listen processTable.setHeight(22 * (processDetailsPropertyNames.length - 1) + Math.max(taskCount, 1) * 35, Sizeable.UNITS_PIXELS); detailsView.setExpandRatio(processTable, 1.0f); } private Collection<Button> createTaskButtons(final ProcessInstanceLog log) { Item item = logContainer.getItemDirectly(log); Property property = item.getItemProperty("state.desc"); String stateDescription = (String) property.getValue(); boolean hasState = Strings.hasText(stateDescription); boolean isFinished = ProcessStatus.FINISHED.equals(log.getProcessInstance().getStatus()) || ProcessStatus.CANCELLED.equals(log.getProcessInstance().getStatus()); final Button showStateButton = createOpenInstanceButton(log, getMessagePrefixed("open.current.step"), getMessagePrefixed("current.not.found", stateDescription), false, hasState, getMessagePrefixed("no.state")); final Button finalStateButton = createOpenInstanceButton(log, getMessagePrefixed("open.final.step"), getMessagePrefixed("final.not.found"), true, isFinished, getMessagePrefixed("not.finished")); return new ArrayList<Button>() {{ add(showStateButton); add(finalStateButton); }}; } private Button createOpenInstanceButton(final ProcessInstanceLog log, String caption, final String notFoundMessage, final boolean finalTask, boolean enabled, String notEnabledDescription) { Button b = VaadinUtility.button(caption, null, "default", new ClickListener() { @Override public void buttonClick(ClickEvent event) { openHistoryInstance(log, notFoundMessage, finalTask); } }); b.setWidth(b.getWidth() + 20, Sizeable.UNITS_PIXELS); b.setIcon(VaadinUtility.imageResource(application, finalTask ? "green-flag.png" : "task.png")); b.setEnabled(enabled); b.setDescription(enabled ? caption : notEnabledDescription); return b; } private void openHistoryInstance(ProcessInstanceLog log, String notFoundMessage, boolean finalTask) { getHistorySelection().update(table.getPageLength(), filterExpression, isBaseUserOnly, (DateRange) dateRangeField.getValue(), log); ProcessToolContext ctx = ProcessToolContext.Util.getThreadProcessToolContext(); ProcessInstance ownPi = null; if(log.getOwnProcessInstance() != null) ownPi = ctx.getProcessInstanceDAO().getProcessInstance(log.getOwnProcessInstance().getId()); else ownPi = ctx.getProcessInstanceDAO().getProcessInstanceByInternalId(log.getExecutionId()); log.setOwnProcessInstance(ownPi); BpmTask task = finalTask ? bpmSession.getPastEndTask(log, ctx) : bpmSession.getPastOrActualTask(log, ctx); if (task != null) { viewCallback.displayProcessData(task, true); } else { VaadinUtility.validationNotification(application, messageSource, notFoundMessage); } } private Table attachTitledTable(VerticalLayout layout, String title, Container dataSource, Object[] visiblePropertyIds, Map<String, ColumnGenerator> customColumns) { Label titleLabel = new Label(getMessagePrefixed(title), Label.CONTENT_XHTML) {{ addStyleName("h1 color processtool-title"); }}; HorizontalLayout hl = new HorizontalLayout(); hl.setWidth("100%"); hl.setSpacing(true); hl.addComponent(titleLabel); hl.setExpandRatio(titleLabel, 1.0f); layout.addComponent(hl); Table table = VaadinUtility.simpleTable(dataSource, visiblePropertyIds, customColumns); layout.addComponent(table); return table; } private String getProcessStatus(ProcessInstance pi) { ProcessStatus status = pi.getStatus() != null ? pi.getStatus() : ProcessStatus.UNKNOWN; return getMessage("process.instance.status." + status.name().toLowerCase()); } private Component getProcessCurrentTasksView(ProcessInstance pi) { ProcessToolContext ctx = ProcessToolContext.Util.getThreadProcessToolContext(); List<BpmTask> tasks = bpmSession.findProcessTasks(pi, ctx); taskCount = tasks.size(); if (tasks.isEmpty()) { return VaadinUtility.boldLabel(getMessagePrefixed("pi.currentTasks.empty")); } VerticalLayout vl = new VerticalLayout(); vl.setWidth("100%"); vl.setSpacing(false); vl.setMargin(false); for (BpmTask task : tasks) { ProcessStateConfiguration state = ctx.getProcessDefinitionDAO().getProcessStateConfiguration(task); vl.addComponent(createTaskComponent(task, state)); } return vl; } private Component createTaskComponent(BpmTask task, ProcessStateConfiguration state) { HorizontalLayout hl = new HorizontalLayout(); hl.setSpacing(true); hl.setMargin(false); hl.addComponent(VaadinUtility.boldLabel(getMessage(state.getDescription()))); String assignedName = "<b>" + (task.getOwner() != null ? task.getOwner().getRealName() : getMessage("activity.assigned.empty")) + "</b>"; Component assignedComponent = VaadinUtility.labelWithIcon(getImage("/img/user_assigned.png"), assignedName, null, getMessage("activity.assigned")); hl.addComponent(assignedComponent); Iterator<Component> it = hl.getComponentIterator(); while (it.hasNext()) { Component next = it.next(); hl.setComponentAlignment(next, Alignment.MIDDLE_LEFT); } return hl; } private void hideDetailedView() { if (detailsView != null) { detailsView.removeAllComponents(); removeComponent(detailsView); } detailsView = null; } public String getMessagePrefixed(String key) { return messageSource.getMessage("activity.task.history." + key); } public String getMessagePrefixed(String key, String... args) { return MessageFormat.format(messageSource.getMessage("activity.task.history." + key), args); } public static class HistorySelection { private String filterExpression; private int pageLength; private DateRange dateRange; private ProcessInstanceLog selectedItemId; private boolean onlyBaseUser; public HistorySelection() { this.pageLength = 25; this.filterExpression = ""; this.onlyBaseUser = true; } public DateRange getDateRange() { return dateRange; } public void setDateRange(DateRange dateRange) { this.dateRange = dateRange; } public ProcessInstanceLog getSelectedItemId() { return selectedItemId; } public void setSelectedItemId(ProcessInstanceLog selectedItemId) { this.selectedItemId = selectedItemId; } public String getFilterExpression() { return filterExpression; } public void setFilterExpression(String filterExpression) { this.filterExpression = filterExpression; } public int getPageLength() { return pageLength; } public void setPageLength(int pageLength) { this.pageLength = pageLength; } public boolean isOnlyBaseUser() { return onlyBaseUser; } public void setOnlyBaseUser(boolean onlyBaseUser) { this.onlyBaseUser = onlyBaseUser; } public void update(int pageLength, String filterExpression, boolean onlyBaseUser, DateRange dateRange, ProcessInstanceLog selectedItemId) { setDateRange(dateRange); setFilterExpression(filterExpression); setOnlyBaseUser(onlyBaseUser); setPageLength(pageLength); setSelectedItemId(selectedItemId); } } private static String getExternalKey(ProcessInstance pi) { do { if (pi.getExternalKey() != null) { return pi.getExternalKey(); } pi = pi.getParent(); } while (pi != null); return null; } }