2.5.1.2. Editor Controller

Choose the Controller tab and replace its contents with the following code:

package com.sample.library.gui.ebook;

import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.global.CommitContext;
import com.haulmont.cuba.core.global.LoadContext;
import com.haulmont.cuba.core.global.PersistenceHelper;
import com.haulmont.cuba.gui.components.*;
import com.haulmont.cuba.gui.data.DataSupplier;
import com.haulmont.cuba.gui.data.DsContext;
import com.haulmont.cuba.gui.export.ExportDisplay;
import com.haulmont.cuba.gui.xml.layout.ComponentsFactory;
import com.haulmont.workflow.core.app.WfService;
import com.haulmont.workflow.core.entity.*;
import com.haulmont.workflow.core.global.AssignmentInfo;
import com.haulmont.workflow.core.global.WfConstants;
import com.haulmont.workflow.gui.base.action.ProcessAction;
import com.sample.library.entity.EBook;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class EBookEdit extends AbstractEditor<EBook> {

    @Inject
    protected WfService wfService;
    @Inject
    protected ComponentsFactory componentsFactory;
    @Inject
    protected BoxLayout actionsBox;
    @Inject
    protected DataSupplier dataSupplier;
    @Inject
    protected Label stateLabel;
    @Inject
    protected FieldGroup fieldGroup;
    @Inject
    protected Table attachmentsTable;
    @Inject
    protected ExportDisplay exportDisplay;

    @Override
    public void init(Map<String, Object> params) {
    }

    @Override
    protected void postInit() {
        EBook eBook = getItem();

        if (PersistenceHelper.isNew(eBook)) {
            initProcess(eBook);
        }

        if (eBook.getState() == null) {
            stateLabel.setValue("State: not started");
        } else {
            stateLabel.setValue("State: " + eBook.getLocState());
            fieldGroup.setEditable(false);
        }

        initProcessActions(eBook);

        initAttachmentsTable();
    }

    private void initProcess(final EBook eBook) {
        LoadContext loadContext = new LoadContext(Proc.class);
        loadContext.setQueryString("select p from wf$Proc p where p.code = :code")
                .setParameter("code", "book_scanning");
        loadContext.setView("start-process");
        Proc proc = dataSupplier.load(loadContext);
        if (proc != null)
            eBook.setProc(proc);
        else
            throw new IllegalStateException("Process not found");

        eBook.setRoles(new ArrayList<CardRole>());

        for (ProcRole procRole : proc.getRoles()) {
            if (procRole.getAssignToCreator())
                continue;
            CardRole cardRole = new CardRole();
            cardRole.setCard(eBook);
            cardRole.setProcRole(procRole);
            List<DefaultProcActor> defaultProcActors = procRole.getDefaultProcActors();
            if (defaultProcActors.isEmpty())
                throw new IllegalStateException("Default actor is not assigned for role " + procRole.getName());
            cardRole.setUser(defaultProcActors.get(0).getUser());
            eBook.getRoles().add(cardRole);
        }

        getDsContext().addListener(new DsContext.CommitListener() {
            @Override
            public void beforeCommit(CommitContext context) {
                context.getCommitInstances().addAll(eBook.getRoles());
            }

            @Override
            public void afterCommit(CommitContext context, Set<Entity> result) {
            }
        });
    }

    private void initProcessActions(EBook eBook) {
        AssignmentInfo assignmentInfo = wfService.getAssignmentInfo(eBook);
        if (eBook.getJbpmProcessId() == null && eBook.getState() == null) {
            addProcessAction(WfConstants.ACTION_START, assignmentInfo);
        } else if (assignmentInfo != null) {
            for (String actionName : assignmentInfo.getActions()) {
                addProcessAction(actionName, assignmentInfo);
            }
        }
    }

    private void addProcessAction(String actionName, AssignmentInfo assignmentInfo) {
        ProcessAction action = new ProcessAction(getItem(), actionName, assignmentInfo, this);
        Button button = componentsFactory.createComponent(Button.NAME);
        button.setAction(action);
        button.setAlignment(Alignment.MIDDLE_RIGHT);
        actionsBox.add(button);
    }

    private void initAttachmentsTable() {
        attachmentsTable.addGeneratedColumn("file", new Table.ColumnGenerator<CardAttachment>() {
            @Override
            public Component generateCell(final CardAttachment attachment) {
                LinkButton link = componentsFactory.createComponent(LinkButton.NAME);
                link.setCaption(attachment.getFile().getName());
                link.setAction(new AbstractAction("") {
                    @Override
                    public void actionPerform(Component component) {
                        exportDisplay.show(attachment.getFile());
                    }
                });
                return link;
            }
        });
    }
}

Below is the explanation of the controller code fragments.

The postInit() method is called after the screen is initialized and the EBook instance is loaded with the view specified in the XML-descriptor (in this case, it is eBook.edit).

