Chapter 2. Implementation with Spring

Table of Contents

2.1. Initializing the project
2.2. Writing some code
2.3. Writing View Pages
2.4. Listing issues
2.5. Handling Issues
2.6. Managing Persistence Contexts
2.6.1. PC Scope Issues
2.6.2. Practical PC Management
2.7. Multi-Page Flows
2.8. Lookup Management
2.9. Creating Entities
2.10. Exception Handling
2.11. Extras

2.1. Initializing the project

The Spring application is being developed in a clean version of Eclipse with the Spring IDE plug-ins installed. I’m using Spring 2.5.4 and Web Flow 2.0, and most of the required libraries are coming from the spring dependencies.

To Start with, I’ll create a new plain old vanilla dynamic web application. I could add in the JPA or Faces facets, but then I have to start configuring the libraries in Eclipse and it’s just easier to do it manually since I’ll be adding all my other libraries manually. I added the elements to web.xml to configure faces, facelets, spring, and spring web flow. As I deploy it I need to add libraries for the features, so I gradually add the 30 or so needed libraries. I then add the persistence.xml file to the classpath, and my log4j.xml config file. I configure the applicationContext.xml to include three other config files, one for JPA and one for Spring Web Flow, and the other for my actual beans. I’m using MySQL for the database and using JPA in both examples. It can take anywhere from 15 minutes to an hour to get the application deployed and running depending on experience and how much trial and error you employ in finding out which jars you need. Having been through this a number of times before, it took me closer to 15 since I just copied over most of the required files.

Once I have the application created, and I can deploy and run it, I create the model, namely the Project , Issue and IssueStatus classes with the JPA annotations. For simplicity, I am using the create-drop JPA functions to create the database schema and also using import.sql to insert some test data.

2.2. Writing some code

We also have a projectDao interface that contains the methods for dealing with Project objects.

Example 2.1.  ProjectDao interface

					
public interface ProjectDao {

	Project findProject(Long projectId);
	Project refresh(Project project);
	List<Project> findProjects();
	void save(Project project);	
}

				

This is implemented in a class called ProjectDaoBean

Example 2.2.  ProjectDaoBean implementation

					
public class ProjectDaoBean implements ProjectDao,Serializable {

	@PersistenceContext
	private transient EntityManager entityManager;
	
	@Transactional
	public Project findProject(Long projectId) {
		return entityManager.find(Project.class, projectId);
	}

	@Transactional
	public List<Project> findProjects() {
		return entityManager.createQuery("select p from Project p").getResultList();
	}

	@Transactional
	public Project refresh(Project project) {
		entityManager.refresh(project);
		return project;
	}

	@Transactional
	public void save(Project project) {
		entityManager.persist(project);		
	}

}
				

This is really simple code, and shouldn’t need too much explanation. The entity manager is defined as transient since spring web flow will complain about the entity manager not being serializable as it tries to serialize the bean. We add the projectDao bean to the Spring Bean configuration.

Example 2.3. Spring Bean Definition

					
<bean name="projectDao" class="org.issuetracker.dao.ProjectDaoBean"/>
				

2.3. Writing View Pages

Once our application is up and running, it is time to start creating some pages. For reasons that will become clear later on, we will put our project list in its own page flow. We create a new folder WEB-INF\WebContent\flows\projects\ and in it we add a new file called projects.xml which will contain our new web flow.

Example 2.4.  projects.xml flow definition.

					
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/webflow
        http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
	start-state="projects">

	<view-state id="projects">
		<on-render>
			<evaluate expression="projectDao.findProjects()"
				result="flowScope.projects" result-type="dataModel" />
		</on-render>

		<transition on="edit" to="projectEdit" />
		<transition on="view" to="projectView" />

	</view-state>

	<subflow-state id="projectEdit" subflow="projectEdit">
		<input name="projectId" value="projects.selectedRow.id" />
		<transition on="cancel" to="projects"/>
		<transition on="save" to="projects"/>
	</subflow-state>
	
	<subflow-state id="projectView" subflow="projectView">
		<input name="projectId" value="projects.selectedRow.id" />
		<transition on="close" to="projects"/>		
	</subflow-state>	

