package pl.net.bluesoft.rnd.processtool.ui.process;
import static com.vaadin.ui.Label.CONTENT_XHTML;
import static org.aperteworkflow.util.vaadin.VaadinExceptionHandler.Util.withErrorHandling;
import static pl.net.bluesoft.util.lang.Formats.nvl;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.aperteworkflow.ui.help.HelpProvider;
import org.aperteworkflow.ui.help.HelpProviderFactory;
import org.aperteworkflow.util.vaadin.VaadinUtility;
import org.aperteworkflow.util.vaadin.ui.AligningHorizontalLayout;
import pl.net.bluesoft.rnd.processtool.ProcessToolContext;
import pl.net.bluesoft.rnd.processtool.bpm.ProcessToolBpmSession;
import pl.net.bluesoft.rnd.processtool.model.BpmTask;
import pl.net.bluesoft.rnd.processtool.model.ProcessInstance;
import pl.net.bluesoft.rnd.processtool.model.UserData;
import pl.net.bluesoft.rnd.processtool.model.config.ProcessStateAction;
import pl.net.bluesoft.rnd.processtool.model.config.ProcessStateConfiguration;
import pl.net.bluesoft.rnd.processtool.model.config.ProcessStateWidget;
import pl.net.bluesoft.rnd.processtool.plugins.ProcessToolRegistry;
import pl.net.bluesoft.rnd.processtool.ui.WidgetContextSupport;
import pl.net.bluesoft.rnd.processtool.ui.common.FailedProcessToolWidget;
import pl.net.bluesoft.rnd.processtool.ui.widgets.ProcessToolActionButton;
import pl.net.bluesoft.rnd.processtool.ui.widgets.ProcessToolActionCallback;
import pl.net.bluesoft.rnd.processtool.ui.widgets.ProcessToolChildrenFilteringWidget;
import pl.net.bluesoft.rnd.processtool.ui.widgets.ProcessToolDataWidget;
import pl.net.bluesoft.rnd.processtool.ui.widgets.ProcessToolVaadinRenderable;
import pl.net.bluesoft.rnd.processtool.ui.widgets.ProcessToolWidget;
import pl.net.bluesoft.rnd.processtool.ui.widgets.event.WidgetEventBus;
import pl.net.bluesoft.rnd.util.i18n.I18NSource;
import pl.net.bluesoft.util.lang.Lang;
import pl.net.bluesoft.util.lang.Strings;
import pl.net.bluesoft.util.lang.TaskWatch;
import com.vaadin.Application;
import com.vaadin.terminal.Sizeable;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Component;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
/**
* Główny panel widoku zawartości kroku procesu
*
* @author tlipski@bluesoft.net.pl, mpawlak@bluesoft.net.pl
*/
public class ProcessDataPane extends VerticalLayout implements WidgetContextSupport {
private Logger logger = Logger.getLogger(ProcessDataPane.class.getName());
private ProcessToolBpmSession bpmSession;
private I18NSource i18NSource;
private Set<ProcessToolDataWidget> dataWidgets = new HashSet<ProcessToolDataWidget>();
private boolean isOwner;
private Application application;
private ProcessDataDisplayContext displayProcessContext;
private BpmTask task;
private HelpProvider helpFactory;
private ProcessToolActionCallback actionCallback;
private GuiAction guiAction = null;
private static enum GuiAction {
ACTION_PERFORMED, SAVE_PERFORMED, ACTION_FAILED;
}
public ProcessDataPane(Application application, ProcessToolBpmSession bpmSession, I18NSource i18NSource, BpmTask bpmTask,
ProcessDataDisplayContext hideProcessHandler) {
this.application = application;
this.bpmSession = bpmSession;
this.i18NSource = i18NSource;
displayProcessContext = hideProcessHandler;
task = bpmTask;
refreshTask();
prepare();
setMargin(new MarginInfo(false, false, true, true));
initLayout(false);
}
private void prepare() {
HelpProviderFactory helpProviderFactory =
ProcessToolContext.Util.getThreadProcessToolContext().getRegistry().lookupService(HelpProviderFactory.class.getName());
if (helpProviderFactory != null)
helpFactory = helpProviderFactory.getInstance(application, task.getProcessDefinition(), true, "step_help");
actionCallback = new MyProcessToolActionCallback();
}
/** Odśwież odśwież widok po zmianie kroku lub procesu */
private void initLayout(boolean autoHide) {
final ProcessToolContext ctx = getCurrentContext();
removeAllComponents();
setWidth(100, Sizeable.UNITS_PERCENTAGE);
dataWidgets.clear();
boolean processRunning = bpmSession.isProcessRunning(task.getInternalProcessId(), ctx);
isOwner = processRunning && !task.isFinished();
if (!isOwner)
{
//showProcessStateInformation(processRunning);
if (autoHide)
{
/* Jeżeli wstrzymujemy proces glowny, albo zamykamy podproces, sprobuj wrocic
* do odpowiedniego procesu
*/
boolean isProcessChanged = changeCurrentViewToActiveProcess();
/* Nie zmienilismy procesu, tak wiec chowamy ten widok */
if(!isProcessChanged)
{
guiAction = null;
displayProcessContext.hide();
return;
}
else
{
/* Zacznij od nowa z nowym przypisanym taskiem */
initLayout(false);
return;
}
}
}
guiAction = null;
final ProcessStateConfiguration stateConfiguration = ctx.getProcessDefinitionDAO()
.getProcessStateConfiguration(task);
Label stateDescription = new Label(getMessage(stateConfiguration.getDescription()));
stateDescription.addStyleName("h1 color processtool-title");
stateDescription.setWidth(100, Sizeable.UNITS_PERCENTAGE);
addComponent(stateDescription);
if (Strings.hasText(stateConfiguration.getCommentary())) {
addComponent(new Label(getMessage(stateConfiguration.getCommentary()), Label.CONTENT_XHTML));
}
if (helpFactory != null)
addComponent(helpFactory.helpIcon(task.getTaskName(), "step.help"));
displayProcessContext.setCaption(task.getExternalProcessId() != null ? task.getExternalProcessId() : task.getInternalProcessId());
final VerticalLayout vl = new VerticalLayout();
vl.setSpacing(true);
vl.setWidth(100, Sizeable.UNITS_PERCENTAGE);
List<ProcessStateWidget> widgets = new ArrayList<ProcessStateWidget>(stateConfiguration.getWidgets());
Collections.sort(widgets, new WidgetPriorityComparator());
TaskWatch watch = new TaskWatch(ProcessDataPane.class.getSimpleName() + " - generowanie interfejsu dla kroku " + stateConfiguration.getName());
final WidgetEventBus widgetEventBus = new WidgetEventBus();
for (final ProcessStateWidget w : widgets) {
try {
watch.watchTask(w.getClassName() + ": " + w.getName(), new Callable() {
@Override
public Object call() throws Exception {
try {
ProcessToolWidget realWidget = getWidget(w, stateConfiguration, ctx, null, widgetEventBus);
if (realWidget instanceof ProcessToolVaadinRenderable && (!nvl(w.getOptional(), false) || realWidget.hasVisibleData())) {
processWidgetChildren(w, realWidget, stateConfiguration, ctx, null, widgetEventBus);
ProcessToolVaadinRenderable vaadinW = (ProcessToolVaadinRenderable) realWidget;
vl.addComponent(vaadinW.render());
}
}
catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
vl.addComponent(new Label(getMessage("process.data.widget.exception-occurred")));
vl.addComponent(new Label(e.getMessage()));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
e.printStackTrace(new PrintWriter(baos));
vl.addComponent(new Label("<pre>" + baos.toString() + "</pre>", CONTENT_XHTML));
}
// TODO Auto-generated method stub
return null;
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
watch.stopAll();
logger.log(Level.INFO, watch.printSummary());
addComponent(vl);
setExpandRatio(vl,1f);
if (isOwner) {
HorizontalLayout buttonLayout = getButtonsPanel(stateConfiguration);
addComponentAsFirst(buttonLayout);
buttonLayout = getButtonsPanel(stateConfiguration);
addComponent(buttonLayout);
}
}
/** Metoda w przypadku wstrzymywania procesu przelacza widok na podproces
* lub w przypadku zamkniecia podprocesu, na proces glowny
*
* @return true jeżeli nastąpiło przełączenie
*/
private boolean changeCurrentViewToActiveProcess()
{
/* Aktualny proces */
ProcessInstance closedProcess = task.getProcessInstance();
/* Proces główny względem wstrzymywanego procesu */
ProcessInstance parentProcess = closedProcess.getParent();
boolean isSubProcess = parentProcess != null ;
boolean isParentProcess = !closedProcess.getChildren().isEmpty();
/* Zamykany proces jest podprocesem, wybierz do otwoarcia jego rodzica */
if(isSubProcess)
{
/* Przełącz się na proces głowny */
if(parentProcess.isProcessRunning())
return changeProcess(parentProcess);
}
/* Zamykany proces jest procesem glownym dla innych procesow */
if(isParentProcess)
{
/* Pobierz podprocesy skorelowane z zamykanym procesem */
for(ProcessInstance childProcess: task.getProcessInstance().getChildren())
{
if(childProcess.isProcessRunning())
{
/* Tylko jeden proces powinien być aktywny, przełącz się na
* niego
*/
return changeProcess(childProcess);
}
}
}
/* Zatrzymywany proces nie posiada ani aktywnego procesu głównego, ani
* aktywnych podprocesów. Zamknij więc widok
*/
return false;
}
private boolean changeProcess(ProcessInstance newProcess)
{
/* Get active task for current process */
List<BpmTask> activeTasks = bpmSession.findProcessTasks(newProcess, getCurrentContext());
/* Check if the current process has active task. It should has at least one */
if(activeTasks.isEmpty())
return false;
UserData user = bpmSession.getUser(getCurrentContext());
String userLogin = user.getLogin();
for(BpmTask task: activeTasks)
{
if(task.getAssignee() != null && task.getAssignee().equals(userLogin))
{
/* Change current task */
updateTask(task);
refreshTask();
return true;
}
}
/* There are no active task or the assigne is diffrent */
return false;
}
private HorizontalLayout getButtonsPanel(ProcessStateConfiguration stateConfiguration) {
// sort the actions to preserve the displaying order
List<ProcessStateAction> actionList = new ArrayList<ProcessStateAction>(stateConfiguration.getActions());
Collections.sort(actionList, new ActionPriorityComparator());
AligningHorizontalLayout buttonLayout = new AligningHorizontalLayout(Alignment.MIDDLE_RIGHT);
buttonLayout.setMargin(new MarginInfo(false, true, false, true));
buttonLayout.setWidth(100, Sizeable.UNITS_PERCENTAGE);
for (final ProcessStateAction a : actionList) {
final ProcessToolActionButton actionButton = makeButton(a);
actionButton.setEnabled(isOwner);
actionButton.loadData(task);
actionButton.setActionCallback(actionCallback);
if (actionButton instanceof ProcessToolVaadinRenderable) {
buttonLayout.addComponent(((ProcessToolVaadinRenderable) actionButton).render());
}
}
buttonLayout.addComponentAsFirst(new Label() {{
setWidth(100, Sizeable.UNITS_PERCENTAGE);
}});
buttonLayout.recalculateExpandRatios();
return buttonLayout;
}
public List<Component> getToolbarButtons() {
List<Component> buttons = new ArrayList<Component>();
Button saveButton = createSaveButton();
buttons.add(saveButton);
return buttons;
}
public boolean canSaveProcessData() {
return isOwner;
}
private Button createSaveButton() {
Button saveButton = VaadinUtility.link(i18NSource.getMessage("button.save.process.data"), new Button.ClickListener() {
@Override
public void buttonClick(Button.ClickEvent event) {
saveProcessDataButtonAction();
}
});
saveButton.addStyleName("with_message");
saveButton.setDescription(i18NSource.getMessage("button.save.process.desc"));
saveButton.setIcon(VaadinUtility.imageResource(application, "save.png"));
saveButton.setEnabled(isOwner);
return saveButton;
}
public boolean saveProcessDataButtonAction() {
final boolean[] result = { false };
withErrorHandling(application, new Runnable() {
@Override
public void run() {
if (validateWidgetsAndSaveData(task)) {
refreshTask();
guiAction = GuiAction.SAVE_PERFORMED;
initLayout(false);
result[0] = true;
}
}
});
return result[0];
}
private void refreshTask() {
task = refreshTask(bpmSession, task);
}
@Override
public void updateTask(BpmTask task) {
this.task = task;
}
@Override
public Set<ProcessToolDataWidget> getWidgets() {
return Collections.unmodifiableSet(dataWidgets);
}
@Override
public void displayValidationErrors(Map<ProcessToolDataWidget, Collection<String>> errorMap) {
String errorMessage = VaadinUtility.widgetsErrorMessage(i18NSource, errorMap);
VaadinUtility.validationNotification(application, i18NSource, errorMessage);
}
@Override
public Map<ProcessToolDataWidget, Collection<String>> getWidgetsErrors(BpmTask bpmTask, boolean skipRequired) {
Map<ProcessToolDataWidget, Collection<String>> errorMap = new HashMap();
for (ProcessToolDataWidget w : dataWidgets) {
Collection<String> errors = w.validateData(bpmTask, skipRequired);
if (errors != null && !errors.isEmpty()) {
errorMap.put(w, errors);
}
}
return errorMap;
}
@Override
public boolean validateWidgetsAndSaveData(BpmTask task) {
task = refreshTask(bpmSession, task);
Map<ProcessToolDataWidget, Collection<String>> errorMap = getWidgetsErrors(task, true);
if (!errorMap.isEmpty()) {
displayValidationErrors(errorMap);
return false;
}
saveTaskData(task);
return true;
}
@Override
public void saveTaskData(BpmTask task, ProcessToolActionButton... actions) {
for (ProcessToolDataWidget w : dataWidgets) {
w.saveData(task);
}
for (ProcessToolActionButton action : actions) {
action.saveData(task);
}
bpmSession.saveProcessInstance(task.getProcessInstance(), getCurrentContext());
}
@Override
public void saveTaskWithoutData(BpmTask task, ProcessToolActionButton... actions) {
for (ProcessToolActionButton action : actions) {
action.saveData(task);
}
}
@Override
public ProcessToolContext getCurrentContext() {
return ProcessToolContext.Util.getThreadProcessToolContext();
}
@Override
public BpmTask refreshTask(ProcessToolBpmSession bpmSession, BpmTask bpmTask) {
return bpmSession.refreshTaskData(bpmTask, getCurrentContext());
}
public String getMessage(String key) {
return i18NSource.getMessage(key);
}
private ProcessToolActionButton makeButton(ProcessStateAction a) {
try {
ProcessToolContext ctx = getCurrentContext();
ProcessToolActionButton actionButton = ctx.getRegistry().makeButton(a.getButtonName());
actionButton.setContext(a, bpmSession, application, i18NSource);
return actionButton;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private void processWidgetChildren(ProcessStateWidget parentWidgetConfiguration, ProcessToolWidget parentWidgetInstance,
ProcessStateConfiguration stateConfiguration, ProcessToolContext ctx, String generatorKey, WidgetEventBus widgetEventBus) {
Set<ProcessStateWidget> children = parentWidgetConfiguration.getChildren();
List<ProcessStateWidget> sortedList = new ArrayList<ProcessStateWidget>(children);
Collections.sort(sortedList, new Comparator<ProcessStateWidget>() {
@Override
public int compare(ProcessStateWidget o1, ProcessStateWidget o2) {
if (o1.getPriority().equals(o2.getPriority())) {
return Lang.compare(o1.getId(), o2.getId());
}
return o1.getPriority().compareTo(o2.getPriority());
}
});
if(parentWidgetInstance instanceof ProcessToolChildrenFilteringWidget){
sortedList = ((ProcessToolChildrenFilteringWidget)parentWidgetInstance).filterChildren(task, sortedList);
}
for (ProcessStateWidget subW : sortedList) {
if(StringUtils.isNotEmpty(subW.getGenerateFromCollection())){
generateChildren(parentWidgetInstance, stateConfiguration, ctx, subW, widgetEventBus);
} else {
subW.setParent(parentWidgetConfiguration);
addWidgetChild(parentWidgetInstance, stateConfiguration, ctx, subW, generatorKey, widgetEventBus);
}
}
}
/**
* Comparator for {@link ProcessStateWidget} objects that takes intro account widget priority
*/
private class WidgetPriorityComparator implements Comparator<ProcessStateWidget> {
@Override
public int compare(ProcessStateWidget w1, ProcessStateWidget w2) {
if (w1 == null || w2 == null) {
throw new NullPointerException("Can not compare null ProcessStateWidgets");
}
if (w1 == w2) {
return 0;
}
if (w1.getPriority() != null && w2.getPriority() != null) {
return w1.getPriority().compareTo(w2.getPriority());
} else if (w1.getPriority() != null && w2.getPriority() == null) {
return 1;
} else if (w1.getPriority() == null && w2.getPriority() != null) {
return -1;
} else {
return w1.getId().compareTo(w2.getId());
}
}
}
/**
* Comparator for {@link ProcessStateAction} object that takes into account action priority
*/
private class ActionPriorityComparator implements Comparator<ProcessStateAction> {
@Override
public int compare(ProcessStateAction a1, ProcessStateAction a2) {
if (a1 == null || a2 == null) {
throw new NullPointerException("Can not compare null ProcessStateActions");
}
if (a1 == a2) {
return 0;
}
if (a1.getActionType() != null && a1.getActionType() != null && !a1.getActionType().equals(a2.getActionType())) {
return ProcessStateAction.SECONDARY_ACTION.equals(a1.getActionType()) ? -1 : 1;
} else if (a1.getActionType() != null && a2.getActionType() == null) {
return -1;
} else if (a1.getActionType() == null && a2.getActionType() != null) {
return 1;
} else {
if (a1.getPriority() != null && a2.getPriority() != null) {
return a1.getPriority().compareTo(a2.getPriority());
} else if (a1.getPriority() != null && a2.getPriority() == null) {
return 1;
} else if (a1.getPriority() == null && a2.getPriority() != null) {
return -1;
} else {
return a1.getId().compareTo(a2.getId());
}
}
}
}
private void generateChildren(ProcessToolWidget parentWidgetInstance, ProcessStateConfiguration stateConfiguration, ProcessToolContext ctx,
ProcessStateWidget subW, WidgetEventBus widgetEventBus) {
String collection = task.getProcessInstance().getSimpleAttributeValue(subW.getGenerateFromCollection(), null);
if(StringUtils.isEmpty(collection))
return;
String[] items = collection.split("[,; ]");
for(String item : items){
addWidgetChild(parentWidgetInstance, stateConfiguration, ctx, subW, item, widgetEventBus);
}
}
private void addWidgetChild(ProcessToolWidget parentWidgetInstance, ProcessStateConfiguration stateConfiguration, ProcessToolContext ctx,
ProcessStateWidget subW, String generatorKey, WidgetEventBus widgetEventBus) {
ProcessToolWidget widgetInstance = getWidget(subW, stateConfiguration, ctx, generatorKey, widgetEventBus);
if (!nvl(subW.getOptional(), false) || widgetInstance.hasVisibleData()) {
processWidgetChildren(subW, widgetInstance, stateConfiguration, ctx, generatorKey, widgetEventBus);
parentWidgetInstance.addChild(widgetInstance);
}
}
private ProcessToolWidget getWidget(ProcessStateWidget w, ProcessStateConfiguration stateConfiguration, ProcessToolContext ctx,
String generatorKey, WidgetEventBus widgetEventBus) {
ProcessToolWidget processToolWidget;
try {
ProcessToolRegistry toolRegistry = VaadinUtility.getProcessToolContext(application.getContext()).getRegistry();
processToolWidget = w.getClassName() == null ? toolRegistry.makeWidget(w.getName()) : toolRegistry.makeWidget(w.getClassName());
processToolWidget.setContext(stateConfiguration, w, i18NSource, bpmSession, application,
bpmSession.getPermissionsForWidget(w, ctx), isOwner);
processToolWidget.setGeneratorKey(generatorKey);
processToolWidget.setWidgetEventBus(widgetEventBus);
if (processToolWidget instanceof ProcessToolDataWidget) {
((ProcessToolDataWidget) processToolWidget).loadData(task);
dataWidgets.add((ProcessToolDataWidget) processToolWidget);
}
}
catch (final Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
FailedProcessToolWidget failedProcessToolVaadinWidget = new FailedProcessToolWidget(e);
failedProcessToolVaadinWidget.setContext(stateConfiguration, w, i18NSource, bpmSession, application,
bpmSession.getPermissionsForWidget(w, ctx),
isOwner);
dataWidgets.add(failedProcessToolVaadinWidget);
processToolWidget = failedProcessToolVaadinWidget;
}
return processToolWidget;
}
private class MyProcessToolActionCallback implements ProcessToolActionCallback, Serializable {
private void actionCompleted(GuiAction guiAction, ProcessStateAction action) {
ProcessDataPane.this.guiAction = guiAction;
refreshTask();
initLayout(action.getAutohide());
}
@Override
public void actionPerformed(ProcessStateAction action) {
actionCompleted(GuiAction.ACTION_PERFORMED, action);
}
@Override
public void actionFailed(ProcessStateAction action) {
actionCompleted(GuiAction.ACTION_FAILED, action);
}
@Override
public WidgetContextSupport getWidgetContextSupport() {
return ProcessDataPane.this;
}
}
}