Taylor

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.

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.

	/** @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.

	/** @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

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

Screen Shots

Enlarge
Enlarge
Enlarge
Enlarge
Enlarge
Enlarge