</flow>
				

SWF lets you customize the names for flows, pages and the url used to invoke it, but it uses sensible defaults such as the state name. For now, I've used this default. The start view is the view state called projects , which calls findProjects() on my stateless bean when the page is rendered, and wraps the result in a JSF data model and puts it in a flowScope variable. The flow scope is a scope which lasts for the life of the flow, and projects is the name of the variable. Note that we can now refer to this variable as #{projects} since all scopes will be searched until the variable is found. We have two transitions from the projects page, edit and view which takes us to the edit and view states which are defined as subflows. We could define the edit/view process as part of this projects flow, but the problem with that is we cannot re-use those flows from other places if we do that. Since we want to provide direct access to the view and edit pages via a URL, we have chosen to put them in separate flows.

In each subflow, we define an input variable called projectId which is the Id of the selected project. Since we use a dataModel as the source of data for the table, we get clickable tables where the selected row in the data model is set based on the item clicked in the table that caused the postback.

Example 2.5.  projects.xhtml page

					
<h:messages globalOnly="true" styleClass="message" />

<h:form>

    <h:dataTable value="#{projects}" var="v_project">
	    <h:column>
		    <f:facet name="header">ID</f:facet>
		    <h:outputText value="#{v_project.id}" />
	    </h:column>

	    <h:column>
            <f:facet name="header">Title</f:facet>
            <h:commandLink value="#{v_project.title}" action="view"/>
            (<h:commandLink value="Edit" action="edit"/>)
	    </h:column>
    </h:dataTable>

</h:form>
				

Figure 2.1. Project listing using Spring

Project listing using Spring

We reference the variable #{projects} which refers to the projects variable we set up in the on-render stage in the page flow. We display the title of the project as a link which returns the action view and we have an edit link which returns the edit action. These actions mean nothing in the page itself, they only have meaning in the faces navigation, or in our case, the Spring flow. Looking at the flow, an edit or view action transitions to the subflows to edit or view the project and they have the project Id passed in to them. With that in mind, lets look at the project edit subflow.

Example 2.6.  projectEdit.xml subflow.


					
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/webflow
        http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
	start-state="projectEdit">

	<persistence-context />
	<input name="projectId" value="requestScope.projectId" />

	<on-start>
		<evaluate expression="projectDao.findProject(projectId)"
			result="flowScope.project">
		</evaluate>
	</on-start>

	<view-state id="projectEdit">

		<transition on="save" to="save">
			<evaluate expression="projectDao.save(project)" />
		</transition>
		<transition on="cancel" to="cancel" />
	</view-state>

	<end-state id="save" view="externalRedirect:/projects" commit="true"/>
	<end-state id="cancel" view="externalRedirect:/projects" />


</flow>
				

The persistence-context element at the start lets us use the same entity manager for this flow. This means we can load, modify and save the entity in this flow and it will use the same entity manager so we don't have to worry about the entity becoming detached. The input element declares projectId as an input value. The projectId variable can be passed in from a parent flow, but if it is not, then the value is assigned from the request parameter called projectId . This means if we enter the project edit page via a URL as opposed to a parent flow, we can pass in the project Id as a parameter and it will be assigned to the projectId variable. This gives us two ways to enter a page but a singular way to get the projectId in the flow that we use to get the project to edit. The on-start element is evaluated when we first start the flow. Here, we call the findProject() method on the project Dao and pass in the projectId value. The result, which is the project we will be editing, is put into the project variable in the flow scope. In our project edit page, we can reference the #{project} variable and it will receive the value of our project variable defined in the web flow.

Example 2.7.  projectEdit.xhtml page

					
<h:messages globalOnly="true" styleClass="message" />

<h:form>

	<h:panelGrid columns="2">
		Project Id : <h:outputText value="#{project.id}" />
		Title : <h:inputText value="#{project.title}" />
	</h:panelGrid>
	<h:commandButton action="save" value="Save" />
	<h:commandButton action="cancel" value="Cancel" />

</h:form>
				

