2.5.1.2. Контроллер экрана редактирования

Перейдите на вкладку Controller и замените ее содержимое на следующий код:

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;
            }
        });
    }
}

Рассмотрим фрагменты кода контроллера.

Метод postInit() вызывается после инициализации экрана и загрузки экземпляра EBook с представлением, указанным в XML-дескрипторе (в данном случае - eBook.edit).

После получения установленного в экране экземпляра EBook производится проверка, новый ли это экземпляр, или загруженный из БД. В первом случае управление передается методу initProcess(), который осуществляет подготовку карточки и экрана к старту нового экземпляра процесса:

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

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

Далее в зависимости от состояния карточки производится инициализация компонентов - stateLabel отображает текущее состояние, а для fieldGroup запрещается редактирование, если процесс уже стартовал:

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

Далее вызываются методы, производящие инициализацию возможных действий пользователя и таблицы вложений:

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

        initAttachmentsTable();
    }

Рассмотрим метод initProcess().

В начале метода производится загрузка из базы данных экземпляра объекта Proc с кодом book_scanning, то есть созданного нами процесса. Если загрузка прошла успешно, то экземпляр Proc устанавливается в карточке EBook:

    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");

Далее производится инициализация объектов CardRole - исполнителей ролей для данной карточки. Инициализировать роли можно различными способами, в том числе интерактивно - например, позволяя создателю карточки самому выбрать исполнителей. Главное, чтобы на момент перехода процесса в какое-либо состояние типа Assignment роль, требуемая для этого этапа, была назначена. Для целей нашего примера исполнители заданы в объектах DefaultProcActor на этапе настройки процесса, поэтому мы возьмем их оттуда и перенесем в объекты CardRole:

    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);
        }

В следующем фрагменте производится добавление всех созданных объектов CardRole в CommitContext перед коммитом экрана. Дело в том, что между Card и CardRole нет отношений каскадности сохранения, и если явно не сохранить созданные объекты CardRole в той же транзакции, что и ссылающийся на них объект Card, на Middleware возникнет ошибка. Обычно за включением в CommitContext всех измененных экземпляров следят источники данных (datasources), однако в данном случае мы создаем и связываем объекты вручную, поэтому данный код необходим:

    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) {
            }
        });
    }

Теперь рассмотрим методы инициализации кнопок, соответствующих возможным действиям пользователя по процессу, и таблицы вложений.

В методе initProcessActions() для данной карточки загружаются данные о текущем назначении, и если таковое имеется для текущего пользователя, в методе addProcessAction() создаются соответствующие кнопки:

    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);
    }

Таблица вложений представляет собой обычный компонент Table, связанный с источником данных attachmentsDs, извлекающим экземпляры CardAttachment данной карточки. Для загрузки файла вложения щелчком по имени файла в таблице создается генерируемая колонка для атрибута file. В результате ячейки данной колонки отображают компонент LinkButton, который по щелчку вызывает выгрузку соответствующего файла через интерфейс ExportDisplay.

    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;
            }
        });
    }