When the EBook instance specified in the screen is loaded, the application verifies whether this instance is new or retrieved from the database. In the first case, the control is passed to the initProcess() method, which prepares the card and the screen for the launch of the new process instance.

        protected void postInit() {
        EBook eBook = getItem();

        if (PersistenceHelper.isNew(eBook)) {
            initProcess(eBook);
        }

Then, depending on the state of the card, components are initialized: stateLabel displays the current state, and for fieldGroup editing is prevented if the process has already started:

    protected void postInit() {
    ...
        if (eBook.getState() == null) {
            stateLabel.setValue("State: not started");
        } else {
            stateLabel.setValue("State: " + eBook.getLocState());
            fieldGroup.setEditable(false);
        }

Then methods are invoked that initialize possible user actions and attachments table:

    protected void postInit() {
    ...
        initProcessActions(eBook);

        initAttachmentsTable();
    }

Below is the initProcess() method.

Initially, the method loads a Proc object instance from the database with the book_scanning code, which is the process that we have created. If the data is loaded successfully, the Proc instance is set for the EBook card:

     private void initProcess(final EBook eBook) {
        LoadContext loadContext = new LoadContext(Proc.class);
        loadContext.setQueryString("select p from wf$Proc p where p.code = :code")
                .setParameter("code", "book_scanning");
        loadContext.setView("start-process");
        Proc proc = dataSupplier.load(loadContext);
        if (proc != null)
            eBook.setProc(proc);
        else
            throw new IllegalStateException("Process not found");

Then, the CardRole objects (role holders for this card) are initialized. The roles may be initialized in various ways, including interactive, for example, by allowing the card creator to choose role holders. The main concern when the process is changed to any state of the Assignment type is to ensure that the role required for this state has been assigned. In our example, the role holders have been assigned in DefaultProcActor objects during the process setup stage. Therefore, we will retrieve them from there and transfer them to CardRole objects:

     private void initProcess(final EBook eBook) {
    ...
        eBook.setRoles(new ArrayList<CardRole>());

        for (ProcRole procRole : proc.getRoles()) {
            if (procRole.getAssignToCreator())
                continue;
            CardRole cardRole = new CardRole();
            cardRole.setCard(eBook);
            cardRole.setProcRole(procRole);
            List<DefaultProcActor> defaultProcActors = procRole.getDefaultProcActors();
            if (defaultProcActors.isEmpty())
                throw new IllegalStateException("Default actor is not assigned for role " + procRole.getName());
            cardRole.setUser(defaultProcActors.get(0).getUser());
            eBook.getRoles().add(cardRole);
        }

In the next fragment, all the created CardRole objects are added to CommitContext before committing the screen. As there is no cascade saving for Card and CardRole, the Middleware will throw an error if the created CardRole objects referenced by the Card object are not explicitly saved in the same transaction. Typically, the inclusion of all changed instances in CommitContext is controlled by datasources. However, in this case we will create and link objects manually, so the following code is required:

     private void initProcess(final EBook eBook) {
    ...
        getDsContext().addListener(new DsContext.CommitListener() {
            @Override
            public void beforeCommit(CommitContext context) {
                context.getCommitInstances().addAll(eBook.getRoles());
            }

            @Override
            public void afterCommit(CommitContext context, Set<Entity> result) {
            }
        });
    }

The methods below initialize the buttons that correspond to possible user actions on the process, and the attachments table.

The information about the current assignment for this card is loaded in the initProcessActions() method. If such an assignment exists for the current user, the corresponding buttons are created in the addProcessAction() method:

    private void initProcessActions(EBook eBook) {
        AssignmentInfo assignmentInfo = wfService.getAssignmentInfo(eBook);
        if (eBook.getJbpmProcessId() == null && eBook.getState() == null) {
            addProcessAction(WfConstants.ACTION_START, assignmentInfo);
        } else if (assignmentInfo != null) {
            for (String actionName : assignmentInfo.getActions()) {
                addProcessAction(actionName, assignmentInfo);
            }
        }
    }

    private void addProcessAction(String actionName, AssignmentInfo assignmentInfo) {
        ProcessAction action = new ProcessAction(getItem(), actionName, assignmentInfo, this);
        Button button = componentsFactory.createComponent(Button.NAME);
        button.setAction(action);
        button.setAlignment(Alignment.MIDDLE_RIGHT);
        actionsBox.add(button);
    }

The attachments table is a standard Table component associated with the attachmentsDs datasource that loads CardAttachment instances related to this card. In the table, a column is generated for the file attribute, which provides the ability to download the attachment by clicking the filename. The column cells display the LinkButton component, which invokes the corresponding file download on click using the ExportDisplay interface.

    private void initAttachmentsTable() {
        attachmentsTable.addGeneratedColumn("file", new Table.ColumnGenerator<CardAttachment>() {
            @Override
            public Component generateCell(final CardAttachment attachment) {
                LinkButton link = componentsFactory.createComponent(LinkButton.NAME);
                link.setCaption(attachment.getFile().getName());
                link.setAction(new AbstractAction("") {
                    @Override
                    public void actionPerform(Component component) {
                        exportDisplay.show(attachment.getFile());
                    }
                });
                return link;
            }
        });
    }