Again, we have save and cancel actions which only have meaning if we look back at our flow. The save transition calls the expression projectDao.save(project) which is used to save the project to the database. It then transitions to the save end-state node which has a commit attribute set to true so the entity manager commits any changes. The view for the end-state is set to redirect to a view outside of the flow. The cancel transition is virtually identical except that it doesn’t call save changes or commit changes at the end state. Note that we might run this flow from a parent flow, or this flow might run as a top level flow. While this is useful from the perspective of having re-usable code, it may cause problems in determining when the right time to commit the changes are. If this subflow is part of a larger process, we don't want to commit right away, but if it is also used as a process on its own, then there may be good reasons to perform the commit here.

If we go to the projects page, we can click on the edit link, and it will go to the project edit page. Clicking cancel or save returns us to the project list page. If we enter the URL /projectEdit?projectId=2 , it will edit the second project. If we click save or cancel, the flow will end and we will be redirected back to the projects list page. One problem we do have is returning messages regarding the success or failure of our actions. The faces messages mechanism has been abstracted through Spring Web Flow, although it can still be used directly. Faces messages do not survive a redirect which both Seam and Spring work around, however Spring does not propagate faces messages outside of a flow since it uses a flash scope. Let’s add a simple message handler by using a message writer bean which is defined as follows :

Example 2.8.  MessageWriter.java helper class

					
public class MessageWriter {

	public void info(MessageContext ctx,String message) {
		ctx.addMessage(new MessageBuilder().info().defaultText(message).build());
	}
	
	public void error(MessageContext ctx,String message) {
		ctx.addMessage(new MessageBuilder().error().defaultText(message).build());
	}
	
	public void savedChanges(MessageContext ctx) {
		info(ctx, "Saved Changes");
	}
	
	public void cancelledChanges(MessageContext ctx) {
		info(ctx, "Changes cancelled");
	}
}
				

This is a really simple class to put messages into the flow. The MessageContext object that is passed in is the instance of the message context used by spring and is an abstraction of the faces message object. We add method calls to the flow to generate messages in response to certain actions.

Example 2.9.  Adding expressions to generate messages on transitions.

					
<view-state id="projectEdit">

	<transition on="save" to="save">
		<evaluate expression="projectDao.save(project)" />
		<evaluate expression="messageWriter.savedChanges(messageContext)"/>
	</transition>
	<transition on="cancel" to="cancel">
	  <evaluate expression="messageWriter.cancelledChanges(messageContext)"/>
	</transition>
</view-state>
				

When we save or cancel the project, the message for the user is pushed into the message context for display on the next page. However, as stated earlier, the messages do not survive the end of a flow which means that those messages will not be displayed if you enter the projectEdit page as a top level workflow.

Figure 2.2. Message after editing the project

Message after editing the project

Now let’s add a flow for viewing the project, everything is almost the same, we use the same mechanism to get the projectId and load the project. The only real difference is that we have a transition to edit the project from the view page.

Example 2.10.  projectView.xml web flow.

					
<input name="projectId" value="requestScope.projectId" />

<on-start>
	<evaluate expression="projectDao.findProject(projectId)"
			result="flowScope.project">
	</evaluate>
</on-start>

<view-state id="projectView">

	<transition on="close" to="close" />
	<transition on="edit" to="edit"/>
</view-state>

<subflow-state id="edit" subflow="projectEdit">
	<input name="projectId" value="project.id" />
	<transition on="save" to="projectView"/>
	<transition on="cancel" to="projectView"/>
</subflow-state>

<end-state id="close" view="externalRedirect:/projects" />
				

From the view page, we can call the edit subflow to invoke the same edit subflow which brings re-use to our flows. Again, we just need to pass in the projectId value like we did from the projects page. Also, since we invoke the editor as a subflow, when we return, we return to the view flow which means it will display any messages that were generated from the edit page.

Figure 2.3.  Message displayed after cancelling changes

Message displayed after cancelling changes

2.4. Listing issues

Now let’s consider the project issues. We want to display the issues for each project on the view page in a kind of master detail fashion. For now, we will take the easy route and just connect the data table to the #{project.issues} value.

