/*
* 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.executor;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.vaadin.data.Item;
import com.vaadin.terminal.Sizeable;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomTable;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.themes.Reindeer;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricDetail;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.ServiceImpl;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.interceptor.CommandExecutor;
import org.activiti.engine.impl.persistence.entity.HistoricVariableUpdateEntity;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.apache.commons.lang.StringUtils;
import org.tepi.filtertable.FilterTable;
import ru.codeinside.adm.AdminServiceProvider;
import ru.codeinside.adm.database.Bid;
import ru.codeinside.adm.ui.FilterDecorator_;
import ru.codeinside.adm.ui.LazyLoadingContainer2;
import ru.codeinside.gses.API;
import ru.codeinside.gses.activiti.forms.CustomTaskFormHandler;
import ru.codeinside.gses.activiti.forms.FormID;
import ru.codeinside.gses.activiti.forms.GetStartArchiveFormCmd;
import ru.codeinside.gses.activiti.forms.GetTaskArchiveFormCmd;
import ru.codeinside.gses.activiti.forms.api.definitions.PropertyNode;
import ru.codeinside.gses.activiti.forms.api.values.FormValue;
import ru.codeinside.gses.activiti.forms.api.values.PropertyValue;
import ru.codeinside.gses.service.F1;
import ru.codeinside.gses.service.F2;
import ru.codeinside.gses.service.Fn;
import ru.codeinside.gses.vaadin.JsonFormIntegration;
import ru.codeinside.gses.webui.ActivitiApp;
import ru.codeinside.gses.webui.Flash;
import ru.codeinside.gses.webui.components.api.IRefresh;
import ru.codeinside.gses.webui.data.OwnHistoryBeanQuery;
import ru.codeinside.gses.webui.form.EFormBuilder;
import ru.codeinside.gses.webui.form.FieldTree;
import ru.codeinside.gses.webui.form.GridForm;
import ru.codeinside.gses.webui.form.JsonForm;
import ru.codeinside.gses.webui.utils.Components;
import ru.codeinside.gses.webui.wizard.ExpandRequired;
import javax.xml.bind.DatatypeConverter;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
//TODO: првести рефакторинг этой мешанины кода
final public class ArchiveFactory implements Serializable {
private static final long serialVersionUID = -3060552897820352216L;
final static private SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy HH:mm");
public static Component create() {
final FilterTable bidsTable = createBidsTable();
final FilterTable phaseTable = createPhaseTable();
bidsTable.addGeneratedColumn("id", new IdColumnGenerator(bidsTable, phaseTable));
final HorizontalLayout bidsLayout = new HorizontalLayout();
bidsLayout.setSizeFull();
bidsLayout.setMargin(true);
bidsLayout.setSpacing(true);
bidsLayout.addComponent(bidsTable);
final VerticalLayout candidateLayout = new VerticalLayout();
candidateLayout.setSizeFull();
candidateLayout.setMargin(true);
candidateLayout.addComponent(phaseTable);
final TasksSplitter vSplitter = new TasksSplitter(new TableRefresh(bidsTable), new TableRefresh(phaseTable));
vSplitter.setSizeFull();
vSplitter.setFirstComponent(bidsLayout);
vSplitter.setSecondComponent(candidateLayout);
vSplitter.setSplitPosition(57);
return vSplitter;
}
static FilterTable createPhaseTable() {
final FilterTable candidate = Components.createFilterTable("100%", "100%");
candidate.setCaption("Этапы");
candidate.addContainerProperty("name", String.class, null);
candidate.addContainerProperty("date", String.class, null);
candidate.addContainerProperty("declarant", String.class, null);
candidate.addContainerProperty("form", Component.class, null);
candidate.setVisibleColumns(new Object[]{"name", "date", "declarant", "form"});
candidate.setColumnHeaders(new String[]{"Название", "Даты исполнения", "Исполнитель", ""});
candidate.setSelectable(false);
candidate.setFilterBarVisible(true);
candidate.setFilterDecorator(new FilterDecorator_());
candidate.setColumnExpandRatio("name", 1.5f);
candidate.setColumnExpandRatio("date", 2f);
candidate.setColumnExpandRatio("declarant", 1.5f);
candidate.setColumnExpandRatio("form", 1f);
return candidate;
}
static FilterTable createBidsTable() {
LazyLoadingContainer2 container = new LazyLoadingContainer2(new OwnHistoryBeanQuery());
container.addContainerProperty("id", Long.class, null);
container.addContainerProperty("procedure.name", String.class, null);
container.addContainerProperty("dateCreated", Date.class, null);
container.addContainerProperty("dateFinished", Date.class, null);
FilterTable bidTable = new FilterTable();
bidTable.setFilterBarVisible(true);
bidTable.setFilterDecorator(new FilterDecorator_());
bidTable.setCaption("Заявки");
bidTable.setImmediate(true);
bidTable.setContainerDataSource(container);
bidTable.setSizeFull();
bidTable.setColumnHeaders(new String[]{"№ Заявки", "Процедура", "Дата заявления", "Дата завершения"});
bidTable.setSelectable(false);
bidTable.setColumnExpandRatio("id", 0.1f);
bidTable.setColumnExpandRatio("procedure.name", 0.5f);
bidTable.setColumnExpandRatio("dateCreated", 0.2f);
bidTable.setColumnExpandRatio("dateFinished", 0.2f);
return bidTable;
}
static List<ActivityImpl> getActivityList(final Bid bid) {
return Fn.withEngine(new GetActivities(), Flash.login(), bid);
}
static boolean canShowUi(final ActivityImpl activity) {
return
isPropertyType(activity, "startEvent") ||
(isPropertyType(activity, "userTask") && activity.getActivityBehavior() instanceof UserTaskActivityBehavior);
}
static String getExecutionDate(HistoricActivityInstance cur) {
String executionDate;
if (cur != null) {
executionDate = formatter.format(cur.getStartTime());
if (cur.getEndTime() != null) {
executionDate += " - " + formatter.format(cur.getEndTime());
}
} else {
executionDate = "";
}
return executionDate;
}
static Map<String, String> getHistoryValues(Bid bid, Date toDate) {
Map<String, String> values = Maps.newHashMap();
for (HistoricVariableUpdateEntity hu : getLastVariableValues(bid, toDate)) {
String value = null;
Object rawValue = hu.getValue();
if (rawValue == null) {
value = "";
} else if (hu.getVariableType().getTypeName().equals("date")) {
value = new SimpleDateFormat("dd/MM/yyyy").format(rawValue);
} else {
if (rawValue instanceof byte[]) {
byte[] bytes = (byte[]) rawValue;
try {
value = new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
value = toBase64HumanString(bytes);
}
}
if (value == null) {
value = rawValue.toString();
}
}
values.put(hu.getName(), value);
}
return values;
}
private static Collection<HistoricVariableUpdateEntity> getLastVariableValues(Bid bid, Date toDate) {
Map<String, HistoricVariableUpdateEntity> lastChanges = Maps.newHashMap();
{
List<HistoricDetail> details = Fn.withEngine(new GetHistoricVariableUpdatesOrderByTimeDesc(), bid);
for (HistoricDetail detail : details) {
HistoricVariableUpdateEntity update = (HistoricVariableUpdateEntity) detail;
if (update.getTime().compareTo(toDate) <= 0) {
String variableName = update.getVariableName();
if (!lastChanges.containsKey(variableName)) {
lastChanges.put(variableName, update);
}
}
}
}
return lastChanges.values();
}
public static String toBase64HumanString(byte[] bytes) {
return Joiner.on('\n').join(
Splitter.fixedLength(64).split(DatatypeConverter.printBase64Binary(bytes))
);
}
static ActivityImpl activityInstance(final List<ActivityImpl> acs, HistoricActivityInstance history) {
ActivityImpl result = null;
for (ActivityImpl ac : acs) {
if (ac.getId().equals(history.getActivityId())) {
result = ac;
break;
}
}
return result;
}
static boolean isPropertyType(ActivityImpl ac, String typeName) {
return typeName.equals(ac.getProperty("type").toString());
}
static String getAssignee(Bid bid, ActivityImpl ac, HistoricActivityInstance cur) {
String assignee = "";
if (isPropertyType(ac, "startEvent")) {
assignee = bid.getDeclarant();
} else if (cur != null) {
assignee = cur.getAssignee();
}
return assignee;
}
static Window createFormWindow(Component su, ActivityImpl ac, String id) {
VerticalLayout content = new VerticalLayout();
content.setMargin(true);
content.addComponent(su);
Window window = new Window();
window.setWidth(800 + 50, Sizeable.UNITS_PIXELS);
window.setHeight(600 + 100, Sizeable.UNITS_PIXELS);
window.center();
window.setContent(content);
window.setCaption("Форма этапа " + ac.getId() + " по заявке #" + id);
if (su instanceof JsonFormIntegration || su instanceof ExpandRequired) {
content.setSizeFull();
content.setExpandRatio(su, 1f);
} else {
window.setResizable(false); // нет подстройки под размер
}
return window;
}
static Component createForm(final Bid bid, ActivityImpl activity, ProcessEngine engine, ActivitiApp app, final Date toDate) {
CommandExecutor commandExecutor = ((ServiceImpl) engine.getRuntimeService()).getCommandExecutor();
Map<String, String> historyValues = commandExecutor.execute(new Command<Map<String, String>>() {
@Override
public Map<String, String> execute(CommandContext commandContext) {
return getHistoryValues(bid, toDate);
}
});
FormValue formValue;
String processDefinitionId = bid.getProcedureProcessDefinition().getProcessDefinitionId();
FormID formID = FormID.byProcessDefinitionId(processDefinitionId);
if (isPropertyType(activity, "startEvent")) {
formValue = commandExecutor.execute(new GetStartArchiveFormCmd(processDefinitionId, historyValues));
} else if (isPropertyType(activity, "userTask") && activity.getActivityBehavior() instanceof UserTaskActivityBehavior) {
CustomTaskFormHandler taskFormHandler = (CustomTaskFormHandler) ((UserTaskActivityBehavior) activity.getActivityBehavior()).getTaskDefinition().getTaskFormHandler();
formValue = commandExecutor.execute(new GetTaskArchiveFormCmd(processDefinitionId, taskFormHandler, historyValues));
} else {
formValue = null;
}
if (formValue == null) {
return null;
}
if (StringUtils.isNotEmpty(formValue.getFormDefinition().getFormKey())) {
return new EFormBuilder(formValue, formID).getForm(null, null);
}
ImmutableMap<String, PropertyNode> propertyNodes = formValue.getFormDefinition().getIndex();
if (propertyNodes.containsKey(API.JSON_FORM)) {
String templateRef = null;
String value = null;
for (PropertyValue<?> propertyValue : formValue.getPropertyValues()) {
if (propertyValue.getId().equals(API.JSON_FORM)) {
templateRef = (String) propertyValue.getValue();
} else {
if (propertyValue.getValue() instanceof byte[]) {
try {
value = new String((byte[]) propertyValue.getValue(), "UTF-8");
} catch (UnsupportedEncodingException e) {
Logger.getAnonymousLogger().info("can't decode model!");
}
} else {
value = String.valueOf(propertyValue.getValue());
}
}
}
if (value != null) {
return JsonForm.createIntegration(formID, app, templateRef, value, true);
}
}
FieldTree fieldTree = new FieldTree(formID);
fieldTree.create(formValue);
GridForm form = new GridForm(formID, fieldTree);
form.setImmediate(true);
return form;
}
final public static class TableRefresh implements IRefresh, Serializable {
private static final long serialVersionUID = -3060552897820352219L;
private final FilterTable[] tables;
public TableRefresh(FilterTable... tables) {
this.tables = tables;
}
@Override
public void refresh() {
for (FilterTable t : tables) {
t.removeAllItems();
t.refreshRowCache();
}
}
}
final private static class IdColumnGenerator implements CustomTable.ColumnGenerator {
private static final long serialVersionUID = 1L;
private final FilterTable bidsTable;
private final FilterTable phaseTable;
public IdColumnGenerator(final FilterTable bidsTable, final FilterTable phaseTable) {
this.bidsTable = bidsTable;
this.phaseTable = phaseTable;
}
public Component generateCell(CustomTable source, Object itemId, Object columnId) {
final Item item = bidsTable.getContainerDataSource().getItem(itemId);
final String id = item.getItemProperty("id").getValue().toString();
Button button = new Button(id, new IdClickListener(id, phaseTable));
button.setStyleName(Reindeer.BUTTON_LINK);
return button;
}
}
final static class IdClickListener implements Button.ClickListener {
private static final long serialVersionUID = 1L;
private final String bidId;
private final FilterTable phaseTable;
public IdClickListener(String bidId, FilterTable phaseTable) {
this.bidId = bidId;
this.phaseTable = phaseTable;
}
@Override
public void buttonClick(ClickEvent event) {
phaseTable.setCaption("Этапы для заявки #" + bidId);
final Bid bid = AdminServiceProvider.get().getBid(bidId);
phaseTable.removeAllItems();
int index = 0;
List<HistoricActivityInstance> histories = Fn.withEngine(new GetHistoricInstances(), bid);
List<ActivityImpl> activities = getActivityList(bid);
for (final HistoricActivityInstance cur : histories) {
ActivityImpl activity = activityInstance(activities, cur);
if (activity == null) {
continue;
}
String assignee = getAssignee(bid, activity, cur);
Button button = null;
if (Flash.login().equals(assignee)) {
if (canShowUi(activity)) {
button = new Button("просмотреть");
Date time = cur.getEndTime() == null ? cur.getStartTime() : cur.getEndTime();
button.addListener(new ShowClickListener(activity.getId(), bidId, time));
}
}
String actName = activity.getProperty("name") != null ? activity.getProperty("name").toString() : "Без названия";
String executionDate = getExecutionDate(cur);
phaseTable.addItem(new Object[]{actName, executionDate, assignee, button}, index++);
}
}
}
// ----------------- persistence -----------------
public final static class ShowClickListener implements Button.ClickListener {
private final String activityId;
private final String bidId;
private final Date toDate;
public ShowClickListener(String activityId, String bidId, Date toDate) {
this.activityId = activityId;
this.bidId = bidId;
this.toDate = toDate;
}
@Override
public void buttonClick(ClickEvent event) {
ActivitiApp app = (ActivitiApp) event.getButton().getApplication();
Window win = Fn.withEngine(new CreateUi(), new Object[]{bidId, activityId, toDate}, app);
event.getButton().getWindow().addWindow(win);
AdminServiceProvider.get().createLog(Flash.getActor(), "Activity", activityId, "View in archive", "", true);
}
}
final private static class GetHistoricInstances implements F1<List<HistoricActivityInstance>, Bid> {
public List<HistoricActivityInstance> apply(final ProcessEngine engine, final Bid bid) {
return engine.getHistoryService().createHistoricActivityInstanceQuery().processInstanceId(bid.getProcessInstanceId()).list();
}
}
final private static class GetActivities implements F2<List<ActivityImpl>, String, Bid> {
@Override
public List<ActivityImpl> apply(ProcessEngine engine, String login, Bid bid) {
RepositoryServiceImpl impl = (RepositoryServiceImpl) engine.getRepositoryService();
String processDefinitionId = bid.getProcedureProcessDefinition().getProcessDefinitionId();
engine.getIdentityService().setAuthenticatedUserId(login);
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) impl.getDeployedProcessDefinition(processDefinitionId);
return processDefinition.getActivities();
}
}
final static class CreateUi implements F2<Window, Object[], ActivitiApp> {
@Override
public Window apply(ProcessEngine engine, Object[] arg, ActivitiApp app) {
String bidId = (String) arg[0];
String activityId = (String) arg[1];
Date toDate = (Date) arg[2];
Bid bid = AdminServiceProvider.get().getBid(bidId);
for (ActivityImpl activity : Fn.withEngine(new GetActivities(), Flash.login(), bid)) {
if (activityId.equals(activity.getId())) {
Component form = createForm(bid, activity, engine, app, toDate);
return createFormWindow(form, activity, bidId);
}
}
throw new IllegalStateException();
}
}
final private static class GetHistoricVariableUpdatesOrderByTimeDesc implements F1<List<HistoricDetail>, Bid> {
@Override
public List<HistoricDetail> apply(ProcessEngine engine, Bid bid) {
String processInstanceId = bid.getProcessInstanceId().trim();
return engine
.getHistoryService()
.createHistoricDetailQuery()
.processInstanceId(processInstanceId)
.variableUpdates()
.orderByTime()
.desc()
.list();
}
}
}