Перейдите на вкладку 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; } }); }