Example 2.11.  Listing issues in projects.xhtml

					
<h:dataTable value="#{project.issues}" var="v_issue">
	<h:column>
		<h:outputText value="#{v_issue.id}" />
	</h:column>

	<h:column>
		<h:commandLink value="#{v_issue.title}" action="view" />
		(<h:commandLink value="edit" action="edit" />)
	</h:column>
</h:dataTable>

If we open this page as-is, we will get an error because of Lazy Initialization of the issues on the project object. The reason for this is because the entity is detached at the point of rendering the page. In order to fix this, we simply add the persistence-context element at the top of our project view flow. If we re-load the page, it works,however we have introduced another problem. If you click the edit button from the project view page, make changes and save it, you are returned to the project view page, the saved changes message appears but…the project that we edited has not changed. Click the close button and go back to the projects page. You should see the changes in the list of projects, so the problem was local to the project view page.

The problem is that we used the same persistence context in the view flow. The project entity was loaded when we first went to the project view page. We edited the project, and returned to the project view page. When we started the view flow, we searched for the project entity based on the project Id and put it into flowScope. When we return from the project edit page, that value is still in flow scope and will be re-used. To get around this, we would need to refresh the project entity if the return value from the editor subflow is save .

Example 2.12. Refreshing the entity in a flow.

					
<subflow-state id="edit" subflow="projectEdit">
		<input name="projectId" value="project.id" />

		<transition on="save" to="projectView">
			<evaluate expression="projectDao.refresh(project)" />
		</transition>

		<transition on="cancel" to="projectView" />
	</subflow-state>
				

This isn’t ideal, since you also have problems with regards to refreshing the page. If you display the project view page while changing the project data in either another window or directly in the database, the same values are displayed in the refreshed project view page. To me, this is wrong as in general, in an entity view, each refresh should literally refresh the data. I’m sure there is a way around this using web flow that I just haven’t come across. In theory, I should be able to put the evaluation expression in an on-render tag in the project view page so the item is refreshed on each page refresh. However, when I tried this, I got errors since web flow had problems with knowing about or seeing the projectId value.

2.5. Handling Issues

Now let’s look at the view and edit pages for the issues. They are pretty much the same as the view and edit pages for the projects, except the names of parameters and variables, so I won’t reproduce the code here.

However, we do need to consider one issue. If we are to display a list of issues for a project in the project view page and allow the selection of that project, we need to wrap it in a dataModel, which isn't done currently since we are using #{project.issues} to get the list of issues. We have three choices here. The first is to simply use a GET request passing the issueId as a parameter. However, request would take us out of any workflows we might be in. Alternatively, we could wrap the current list of issues in a data model.

Example 2.13.  Putting the list of issues into a flow as a dataModel.

					
<evaluate expression="project.issues" 
    result="flowScope.issues" result-type="dataModel">
				

Adding this to the start of the projectView flow wraps the list of issues from the project attribute in a data model. Alternatively, we can create a separate function which will allow us to get a list of issues independently from the project entity. This is probably a better solution since a project may end up with thousands of issues which we will want to paginate without having to load them all. However, using this solution changes a number of things for us as we will see. First off, we’ll implement an bean that has an IssueDao interface.

Example 2.14.  IssueDao interface.

					
public interface IssueDao {
    public List<Issue> findIssuesForProject(Long projectId);
    public Issue findIssue(Long issueId);
    public void refresh(Issue issue);
    public void save(Issue issue);	
}
				

I won’t include the implementation since it is fairly straightforward, but we call it IssueDaoBean and add it as a spring bean called IssueDao .

At the top of our projectView flow, we add a call to find the issues for a project and put the results into a flow scoped data model called issues .

Example 2.15.  Putting the issues into a flow based on query results.

					
<evaluate expression="issueDao.findIssuesForProject(projectId)"
		result="flowScope.issues" result-type="dataModel">
</evaluate>
				

In our web page, where the dataTable was using #{project.issues} , we change it to just #{issues} .

Example 2.16.  Using our new issues variable in the data table.

					
<h:dataTable value="#{issues}" var="v_issue">
				

