/*
* @(#)ManageMetaProcessStatesComponent.java
*
* Copyright 2011 Instituto Superior Tecnico
* Founding Authors: Paulo Abrantes
*
* https://fenix-ashes.ist.utl.pt/
*
* This file is part of the Meta-Workflow Module.
*
* The Meta-Workflow Module is free software: you can
* redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version
* 3 of the License, or (at your option) any later version.
*
* The Meta-Workflow Module is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the Meta-Workflow Module. If not, see .
*
*/
package module.metaWorkflow.presentationTier.vaadin;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import module.metaWorkflow.domain.MetaField;
import module.metaWorkflow.domain.MetaFieldSet;
import module.metaWorkflow.domain.MetaProcessState;
import module.metaWorkflow.domain.MetaProcessStateConfig;
import module.metaWorkflow.domain.WorkflowMetaType;
import module.metaWorkflow.domain.WorkflowMetaTypeVersion;
import module.vaadin.ui.BennuTheme;
import pt.ist.bennu.core.applicationTier.Authenticate.UserView;
import pt.ist.bennu.core.domain.RoleType;
import pt.ist.bennu.core.domain.exceptions.DomainException;
import pt.ist.bennu.core.util.BundleUtil;
import pt.ist.fenixframework.pstm.AbstractDomainObject;
import pt.ist.vaadinframework.annotation.EmbeddedComponent;
import pt.ist.vaadinframework.data.reflect.DomainContainer;
import pt.ist.vaadinframework.data.reflect.DomainItem;
import pt.ist.vaadinframework.ui.DefaultFieldFactory;
import pt.ist.vaadinframework.ui.EmbeddedComponentContainer;
import pt.ist.vaadinframework.ui.TransactionalForm;
import pt.ist.vaadinframework.ui.TransactionalTable;
import pt.ist.vaadinframework.ui.fields.MultiLanguageStringField;
import pt.utl.ist.fenix.tools.util.i18n.Language;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.util.ContainerHierarchicalWrapper;
import com.vaadin.terminal.ThemeResource;
import com.vaadin.ui.AbstractSelect.MultiSelectMode;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.Table;
import com.vaadin.ui.Table.ColumnGenerator;
import com.vaadin.ui.TextField;
import com.vaadin.ui.TreeTable;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.themes.BaseTheme;
/**
*
* @author David Martinho
* @author João Neves
* @author João Antunes
*
*/
@EmbeddedComponent(path = { "metaProcessStatesManagement" }, args = { "metaTypeVersion" })
public class ManageMetaProcessStatesComponent extends CustomComponent implements EmbeddedComponentContainer {
private static final String RESOURCE_BUNDLE = "resources/MetaWorkflowResources";
private static final long serialVersionUID = 1L;
private final TransactionalForm form = new TransactionalForm(RESOURCE_BUNDLE);
private final TransactionalTable stateTable = new TransactionalTable(RESOURCE_BUNDLE);
private MetaProcessState selectedState;
private VerticalLayout operationsLayout;
private DomainContainer states;
public ManageMetaProcessStatesComponent(WorkflowMetaTypeVersion metaTypeVersion) {
manageStatesInterface(metaTypeVersion);
}
public static abstract class ConfirmationWindow extends Window {
private static final long serialVersionUID = 1L;
public static final Integer STR_TO_PX_RATIO = 8;
public ConfirmationWindow(String confirmationMessage) {
this(confirmationMessage, getMessage("confirm"));
}
public ConfirmationWindow(String confirmationMessage, String windowTitle) {
super(windowTitle);
setModal(true);
Integer width = (confirmationMessage.length() * STR_TO_PX_RATIO);
setWidth(width + "px");
VerticalLayout content = (VerticalLayout) getContent();
content.setSpacing(true);
content.addComponent(new Label(confirmationMessage));
HorizontalLayout buttons = new HorizontalLayout();
buttons.setSpacing(true);
Button buttonConfirm = new Button(getMessage("confirm"), new Button.ClickListener() {
private static final long serialVersionUID = 1L;
@Override
public void buttonClick(ClickEvent event) {
getParent().removeWindow(ConfirmationWindow.this);
onConfirm();
}
});
Button buttonCancel = new Button(getMessage("cancel"), new Button.ClickListener() {
private static final long serialVersionUID = 1L;
@Override
public void buttonClick(ClickEvent event) {
getParent().removeWindow(ConfirmationWindow.this);
}
});
buttons.addComponent(buttonConfirm);
buttons.addComponent(buttonCancel);
content.addComponent(buttons);
}
abstract public void onConfirm();
}
private void manageStatesInterface(final WorkflowMetaTypeVersion metaTypeVersion) {
VerticalLayout headerLayout = new VerticalLayout();
setCompositionRoot(headerLayout);
ManageMetaTypeVersionComponent.addOperationTitleAndMetaTypeName(headerLayout, "label.metaType.manageStates",
metaTypeVersion);
final HorizontalLayout content = new HorizontalLayout();
content.setSpacing(true);
headerLayout.addComponent(content);
initContent(content, metaTypeVersion);
}
private void initContent(final HorizontalLayout content, final WorkflowMetaTypeVersion metaTypeVersion) {
DomainItem metaTypeDI = new DomainItem(metaTypeVersion);
states = (DomainContainer) metaTypeDI.getItemProperty("processStates");
states.setContainerProperties("name", "position");
VerticalLayout tableLayout = new VerticalLayout();
tableLayout.setSpacing(true);
content.addComponent(tableLayout);
initTableLayout(tableLayout, metaTypeVersion);
operationsLayout = new VerticalLayout();
operationsLayout.setSpacing(true);
content.addComponent(operationsLayout);
form.setFormFieldFactory(new DefaultFieldFactory(RESOURCE_BUNDLE));
form.addSubmitButton();
ManageMetaTypeVersionComponent.readOnlyOrDisabledIfPublished(form, metaTypeVersion);
stateTable.addListener(new ValueChangeListener() {
@Override
public void valueChange(ValueChangeEvent event) {
selectedState = (MetaProcessState) event.getProperty().getValue();
if (selectedState != null) {
initOperationsLayout(metaTypeVersion);
} else {
operationsLayout.removeAllComponents();
}
}
});
}
private void initTableLayout(VerticalLayout tableLayout, final WorkflowMetaTypeVersion metaTypeVersion) {
stateTable.setContainerDataSource(states);
stateTable.setSelectable(true);
stateTable.setImmediate(true);
stateTable.addGeneratedColumn("", new ColumnGenerator() {
@Override
public Object generateCell(final Table source, final Object itemId, Object columnId) {
Button buttonRemove = new Button("", new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
final MetaProcessState state = ((MetaProcessState) itemId);
ConfirmationWindow confirmationWindow =
new ConfirmationWindow(getMessage("state.remove.confirm", state.getName().getContent())) {
@Override
public void onConfirm() {
try {
state.delete();
source.unselect(state);
states.removeItem(state);
} catch (DomainException ex) {
ManageMetaProcessStatesComponent.this.getWindow().showNotification(
getMessage("error.while.deleting.state") + "
",
getMessage(ex.getMessage()), Notification.TYPE_ERROR_MESSAGE);
}
}
};
getWindow().addWindow(confirmationWindow);
}
});
buttonRemove.setIcon(new ThemeResource("../runo/icons/16/cancel.png"));
buttonRemove.addStyleName(BennuTheme.BUTTON_SMALL);
ManageMetaTypeVersionComponent.readOnlyOrDisabledIfPublished(buttonRemove, metaTypeVersion);
return buttonRemove;
}
});
tableLayout.addComponent(stateTable);
Button buttonAdd = new Button(getMessage("link.processState.add"), new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
addProcessState(metaTypeVersion, states);
}
});
buttonAdd.addStyleName(BaseTheme.BUTTON_LINK);
ManageMetaTypeVersionComponent.readOnlyOrDisabledIfPublished(buttonAdd, metaTypeVersion);
tableLayout.addComponent(buttonAdd);
}
private void initOperationsLayout(final WorkflowMetaTypeVersion metaTypeVersion) {
operationsLayout.removeAllComponents();
operationsLayout.addComponent(form);
form.setItemDataSource(stateTable.getItem(selectedState), Arrays.asList(new Object[] { "name", "position" }));
form.setWriteThrough(false);
if (!selectedState.hasAnyConfigs()) {
Label noConditions = new Label(getMessage("state.activation.conditions.none"));
operationsLayout.addComponent(noConditions);
} else {
Panel conditionsPanel = new Panel(getMessage("state.activation.conditions"));
conditionsPanel.setHeight("275px");
conditionsPanel.setWidth("620px");
operationsLayout.addComponent(conditionsPanel);
Iterator configIterator = selectedState.getConfigs().iterator();
while (configIterator.hasNext()) {
MetaProcessStateConfig config = configIterator.next();
HorizontalLayout configLayout = new HorizontalLayout();
configLayout.setSpacing(true);
conditionsPanel.addComponent(configLayout);
initConfigLayout(config, configLayout, metaTypeVersion);
if (configIterator.hasNext()) {
Label or = new Label(getMessage("state.activation.conditions.or"));
conditionsPanel.addComponent(or);
}
}
}
Button addConfigButton = new Button(getMessage("state.activation.conditions.add"), new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
addStateConfig(selectedState);
initOperationsLayout(metaTypeVersion);
}
});
addConfigButton.addStyleName(BaseTheme.BUTTON_LINK);
ManageMetaTypeVersionComponent.readOnlyOrDisabledIfPublished(addConfigButton, metaTypeVersion);
operationsLayout.addComponent(addConfigButton);
}
private void initConfigLayout(final MetaProcessStateConfig config, HorizontalLayout configLayout,
final WorkflowMetaTypeVersion metaTypeVersion) {
Iterator dependedStatesIterator = config.getDependedStates().iterator();
Iterator dependedFieldsIterator = config.getDependedFields().iterator();
Label indentationLabel = new Label(" - ");
configLayout.addComponent(indentationLabel);
if (!dependedStatesIterator.hasNext() && !dependedFieldsIterator.hasNext()) {
Label noDependenciesLabel = new Label(getMessage("state.activation.conditions.depended.none"));
configLayout.addComponent(noDependenciesLabel);
}
if (dependedStatesIterator.hasNext()) {
Label statesLabel = new Label(getMessage("state.activation.conditions.depended.states") + ": ");
configLayout.addComponent(statesLabel);
}
while (dependedStatesIterator.hasNext()) {
MetaProcessState dependedState = dependedStatesIterator.next();
Label dependedStateLabel = new Label(dependedState.getName().getContent());
configLayout.addComponent(dependedStateLabel);
if (dependedStatesIterator.hasNext()) {
Label separatorLabel = new Label(", ");
configLayout.addComponent(separatorLabel);
} else {
Label separatorLabel = new Label(" ");
configLayout.addComponent(separatorLabel);
}
}
if (dependedFieldsIterator.hasNext()) {
Label fieldsLabel = new Label(getMessage("state.activation.conditions.depended.fields") + ": ");
configLayout.addComponent(fieldsLabel);
}
while (dependedFieldsIterator.hasNext()) {
MetaField dependedField = dependedFieldsIterator.next();
Label dependedFieldLabel = new Label(dependedField.getName().getContent());
configLayout.addComponent(dependedFieldLabel);
if (dependedFieldsIterator.hasNext()) {
Label separatorLabel = new Label(", ");
configLayout.addComponent(separatorLabel);
}
}
Button deleteConfigButton = new Button(getMessage("state.activation.conditions.del"), new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
deleteStateConfig(config);
initOperationsLayout(metaTypeVersion);
}
});
deleteConfigButton.addStyleName(BaseTheme.BUTTON_LINK);
ManageMetaTypeVersionComponent.readOnlyOrDisabledIfPublished(deleteConfigButton, metaTypeVersion);
configLayout.addComponent(deleteConfigButton);
configLayout.addComponent(new Label(", "));
Button editConfigButton =
new Button(getMessage("state.activation.conditions.depended.change"), new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
openEditConfigWindow(config, metaTypeVersion);
}
});
editConfigButton.addStyleName(BaseTheme.BUTTON_LINK);
ManageMetaTypeVersionComponent.readOnlyOrDisabledIfPublished(editConfigButton, metaTypeVersion);
configLayout.addComponent(editConfigButton);
}
private static final String NEW_STATE_LABEL = "new.state.name";
private void openEditConfigWindow(final MetaProcessStateConfig config, final WorkflowMetaTypeVersion metaTypeVersion) {
final Window editConfigWindow = new Window(getMessage("state.activation.conditions.depended.change"));
getWindow().addWindow(editConfigWindow);
editConfigWindow.setModal(true);
editConfigWindow.setHeight("550px");
editConfigWindow.setWidth("550px");
VerticalLayout content = new VerticalLayout();
editConfigWindow.addComponent(content);
content.setSpacing(true);
content.addComponent(new Label(getMessage("state.activation.conditions.choose.dependencies")));
TabSheet dependenciesTabs = new TabSheet();
dependenciesTabs.setHeight("400px");
content.addComponent(dependenciesTabs);
VerticalLayout fieldsLayout = new VerticalLayout();
fieldsLayout.setSpacing(true);
fieldsLayout.setMargin(true);
dependenciesTabs.addTab(fieldsLayout, getMessage("state.activation.conditions.depended.fields"));
final TreeTable fieldsTable = new TreeTable();
fieldsLayout.addComponent(fieldsTable);
fieldsTable.setSelectable(true);
fieldsTable.setMultiSelect(true);
fieldsTable.setMultiSelectMode(MultiSelectMode.SIMPLE);
DomainContainer fieldContainer = new DomainContainer(MetaField.class);
fieldContainer.setContainerProperties("name", "localizedClassName", "fieldOrder");
ContainerHierarchicalWrapper fieldContainerWrapper = new ContainerHierarchicalWrapper(fieldContainer);
MetaFieldSet fieldSet = config.getMetaProcessState().getWorkflowMetaType().getFieldSet();
fieldContainerWrapper.addItem(fieldSet);
addFieldHierarchy(fieldContainerWrapper, fieldSet);
fieldsTable.setContainerDataSource(fieldContainerWrapper);
fieldsTable.setColumnHeader("name", getMessage("module.metaWorkflow.domain.MetaField.name"));
fieldsTable.setColumnHeader("localizedClassName", getMessage("module.metaWorkflow.domain.MetaField.localizedClassName"));
fieldsTable.setColumnHeader("fieldOrder", getMessage("module.metaWorkflow.domain.MetaField.fieldOrder"));
fieldsTable.setCollapsed(fieldSet, false);
// Pre-select already depended fields
for (MetaField dependedField : config.getDependedFields()) {
fieldsTable.select(dependedField);
}
VerticalLayout statesLayout = new VerticalLayout();
statesLayout.setSpacing(true);
statesLayout.setMargin(true);
dependenciesTabs.addTab(statesLayout, getMessage("state.activation.conditions.depended.states"));
WorkflowMetaType metaType = config.getMetaProcessState().getWorkflowMetaType();
Set possibleStates = new HashSet();
possibleStates.addAll(metaType.getProcessStates());
// A state cannot depend on itself
possibleStates.remove(config.getMetaProcessState());
if (possibleStates.isEmpty()) {
statesLayout.addComponent(new Label(getMessage("state.activation.conditions.depended.none.to.add")));
return;
}
final TransactionalTable dependedStatesTable = new TransactionalTable(RESOURCE_BUNDLE);
DomainContainer dependedStates =
new DomainContainer(possibleStates, MetaProcessState.class);
dependedStates.setContainerProperties("name", "position");
dependedStatesTable.setContainerDataSource(dependedStates);
dependedStatesTable.setSelectable(true);
dependedStatesTable.setMultiSelect(true);
dependedStatesTable.setMultiSelectMode(MultiSelectMode.SIMPLE);
// Pre-select already depended states
for (MetaProcessState dependedState : config.getDependedStates()) {
dependedStatesTable.select(dependedState);
}
statesLayout.addComponent(dependedStatesTable);
Button saveButton = new Button(getMessage("state.activation.conditions.save"));
saveButton.addListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
config.updateDependedStates((Collection) dependedStatesTable.getValue());
config.updateDependedFields((Collection) fieldsTable.getValue());
getWindow().removeWindow(editConfigWindow);
initOperationsLayout(metaTypeVersion);
}
});
content.addComponent(saveButton);
}
public void addFieldHierarchy(ContainerHierarchicalWrapper container, MetaFieldSet fieldSet) {
for (MetaField childField : fieldSet.getOrderedChildFields()) {
container.addItem(childField);
container.setParent(childField, fieldSet);
if (childField instanceof MetaFieldSet) {
addFieldHierarchy(container, (MetaFieldSet) childField);
} else {
container.setChildrenAllowed(childField, false);
}
}
}
private void addProcessState(WorkflowMetaTypeVersion metaTypeVersion, DomainContainer states) {
MetaProcessState newState = MetaProcessState.create(metaTypeVersion, getMessage(NEW_STATE_LABEL), 1);
states.addItem(newState);
stateTable.setValue(newState);
stateTable.sort();
TextField ptText = ((MultiLanguageStringField) form.getField("name")).getTextField(Language.pt);
ptText.focus();
ptText.setSelectionRange(0, ((String) ptText.getValue()).length());
}
private void addStateConfig(MetaProcessState state) {
MetaProcessStateConfig.create(state);
}
private void deleteStateConfig(MetaProcessStateConfig config) {
config.delete();
}
private static String getMessage(String message, String... args) {
return BundleUtil.getFormattedStringFromResourceBundle(RESOURCE_BUNDLE, message, args);
}
@Override
public boolean isAllowedToOpen(Map arguments) {
return UserView.getCurrentUser().hasRoleType(RoleType.MANAGER);
}
@Override
public void setArguments(Map arg0) {
WorkflowMetaTypeVersion metaTypeVersion = AbstractDomainObject.fromExternalId(arg0.get("metaTypeVersion"));
manageStatesInterface(metaTypeVersion);
}
}