Editor and Finder Code
From Taylor
The Editor/Finder pattern provides the controller classes/backing beans for the CRUD MVC.
This page discusses the details of the finder and editor bean code that is generated for each entity in a model.
For an outline of the user story fulfilled by the Editor/Finder pattern see the abstract use case.
For more information we recommend reviewing the code generation templates and the base classes in taylor-commons packages: net.taylor.seam and net.taylor.richfaces.
The code is generated in an editor package under the entity package. The facelets are generated under src/main/resources.
Contents |
Finder
Finder beans control the interaction between the Model and the Search View.
@Name("projectFinder")
@Stateful
@Scope(ScopeType.SESSION)
public class ProjectFinderBean extends AbstractFinderBean< Project > implements ProjectFinder {
...
}
Columns
Columns are the most frequently customized part of a finder bean.
Instead of generating the columns into the facelets, which can not be merged on regeneration, this method provides the necessary metadata to the facelet.
The arguments to addColumn() are:
- property access expression
- property type
- messages expression (optional)
- rendered expression (optional)
/** @NOT generated */
protected void initColumns() {
addColumn("type.name", "String", "Tag", null);
addColumn("status.name", "String", "Tag", null);
addColumn("title", "String", null);
addColumn("owner", "String", null);
addColumn("created", "Timestamp", null);
addColumn("project.name", "String", "Project", null);
addColumn("milestone.name", "String", "Milestone", null);
}
Filter
This method initializes the metadata that is used to render the filter criteria form of the Search facelets.
/** @generated */
protected void initFilter(Filter filter) {
filter.addStringParameter("title");
filter.addDateParameter("created");
filter.addM2oParameter("project", "name", "select id, name from Project order by name");
}
- Many2one parameters with large data sets should use setFreeForm(true)
- Also leveraged in My Filters
Order By
Customize this code to change the initial sort order.
/** @generated */
public ProjectFinderBean() {
this.order = "name";
this.orderBy = "name";
//this.descending = true;
}
Render Rules
Finders inherit several isRender methods for controlling what links are displayed. Override these as necessary.
These are classes related and used in the action sidebar:
- isRenderCreate
- isRenderPrint
- isRenderCharts
- isRenderExcel
- isRenderExport
- isRenderImport
- isRenderAction
- isRenderQuickAdd
These are instance related and used in the data table action column:
- isRenderView
- isRenderEdit
- isRenderPrintDetails
- isRenderDelete
ComboBoxes and Pickers
ComboBoxes and Pickers provide functionality for finding entities that will be used in many2one and many2many associations.
These classes are generated in the finder bean, because they are reusable across many editor beans. The common logic is generated/defined once and extended when used in the editor beans (as discussed below).
These classes also provide filtering for entities with large data sets to ensure high performance.
- For comboBox, override isFiltered() and rich:suggestionbox will be used
- For pickers, this is done automatically via paging
These classes will also filter out data based on security permissions when the Taylor Security stereotypes/annotations are used.
Some methods will be generated with the following TODO comment to indicate when customization is necessary.
- // TODO change the 'name' property as appropriate
- The generated unit tests should fail when these changes have not been made.
These classes use the template pattern extensively, so there are many more extension points available for overriding behavior.
/** @generated */
public static class ProjectComboBox<COMP, SRC> extends ComboBox<COMP, Project, SRC> {
...
/** @generated */
protected Class getEntityClass() {
return Project.class;
}
/** @generated */
protected String getItemLabel() {
return "#{t1.name}";
}
/** @generated */
protected String getOrderBy() {
return "t1.name";
}
...
public boolean isFiltered() {
return true;
}
protected String getFilterRestriction() {
return "lower(t1.name) like :filter";
}
}
/** @generated */
public static class ProjectPicker<COMP, SRC> extends Picker<COMP, Project, SRC> {
...
/** @generated */
protected Class getEntityClass() {
return Project.class;
}
/** @generated */
protected String getItemLabel() {
return "#{t1.name}";
}
/** @generated */
protected String getOrderBy() {
return "t1.name";
}
/** @generated */
protected String getFilterRestriction() {
return "lower(t1.name) like :filter";
}
...
}
Menu
Some entities are referred to as master entities. These are entities that do not play a part of or child role in any one2many association.
For these entities a menu class will be generated in the finder to add the entity's search/landing page to the menu bar.
- see Taylor Portal for details
Simple Entity
Some entities are referred to as simple entities. These are entities that do not play the parent role in a one2many associations or the source role in a many2many association.
When this is true then the Search facelet will display a Quick Add form for adding new instances. Plus, various isRender methods leverage this by default as well.
This is controlled by the generated isSimpleEntity() method. A similar method is generated on the editor bean as well.
Charts
Pie charts will be generated for entities with properties of type enumeration or many2one.
/** @generated */
public PieChart getProjectPieChart() {
return new PieChart(getEntityManager(), "Ticket", "project.name");
}
These will be generated with the following TODO comment to indicate when customization is necessary.
- // TODO change the 'name' property as appropriate
- The generated unit tests should fail when these changes have not been made.
Editor
Editor beans are responsible for controlling the interaction between a specific instance of a model entity and the view layer in edit or view mode.
@Name("projectEditor")
@Stateful
public class ProjectEditorBean extends AbstractEditorBean<Project> implements
ProjectEditor {
...
}
- net.taylor.seam.AbstractEditorBean extends from org.jboss.seam.framework.EntityHome
Instance Access
The getInstance() method is responsible for retrieving a specific instance based on the id @RequestParameter or creating a new instance.
The init factory method is used to trigger getInstance() in a decoupled manor. It is event scoped to support the quick add feature.
You will likely want to customize the createInstance() method to set default values.
/** @generated */
@Factory(value="ticket", scope=ScopeType.EVENT, autoCreate=true)
public Ticket initTicket() {
return getInstance();
}
/** @NOT generated */
@Override
protected Ticket createInstance() {
Ticket result = new Ticket();
result.setOwner(getCurrentUserName());
result.setCreated(new Date());
result.setStatus(Status.Created);
return result;
}
Persistence
Editors provide the save() and delete() methods for entities. Following the template pattern there are several extension points.
prePersist() and preRemove() methods are generated for entities that play the child role in a one to many association. They are responsible for connecting and disconnecting the association. The initNewInstance() methods on FolderTreeNodes are responsible for starting the association. (see below)
/** @generated */
protected void prePersist() {
super.prePersist();
if (getInstance().getParent() != null) {
getInstance().getParent().addSubTicket(getInstance());
}
}
/** @generated */
protected void preRemove() {
super.preRemove();
if (getInstance().getParent() != null) {
getInstance().getParent().removeSubTicket(getInstance());
}
}
The canDelete() method is generated for entities that play the referenced role in a manyToOne association. This method checks to ensure the instance is not associated before deletion to maintain referential integrity. If canDelete() returns false then a message is display and the entity is not deleted.
/** @generated */
protected boolean canDelete() {
if (!super.canDelete()) {
return false;
}
long count = 0;
count += countReferences("from Artifact where project = :me");
count += countReferences("from Artifact where project = :me");
count += countReferences("from Artifact where project = :me");
return count == 0;
}
ComboBoxes and Pickers
A combobox will be generated for each many2one association.
- Used in conjunction with tt:reference-input
/** @NOT generated */
@Out
protected ComboBox milestoneComboBox = new MilestoneComboBox<TicketEditor, Ticket>(this, "milestone")
.addRestriction("t1.project = #{ticket.project}");
A picker will be generated for each many2many, one2many shared, and one2one association.
- Used in conjunction with tt:picker and edit-folder.xhtml
/** @generated */
@Out
protected Picker tagsPicker = new TagPicker<ArtifactEditor, Artifact>(this, "tags") {
protected void add(Artifact instance, Tag ref) {
instance.addTag(ref);
}
protected void remove(Artifact instance, Tag ref) {
instance.removeTag(ref);
}
public boolean isRendered() {
return true;
}
};
Seam managed entity managers allow eql to be created with expression language for paramaters. Both comboboxes and pickers can be extended by adding these restrictions.
The combobox above shows an example of restricting the milestone combobox to milestones that are associated with the project that is already associated to the ticket.
addRestriction("t1.project = #{ticket.project}")
Render Rules
Editors inherit several isRender methods for controlling what links are displayed.
- isRenderView
- isRenderEdit(E instance)
- isRenderDelete(E instance)
- isRenderCreateClone
- isRenderPrint
Override these methods as necessary. The following page suggests when to override these methods. Entity Maintenance
For property render rules - see Defining Presentation Rules
Status Flow
- see Status Flow
Tree
The major purpose of the Tree pattern is to simplify the view logic by providing a generic, recursive api for rendering the entity model graph in the edit, view, and pdf facelets.
It also borrows from the flyweight pattern by leveraging a single editor instance over multiple entities of the same type.
NOTE: This tree concept was originally implemented to work with the RichFaces tree, but evolved to its current state.
See Template
Root
Every tree needs a root. The root is initialized by the editor bean at the beginning of a conversation with the master entity node.
/** @generated */
@Factory("ticketTree")
public RootTreeNode initTicketTree() {
TicketTreeNode node = new TicketTreeNode(root, getInstance());
return initRoot(node);
}
Entity Node
Entity nodes provide a generic wrapper api around a single entity instance that is leveraged by the facelets templates.
A concrete entity tree node is generated for each entity.
The getText() method is used to display identifying information about an entity. If the entity does not have a name property then this method will need to be customized.
/** @NOT generated */
public String getText() {
// TODO change the 'name' property as appropriate
return getInstance().getTitle();
}
The entity tree node will define a folder tree node for each one2many, many2many, and one2one association.
/** @generated */
private FolderTreeNode commentsFolder = new CommentFolderTreeNode(this,
"Ticket_comments", CardinalityType.O2M) {
protected Collection<Comment> getCollection() {
return getInstance().getComments();
}
protected void initNewInstance(Comment newInstance) {
newInstance.setArtifact(getInstance());
}
};
The getCollection() method is used to initialize the folder with the associated entities.
The initNewInstance() method is used to start a new association. (see prePersist & preRemove above)
Folder
Folder nodes provide a generic wrapper api around an association collection that is leveraged by the facelets templates.
- The add/remove child methods modify the nodes in the tree
Folders also provide various isRender and metadata methods that can be overridden to control how they are displayed.
/** @generated */
public static class CommentFolderTreeNode extends FolderTreeNode<Comment> {
...
/** @generated */
public EntityTreeNode addChild(Comment newInstance) {
return new CommentTreeNode(this, newInstance);
}
/** @generated */
public void removeChild2(Comment instance) {
removeChild(instance.getId());
}
}
Configurtion Files
For each entity the following is generated.
- faces-config.xml
<navigation-rule> <navigation-case> <from-outcome>createProject</from-outcome> <to-view-id>/jsf/Project/Edit.xhtml</to-view-id> <redirect/> </navigation-case> <navigation-case> <from-outcome>findProject</from-outcome> <to-view-id>/jsf/Project/Search.xhtml</to-view-id> <redirect/> </navigation-case> <navigation-case> <from-outcome>viewProject</from-outcome> <to-view-id>/jsf/Project/View.xhtml</to-view-id> <redirect/> </navigation-case> <navigation-case> <from-outcome>editProject</from-outcome> <to-view-id>/jsf/Project/Edit.xhtml</to-view-id> <redirect/> </navigation-case> <navigation-case> <from-outcome>projectCharts</from-outcome> <to-view-id>/jsf/Project/Charts.xhtml</to-view-id> <redirect/> </navigation-case> <navigation-case> <from-outcome>excelProject</from-outcome> <to-view-id>/xls/Project/Project.xhtml</to-view-id> </navigation-case> </navigation-rule>
- pages.xml
<page view-id="/jsf/Project/Edit.xhtml" login-required="true">
<begin-conversation join="true"/>
<restrict>#{s:hasRole('User')}</restrict>
<param name="id" value="#{projectEditor.requestedId}"/>
<description>#{messages['Project']} : #{projectTree.master.text}</description>
</page>
<page view-id="/jsf/Project/View.xhtml" login-required="true">
<begin-conversation join="true"/>
<restrict>#{s:hasRole('User')}</restrict>
<param name="id" value="#{projectEditor.requestedId}"/>
<description>#{messages['Project']} : #{projectTree.master.text}</description>
</page>
<page view-id="/jsf/Project/Search.xhtml" login-required="true"/>
<page view-id="/jsf/Project/Charts.xhtml" login-required="true"/>
<page view-id="/pdf/Project/Project.xhtml" login-required="true"/>
<page view-id="/pdf/Project/ProjectList.xhtml" login-required="true"/>
<page view-id="/xls/Project/Project.xhtml" login-required="true"/>
- messages.properties
Project=Project Project_Menu=Project Maintenance ProjectListReportTitle=Project List Report ProjectDetailReportTitle=Project Report ProjectDoc=Projects are used to group tickets. Project_id=Id Project_idDoc=Id - The primary key. Project_name=Name Project_nameDoc=Name - The name of the project. Project_description=Description Project_descriptionDoc=Description - The description of the project. Project_tags=Tags Project_tagsDoc=Tags - @todo add comment for javadoc Project_dependencies=Dependencies Project_dependenciesDoc=Dependencies - @todo add comment for javadoc Project_dependents=Dependents Project_dependentsDoc=Dependents - @todo add comment for javadoc Project_members=Members Project_membersDoc=Members - @todo add comment for javadoc Project_milestones=Milestones Project_milestonesDoc=Milestones - The associated releases.
Facelets
- see anatomy page ???
- search
- view
- edit
- charts
- excel
- pdf list
- pdf detail
- panels
- etc
- messages
- tags & templates (separate page?)
Html input fields are generated based on the data types of the entity attributes.
For more information on types see Types to Java and JSF Mapping
Tests
- seam - redesign similar to pageflow test???
- jpa