Now that we independently fetch the list of issues, we can technically remove the persistence-context element. If you recall, we added this because we wanted to fetch the list of issues in the rendering phase and didn't want to cause a Lazy Initialization Exception. Since we know we have already fetched the data we need at the start of the flow, we no longer need it. However, if we need to display the status entity of the issue, we would need the same PC to be available on the rendering phase assuming the status is fetched lazily. To solve this, we have two options. Either keep the persistence context for the duration of the flow, or explicitly load the status entities when we get the list of issues in the query which is a better option. However, for now, let's keep the persistence context for the duration of the flow. There are issues associated with unnecessarily keeping a persistence context which we'll see in the next section.

2.6. Managing Persistence Contexts

In Spring Web Flow, Persistence Contexts (PCs) can last the duration of the flow, or for the duration of the request. It is possible to have a PC scoped to last for a conversation but this is not yet implemented. Whether you use the long or short PC scopes, they both introduce different problems. Note that some of these same problems apply to Seam also where a PC can be conversational or non-conversational depending on whether a long running conversation is active or not.

2.6.1. PC Scope Issues

Short PC scopes can result in detached entities that need re-attaching when persisting changes. The problem with this is that you lose optimistic locking since the version you started out with could be different than the version you ultimately save with if another user has modified the entity.

While it may seem tempting to always use a longer PC scope, such as one scoped to the flow, there are different problems associated with it. For example, in a view page with a flow scoped PC, when you refresh the page, an attempt is made to refresh the data you are viewing. However, because you are using a flow scoped PC, the same instance of the data is returned from the PC. This means that you would manually need to trigger a refresh of the data by calling the refresh method on the persistence context.

2.6.2. Practical PC Management

So, now we have our issue edit/view pages up and running, let's look through a couple of scenarios that we might encounter. We can remove the persistence-context element from the issue view flow since we don't need to keep our PC around. If we try and edit the issue and save it, when we get back to the issueView flow, we run into a problem. When the outcome was save , we refreshed the issue entity to get the latest version using the variable in the flow.

Example 2.17.  Refreshing the entity on a save action.

						
<subflow-state id="edit" subflow="issueEdit">
		<input name="issueId" value="issue.id" />
		<transition on="save" to="issueView">
			<evaluate expression="issueDao.refresh(issue)"/>
		</transition>
		<transition on="cancel" to="issueView" />
	</subflow-state>
					

Because we are not using the same persistence context for the duration of the flow, we get an 'Entity not managed' exception because we are refreshing the entity against a different PC. We can solve this a few ways. One is to change the call to refresh to a call to find the issue.

Example 2.18.  Refreshing the issue by re-loading it as a new instance.

						
<evaluate expression="issueDao.findIssue(issue.id)" result="flowScope.issue">
					

This calls the find method using the current PC and puts the value in the issue variable in the flow scope. Another way is to write a smarter refresh method in the Dao. We can't just call find() since we will get the stale version back, so we check if it is in the PC and if so, we refresh it. Otherwise we call the find() method to get an instance back.

Example 2.19. Refreshing / loading the entity.

						
public Issue refresh(Issue issue) {
		if (entityManager.contains(issue)) {
			entityManager.refresh(issue);
			return issue;			
		} else {
			return findIssue(issue.getId());
		}
	}
					

This way, if the issue is not part of the current PC, then it loads a new one using the ID of the old one. If it is a part of the PC, then it simply refreshes it. The returned value is pushed into the flow scope issue variable.

Example 2.20.  Refreshing the entity and putting it into the flow

						
<evaluate expression="issueDao.refresh(issue)" result="flowScope.issue">
					

The advantage of this method is that it will work whether you are using flow scoped persistence contexts or not and you can change between the two without requiring any additional changes (toggling between the find() and refresh() methods on the issueDao ).

Incidentally, this kind of feature can also be used in the projectView page where we list the issues. When the user views or edits an issue we need to refresh the issue in the list on the project page since it may have changed (the user may click view issue, then edit the issue, and then end up back to the project view page). To ensure that we refresh the issue, we can add an on-exit element to the issueView and issueEdit states so that we can refresh the item in the issue list.

