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