Example 2.21.  Flow controlled refresh of the entity on the on-exit phase in projectView flow.

						
<subflow-state id="issueView" subflow="issueView">
		<input name="issueId" value="issues.selectedRow.id" />
		<transition on="save" to="projectView" />
		<transition on="close" to="projectView" />
		<on-exit>
			<evaluate
				expression="issueDao.refresh(issues.selectedRow)">
			</evaluate>
		</on-exit>
		
	</subflow-state>
					

This works great, when the user views an issue and we come back to the project view flow, just that one selected issue is refreshed.

One nice aspect of Spring Web Flow is the amount of control you can have over the data in your flows, the scope of it, and the scope of the persistence contexts. Also, everything is local to the flow itself, the data is declared in the flow, and inserted into a scope according to the flow definition. This means your flows can be strongly decoupled from each other, and also from your code since most code is stateless. State is handled by declaring stateful variables in the flow itself.

I can imagine situations where this will result in problems though if careful variable naming is not used. The fact that variables can be declared as flow scoped as opposed to conversationally scoped will reduce these incidents if users can limit the scope to the flow

2.7. Multi-Page Flows

Let's look at one more scenario that is a little more complex. Let's say that as part of editing an issue we want to select a different project for the issue. For this, we can re-use the project list page, but this time the list will have a button to select the project for the issue we are editing. When a project is selected, we should come straight back to the issue editing page, and the project should be selected as the issue's project. It is feasable that such a listing page would be used in a number of places throughout the application making the re-use of this page important. We don't want any hard coding or hacks in the page to indicate where to navigate to next, we want to use the page flow functions to control our navigation. The other aspect of this problem is that we need to pass the selected project back to the issue edit flow.To start with, we'll add a selectProject transition to our issueEdit flow.

Example 2.22.  Transition to navigate to the project selection page.

					
<view-state id="issueEdit">
…
…
<transition on="selectProject" to="selectProject" />
…
…
</view-state>
				

This transitions to our select project sub flow which is the projects flow we created at the beginning. Again, the goal here is to re-use elements we have already built. This flow state is also responsible for any changes to our issue entity due to the project selection.

Example 2.23.  The project selection state in the edit issue flow.

					
<subflow-state id="selectProject" subflow="projects">
	<input name="doSelect" value="true" type="java.lang.Boolean" />
	<transition on="selectProject" to="issueEdit">
		<set name="issue.project"
			value="currentEvent.selectedProject">
		</set>
	</transition>
	<transition on="selectCancel" to="issueEdit" />

</subflow-state>
				

Caution

As of Web Flow 2.0.1, currentEvent.selectedProject should be replaced by currentEvent.attributes.selectedProject due to changes in how SWF accesses the attributes in the current event.

What we are doing is calling the projects flow and pushing a variable called doSelect in and setting it to true . If the subflow returns selectProject then we expect the subflow to pass out a value called selectedProject which we assign to the issue.project value.

In our projects.xhtml page, we add a new column to the list of projects to contain the button to select a project. This column should only be rendered if the doSelect flag is set to true. This flag is only set to true when we are calling the flow from another flow for the purpose of project selection.

Example 2.24.  projects.xhtml column containing a link for selecting a project

					
<h:column rendered="#{doSelect == true}">
	<f:facet name="header">Select</f:facet>
	<h:commandLink value="Select" action="selectProject"/>
</h:column>
				

At the bottom of the table we add another button to cancel the selection if the user decides not to change the project.Again, rendering this button depends on the doSelect flag being set.

Example 2.25.  projects.xhtml button for cancelling project selection.

					
<h:commandButton rendered="#{doSelect == true}" value="Cancel" action="selectCancel"/>
				

The select and cancel buttons returns the actions selectProject and selectCancel respectively. These actions are handled by the projects page flow. As a part of selection process, we need to pass the selected project out of the flow and into the parent flow. We do this using a new end-state which outputs the selected row. We also have an end state for the cancel action which does not output the selected project.

Example 2.26.  End states in projects.xml flow for selecting or cancelling project selection.

					
<end-state id="selectProject">
	<output name="selectedProject" value="projects.selectedRow" />
</end-state>
<end-state id="selectCancel" />
				

If the user cancels the selection, then we just go to the end state, but if they click select, then we pass back the projects.selectedRow value in a variable called selectedProject .

What is really great about this is I can start any number of flows and they will manage themselves correctly. I can navigate the pages in any order by editing and viewing projects or issues, changing the project for an issue but editing the project before selecting it. As I save and close my way back down the flow stack everything works perfectly. My data is isolated between different conversation and I don't worry about overwriting values.

2.8. Lookup Management

Unfortunately, there is no provision for using a drop down in Jsf without resorting to manually handling selectItems and taking responsibility for mapping the selected item to a scoped list within the flow.

2.9. Creating Entities

Let's look at the task of creating a new project and a new issue. For this, we need to determine that the project or issue Id is blank, and thus create a new one. I found this to be a somewhat thorny problem to a degree. The most obvious way was to use the findProject(projectId) to return a project, or a new project if projectId is null or 0. Note that this is a quick solution and you may want to use an alternative mechanism for production. Especially since security authorization will be involved in most cases to determine whether the user can view or create the item in question.

Example 2.27.  Code in projectDao to find or create a project.

					
public Project find(Long projectId) {
	if (projectId == null || projectId == 0) {
		return new Project();
	} else {
		return entityManager.find(Project.class, projectId);
	}
}
				

Now we just add a "new" button on the project list and make it call the sub flow for editing the project

Example 2.28.  Transition in projects.xml for creating a new project.

					
<view-state id="projects">
…
…
…
	<transition on="new" to="projectNew" />
…
…
…
</view-state>

<subflow-state id="projectNew" subflow="projectEdit">		
	<transition on="cancel" to="projects" />
	<transition on="save" to="projects" />
</subflow-state>
				

Note that we do not pass a projectId in this time. If we run this, we can click new, edit a new project and save it. Note also that this fits in with our existing project edit workflow without needing any changes.

I can view an existing project, edit one of the issues, and decide to change the project. Rather than pick an existing project, I can create a new project, save it, and then select it as the project for the issue I'm editing. Also, I could cancel the changes to my issue and the project will still be saved. The beauty of it is that since the flows are self contained, data is isolated and I don't have to worry about variable names overlapping or the flows becoming intertwined. It just works without any changes to the other flows.

For issues, we have a tougher challenge. If the issue Id is null, we cannot just create a new issue, we need to have a project to attach it to. We add a new method to the issueDao called findOrCreateIssue into which we pass the issueId and any projectId we have.

Example 2.29.  issueDaoBean.java method to find or create an issue.

					
public Issue findOrCreateIssue(Long issueId, Long projectId) {
	if (issueId == null || issueId == 0) {
		if (projectId == null || projectId == 0) {
			//error!
			return null;
		} else {
			Issue issue = new Issue();
			issue.setProject(entityManager.find(Project.class, projectId));
			return issue;
		}
		
	} else {
		return findIssue(issueId);
	}		
}
				

This should take care of creating a new issue or project when we don't pass an Id in. There is the question of security and exception handling which I'll get to later.

For now, let's continue with the problem of adding issues. In the project view page, we add a button to add a new issue to the project.

Example 2.30.  Button in projectView.xhtml to create a new issue.

					
<h:commandButton action="issueNew" value="New Issue" />
				

In the projectview flow, we add a transition to the view for creating new issues.

Example 2.31.  Transition in projectView.xml flow to create a new issue.

					
<transition on="issueNew" to="issueNew" />
				

This goes to a subflow for editing issues. As part of this subflow, we pass in the value project.id as the ID of the project we want the new issue for.

Example 2.32.  Subflow state for creating a new issue for the project

					

<subflow-state id="issueNew" subflow="issueEdit">
	<input name="projectId" value="project.id" />
	<transition on="save" to="projectView" />
	<transition on="cancel" to="projectView" />
	<on-exit>
		<evaluate
		expression="issueDao.findIssuesForProject(project.id)"
		result="flowScope.issues" result-type="dataModel">
		</evaluate>
	</on-exit>

</subflow-state>
				

When we return and exit that subflow, we refresh the list of issues for the project. Again, the level of de-coupling between the two flows is excellent, we pass in what we need to and we check for the responses we expect.

At this point, it looks like we’ve completed the demo application. While the example is not that complex, it should give you an idea of how to write a stateful CRUD application with Spring and Spring Web Flow.

2.10. Exception Handling

In our application, when an error is reached, we should be throwing an exception which should then be caught and our flow redirected, and/or an error message displayed. In particular I’m thinking of the cases where we want to load an issue or project, and there is no Id, or the item for that Id does not exist anymore. There may also be cases where the user may be creating a new item when they don't have security rights to do so. There exists mechanisms for handling exceptions which lets us write exception handling classes to handle the exception and if possible add messages to be displayed on the current view.

However, we call these functions on the start of the flow at which point, the view state is not available to the flow, and the faces context is not available, therefore if we do find an exception, there isn’t much we can do about it. We could make our findOrCreateIssue() call in the on-render phase of a state, but we have problems there since it cannot see the projectId or issueId values properly. It seems like these values have a very short scope.

Overall, the exception handling seems limited (almost non-existent) without writing custom exception handlers for the view. Again, this is done through the bean mechanism so there is plenty of chance to write handlers that uses lists of re-usable handlers to process exceptions. While this does offer a flexible system, it would be nice to see some default in there for error handling, even if it is just staying on the page, and pushing the exception message into the messageContext. I think though that there are more complications involved in that though. The help documents and examples don’t cover exception handling very well, but I’m sure we’ll see more on the topic from Spring.

2.11. Extras

Spring Web Flow does come with a number of ajax related JSF components that can allow you to limit the areas that are re-rendered on submission which is quite nice. It also provides some decorations for existing DOM nodes that can apply client side validation which is also nice. JSF has a number of existing Ajax frameworks out there that can achieve some of this, however they may be considered heavyweight and loaded with unneeded components, so Spring's Ajax controls offer a lighter alternative if you wanted to apply a simple ajax solution to your application.

Spring Security can also be used with flows and you can apply authentication at the flow, view and even transition levels.

Flows also have an inheritance mechanism which allows you to let one flow inherit from another. While the inheritance is a little less flexible than object inheritance, this is still a great feature for some of those views that are often used. This could easily make up for some of those cases where the data factory methods needs to be repeated in similar flows. For example, the project view and edit flows both need to grab an instance of an object based on the project Id parameter.

IDE support is missing in some areas, although mostly in the non-Spring areas. The JSF page editor is missing the ability to auto complete code for beans, and there are places where I expected auto-completion in the flow editors, but didn’t get it. The web flow editor in this version was still thinking in terms of Web Flow 1.0, although it was still able to come up with diagrams for my flows. Auto completion for the Spring elements worked great, from specifying beans to writing flows and auto-completing the list of states available to transition to. For me, not having auto complete in the JSF editor is a pain.

Spring Web Flow offers some flexibility with the Persistence Context. The Persistence-Context element at the start of flows lets us define whether the PC is event, flow or conversation scoped. Conversation scoped is not currently implemented. This could cause some problems if you are pushing entities from one flow to another since they could have different PCs and the entity would be detached in the new flow.

Overall, this is a great framework. Version 2.0 included a heavy re-write to make it work much more amicably with JSF and it shows. The flow language is clean and straightforward, although it does tend towards some repetition. Having default transitions might be a nice addition to both ease repetition and also avert disaster in the event of changes to a subflow which returns an unexpected value to a parent flow. It is great how easily the flows can be written so you can take a navigation path that is an endless circle, and still come back out the way you came in without data getting overwritten. The only other criticism is that it does make a mess of your URLs by adding large flow state values in there. As a newly re-written framework, there are plenty of places where the documentation is lacking, especially for newcomers.

The data management and scoping is really nice, the localization of the definition to the flows is great, even if it is at the expense of defining things multiple times for similar flows. The IoC and Dependency Injection pieces of this solution probably need no introduction as they are provided by the Spring core.


http://www.andygibson.net/