Tag Archives: Spring MVC

Configuring Spring MVC for AJAX to enable SEO

AJAX based web applications always have challenges with Search engines. The fundamental aspect of AJAX is the content bound to AJAX components are not statically bound when the page loads, but are partially rendered as and when a page events occur and loaded in Javascript DOM tree. Search engines like Google and Bing only indexes static contents.

One of our applications has been built using modern web technologies and was completely Ajax based. We extensively used Backbone.js and other AJAX based technologies. And our application was not optimized for Search engines. As our application was built around Spring MVC so as a first step, I wanted to explore if Spring supports SEO. I found a blog from MKYong Spring 3 MVC ContentNegotiatingViewResolver example. We tried this example and it was slightly complicated to setup our application using this technique.

On further research we found an interesting article in Google Webmasters link. As per this link for every request if we pass a “_escaped_fragment_” the Search Engine are capable of indexing these links. In order enable this in a Spring MVC application; we need to use a framework like Phantom.js. Phantom.js has a capability where in it will take all the data in a DOM tree and output HTML content.

As a 1st step create a Phantom.js script as below,

var page = require('webpage').create();
var system = require('system');

// Open the page
page.open(system.args[1], function (status) {
console.log(page.content);
phantom.exit();
});

We need to execute this code from Java and pass the server returned content to this code as below,

public static String contentBuilderHelper(String redirectUrl){
pbCommand.add("phantom-server.js");
pbCommand.add(redirectUrl);
ProcessBuilder builder = new ProcessBuilder(pbCommand);

final Process process = builder.start();
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
output = output + line;
}
return output;
}

Configure Spring MVC component is we need to create a custom filter as below,

public class CrawlerFilter implements Filter{
...
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
if(httpRequest.getParameter("_escaped_fragment_")!=null){
String redirectUrl = httpRequest.getRequestURL().toString()+"#!"+httpRequest.getParameter("_escaped_fragment_");
redirectUrl = URLDecoder.decode(redirectUrl,"UTF-8");
System.out.println(redirectUrl);
try {
//Get content from the web
String content = contentBuilderHelper(redirectUrl);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.write(content);
out.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
chain.doFilter(request, response);
}
...
}

Configuring filter in web.xml

<filter>
<filter-name>GoSmarter Crawler Filter</filter-name>
<filter-class>com.example.filter.CrawlerFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>Crawler Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Finally, we also need to configure Phantom.js framework in Tomcat server.

I hope this blog helped you.

Advertisements

Java Web Development Frameworks: Play 2.0 and Spring MVC

Recently I was exploring alternative Java Web Development frameworks, I stumbled upon Play 2.0, I wanted to compare this against Spring MVC which has a good enterprise development capabilities. In this blog I will discuss few of those points. Though I am biased towards Spring MVC, I still liked Play 2.0 as a potential candidate. Also note, there are some major differences between Play 2.0 and Play 1.0.

Taking advantage of existing knowledge

Play 2.0 support MVC design pattern similar to Spring MVC. Java is the primary language in Play 2.0, it also support for Scala. Lift is another popular framework that support Scala. There is a good comparison of Play 2.0 and Lift here. Java developers can be productive immediately, I was up and running a web application in 15mins.

There is a good article in Stackoverflow comparing Play 2.0 with other non java frameworks like, Grails and  Django on What is pro and contra of using Play Framework?

SBT: Ease of build

Play 2.0 uses SBT (Simple build tool) for build process and Maven for repository management. Maven and Gradle are popular among Java and Spring MVC world for build and repository management.

SBT Support for continuous Integration

As mentioned earlier, Play 2.0 is built on top of SBT. SBT has plugins for Hudson for continuous integration

Netty based Ease of deploy

Play 2.0 is built on top and bundled with Netty web server which is an asynchronous event-driven network application framework. Netty is similar to Node.js, which is popular among people who are looking for high performance web application development. It also supports websocket protocol.  Ideally for deployment, you can use commands like

play dist
play publish

The above commands will zip the whole Play 2.0 application into a zip file and publish the application to Maven repository. In the Production environment, based on the version, you can download and unzip and run the Play 2.0 environment. There are few good articles around this in Stackoverflow, Play Framework Deployment.

The other and not so preferred way is to bundle the play application in a war file and deploy it in a Servlet container like vFabric tc Server. When you do this, you will not exploit the full potential of Play framework like Netty. Play development team will support Servlet containers like vFabric tc Server in their future version, till than there is a article in Stackoverflow which shows how to deploy in Tomcat.

Ease of Unit test

There is a good article on how to do JUnit test in Play 2.0. This will demonstrate testing of all the layers of Play 2.0 like Routes, Controllers and Views. Play 2.0 definitely encourages Test first development. Sonar is a good software quality measuring tool, there are also some configuration for integrating SBT with Sonar.

Support for ORM: Ebean and JPA

Play 2.0 primarily supports EBean and JPA. There are hacks to support iBatis and other lightweight ORMs but it is not officially supported.

JUnit testing REST Services and Spring MVC

For people in hurry get the latest code in Github and run “mvn test”

Introduction

There is a good comparison of Webservices and REST here. REST Services a point-to-point communication over HTTP using XML. There are couple of good discussion around this, in this Stackoverflow article and Toolbox article.

Spring MVC is a good framework to develop REST Services. In this blog I will show you how JUnit test REST Services. I started trying out few of materials around spring-test-mvc and testing Spring MVC REST Services. The GET was working, but the POST was not working. You can see that in the JUnit test in the sample. Next I started trying MockHttpServletRequest/ MockHttpServletResponse there is a good material in Stacktrace on JUnit testing Spring MVC and REST Services. I followed the steps, but it still did not work. It was throwing an exception related to PropertyEditor. There was a good article on how to get this error fixed. Once I put this fix it started working.

Details: JUnit for Spring MVC and REST Services

In this example, we developed a REST Service and Spring MVC, which has 5 operations, getAll, findbyId, create, update, delete and we write JUnit test to call each operations and assert for the expected value.

Based on TDD, we write the test first and for all the 5 methods and assert for expected values as below,

@Test
public void testGetLoanRequest() throws Exception {
LoanRequest loanRequest1 = new LoanRequest();
loanRequest1.setId(2);
ibatisTemplate.insert("GoSmarter.loanRequestInsert", loanRequest1);
requestMock.setMethod("GET");
requestMock.setRequestURI("/loanrequests/2");
handlerAdapter.handle(requestMock, responseMock, loanRequestController);
logger.debug(responseMock.getContentAsString());
LoanRequest loanRequest = mapper.readValue(responseMock.getContentAsString(), LoanRequest.class);
assertNotNull(loanRequest);
assertEquals(loanRequest.getId(), Integer.valueOf(2));
}

@Test
public void testListLoanRequest() throws Exception {
LoanRequest loanRequest1 = new LoanRequest();
ibatisTemplate.insert("GoSmarter.loanRequestInsert", loanRequest1);
loanRequest1 = new LoanRequest();
loanRequest1.setId(2);
ibatisTemplate.insert("GoSmarter.loanRequestInsert", loanRequest1);
requestMock.setMethod("GET");
requestMock.setRequestURI("/loanrequests");
handlerAdapter.handle(requestMock, responseMock, loanRequestController);
List loanRequests = mapper.readValue(responseMock.getContentAsString(), List.class);
assertNotNull(loanRequests);
assertEquals(loanRequests.size(), 2);
}

@Test
public void testCreateLoanRequest() throws Exception {
requestMock.setMethod("POST");
String jsonPcUser = mapper.writeValueAsString(loanRequest);
logger.debug(jsonPcUser);
requestMock.setRequestURI("/loanrequests/create/" + jsonPcUser );
handlerAdapter.handle(requestMock, responseMock, loanRequestController);
logger.debug(responseMock.getContentAsString());
assertEquals(responseMock.getContentAsString(), "true");
}

@Test
public void testListLoanUpdate() throws Exception {
requestMock.setMethod("POST");
LoanRequest loanRequest1 = new LoanRequest();
ibatisTemplate.insert("GoSmarter.loanRequestInsert", loanRequest1);
loanRequest1.setCustomerName("krishna prasad");
String jsonPcUser = mapper.writeValueAsString(loanRequest1);
logger.debug(jsonPcUser);
requestMock.setRequestURI("/loanrequests/update/" + jsonPcUser );
handlerAdapter.handle(requestMock, responseMock, loanRequestController);
logger.debug(responseMock.getContentAsString());
assertEquals(responseMock.getContentAsString(), "true");
LoanRequest loanRequest2 = (LoanRequest) ibatisTemplate.queryForObject(
"GoSmarter.loanRequestDetails", 1);
assertEquals(loanRequest2.getCustomerName(), "krishna prasad");
}

@Test
public void testListLoanDelete() throws Exception {
LoanRequest loanRequest1 = new LoanRequest();
ibatisTemplate.insert("GoSmarter.loanRequestInsert", loanRequest1);
requestMock.setMethod("POST");
requestMock.setRequestURI("/loanrequests/delete/1" );
handlerAdapter.handle(requestMock, responseMock, loanRequestController);
List loanRequests = (List) ibatisTemplate
.queryForList("GoSmarter.loanRequestList");
assertEquals(loanRequests.size(), 0);
}

If you see we need to setup the AnnotationMethodHandlerAdapter and setup MappingJacksonHttpMessageConverter. as below,


@Before
public void setUp() {
requestMock = new MockHttpServletRequest();
requestMock.setContentType(MediaType.APPLICATION_JSON_VALUE);
requestMock.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, Boolean.FALSE);

responseMock = new MockHttpServletResponse();

handlerAdapter = new AnnotationMethodHandlerAdapter();
HttpMessageConverter[] messageConverters = {new MappingJacksonHttpMessageConverter()};
handlerAdapter.setMessageConverters(messageConverters);

mapper = new ObjectMapper();
loanRequest = new LoanRequest();
}

Once we do the setup, we need to add a InitBinder method in the controller as below,

public class LoanRequestController {
@InitBinder
public void initBinder(WebDataBinder b) {
b.registerCustomEditor(LoanRequest.class, new LoanRequestEditor());
}
}

public class LoanRequestEditor extends PropertyEditorSupport {
ObjectMapper mapper = new ObjectMapper();

@Override
public void setAsText(String text) throws IllegalArgumentException {
LoanRequest obj = mapper.readValue(text, LoanRequest.class);
setValue(obj);
}

@Override
public String getAsText() {
return getValue().toString();
}
}

Conclusion

In the above example, we used MockHttpServletRequest and MockHttpServletResponse to do Junit test of REST Services and Spring MVC.

I hope it helped.

Responsive Web Design using Twitter Bootstrap, Spring MVC

Introduction

Responsive Web Design is a new way of building web application. Once you build a application using Responsive Web Design, you will be easily able to make this web application work on any device including Mobile and Handheld devices. Twitter the company open sourced their Twitter Bootstrap framework which supports Responsive Web Design(RWD). Kickstrap is another variant of Twitter Bootstrap. In this blog, I will demonstrate how we will build a Spring MVC based application that uses jquery-tmpl to build a JSON based RWD.

The use case we cover is a simple Airline reservation system, where in for a given origin, destination, the start and end date, we return all the flights. When we select a flight, we show all the deals in the target location.

For people in hurry get the code and the steps from @ Github.

Responsive Web Design

There are 3 key technical features that are heart of Responsive Web design:

Flexible grid-based layout: When you are viewing the page on a mobile devices, when you change the device to landscape or portrait, the page layout automatically adjusts and flows to display within the layout, this is Flexible grid-based layout. In Twitter Bootstrap, it can be achieved using CSS tags as below,

<div class="row-fluid"><!-- put some HTML stuff --></div>

Flexible images: dynamic resizing of images

Media queries: This is CSS3 feature, where in the CSS is returned to the Browser based on querying the Media device. Typical HTML tag you use for this as below,

<!-- for iPad, this is how the media query looks like -->
<link rel="stylesheet" media="all and (orientation:portrait)" href="portrait.css">
<link rel="stylesheet" media="all and (orientation:landscape)" href="landscape.css">

Spring MVC and Twitter Bootstrap

The overall data flow is as below,

Responsive Web Design using Twitter Bootstrap, Spring MVC

Responsive Web Design using Twitter Bootstrap, Spring MVC

In this example we build a Single Page Website using Twitter Bootstrap and JQuery-tmpl. On the frontend side, the data is submitted as below,


$('#searchResults').click(function(){
var origin =  $("#origin option:selected").val();
var destination = $("#destination option:selected").val();
var startDate= $("#startDate").val();
var endDate = $("#endDate").val();

$.get("resources/datatemplates/flightList.html", function (template){
$.get("/air/searchResultsJson?leavingFrom=" + origin + "&goingTo=" + destination + "&startDate=" + startDate + "&endDate=" + endDate, function (data){
$("#dataRegion").html("");
$.tmpl(template, data).appendTo("#dataRegion");
});
});
return false;
}

This executes a JQuery and gets the list of flights in the form as JSon objects.

The JQuery-tmpl plugin is used to bind the flightList.html to achieve Single Page Webpage design. The flightList.html looks as below,

<tr>
<td>${startTime}</td>
<td>${startAirport}</td>
<td>${endTime}</td>
<td><a href="#" onclick="return getDetails('${endAirport}')">${endAirport}</a></td>
</tr>

On the Spring MVC side, we need to add the Maven dependency and call the method, refer this link for more details.

The controller code looks as below,

@RequestMapping(value = "searchResultsJson", method = RequestMethod.GET)
public @ResponseBody
List searchResultsJson(@RequestParam String leavingFrom,
@RequestParam String goingTo, @RequestParam String startDate,
@RequestParam String endDate) {
Form form = new Form();

form.setOrigin(leavingFrom);
form.setDestination(goingTo);
form.setStartDate(startDate);
form.setReturnDate(endDate);

return locationService.selectFlights(form);
}

In the above example @ResponseBody help in returning the JSon response to the client.

Conclusion

In this blog I demonstrated, how we can build a web application, that can be adapted to work on any device. It also show how to return JSon response from a Spring MVC based web application.

I hope it helps you.

JUnit testing Spring Security, Spring MVC and Activiti BPM

Spring Test MVC is a good framework for JUnit testing Spring MVC based application. In this sample, we demonstrated a simple LoanRequest application where in we test the Spring Security, Spring MVC and Activiti BPM Spring Support.

For people in hurry,

  • Get the latest Spring Test MVC code base
  • run “mvn clean install” on the above codebase
  • Get the loanrequest codebase latest code from @ Github
  • At the command prompt type “mvn test”, if the tests are successful, you are all set.

Details: JUnit testing Spring Security, Spring MVC and Activiti BPM

Spring Test MVC is an awesome framework to test the Spring MVC backend code. Spring Test MVC framework has a built in Spring MVC web container. You can point it to the webapp folder, and the spring application context and it will configure the buildin container to work with the JUnit test. You can also configure the Spring Security as below,

//You need to define a class as below,
class WebContextLoader extends GenericWebContextLoader {
public WebContextLoader() {
super("src/main/webapp", false);
}
}

//In your test case you need to configure the WebContexLoader as below with the Spring context files
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, value = {
"classpath:META-INF/spring/applicationContext-activiti.xml",
"classpath:META-INF/spring/applicationContext-security.xml",
"classpath:META-INF/spring/applicationContext.xml",
"classpath:META-INF/spring/test-datasource-config.xml"})
public class LoanRequestControllerTest {

//this FilterChainProxy configures the Spring Security feature
@Autowired
private FilterChainProxy springSecurityFilterChain;

@Autowired
private WebApplicationContext wac;

private MockMvc mockMvc;

@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webApplicationContextSetup(this.wac)
.addFilters(this.springSecurityFilterChain).build();
}

The API syntax is simple and expresses technical functionality closely, for example if the LoanRequest usecase is as below,

  • The accountant logged in and submitted the LoanRequest. The Server will return the HTTP Status as ok, and you can also assert if the Submitting went thru
  • Once the Accountant submitted the LoanRequest, the Management can review it and approve the Loan request

The above flow can be expressed by the below code using the TDD methodology,


@Test
public void testApprove() throws Exception {
mockMvc.perform(
post("/loanrequests/create").param("id", "4")
.param("customerName", "krishna").param("amount", "26")
.with(userDeatilsService("fozzie")))
.andExpect(status().isOk()).andExpect(redirectedUrl("/list"))
.andExpect(model().attribute("loanRequests", any(List.class)));

mockMvc.perform(
get("/loanrequestsapproval/approve/4").with(userDeatilsService("kermit")))
.andExpect(status().isOk());
}

If you notice, the http actions like Post and Get are demonstrated here. Also it has demonstrated how the user logging happens.

This container is configured to work with Spring Security, for example, if an accountant logs into to approval page, it will be forbidden. We write the test using TDD as below,


@Test
public void testApproveForbidden() throws Exception {
mockMvc.perform(
get("/loanrequestsapproval/approve/2").with(userDeatilsService("fozzie")))
.andExpect(status().isForbidden());
}

This sample also demonstrates the capability of Activiti BPM and how it can participate in JUnit testing.

You can configure the inmemory Activity Process Engine Configuration along with the deployed Bpmns as below,

<!-- <bean id="processEngineConfiguration"> -->
<bean id="processEngineConfiguration">
<property name="dataSource" ref="dataSource-activiti" />
<property name="databaseSchemaUpdate" value="true" />
<property name="transactionManager" ref="transactionManager" />
<property name="jpaHandleTransaction" value="false" />
<property name="jpaCloseEntityManager" value="false" />
<property name="jobExecutorActivate" value="false" />
<property name="deploymentResources" value="classpath*:/org/activiti/spring/test/usertask/LoanProcess.bpmn20.xml" />
</bean>

In the JUnit test TDD you can autowire this as below and populate some sample users,


@Autowired
SpringProcessEngineConfiguration processEngineConfiguration;

@Before
public void setup() {
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();

IdentityService identityService = processEngine.getIdentityService();
User user = identityService.newUser("fozzie");
user.setPassword("fozzie");
identityService.saveUser(user);
user = identityService.newUser("kermit");
user.setPassword("kermit");
identityService.saveUser(user);

identityService.saveGroup(identityService.newGroup("accountancy"));
identityService.saveGroup(identityService.newGroup("management"));

identityService.createMembership("fozzie", "accountancy");
identityService.createMembership("kermit", "management");
}

In your controller, you can autowire the inmemory Activity Process Engine Configuration and start a process, approve a task and mark it as complete. For more details refer the LoanRequestController code.

Conclusion

In this example, I have demonstrated a Activiti BPM based workflow initiated from a Spring MVC container and goes thru a lifecycle. I also demonstrated how Spring Security is used to authorize the Activiti BPM users.

I hope this example helps.

Measuring navigation flow JUnit test in Spring Web flow

Currently I am exploring Spring Web flow. Someone has already written a utility for measuring how well you are testing the Spring Web Flow web application navigation flow using JUnit. In this blog I am extending this utility to work with Sonar. The working project is here and Sonar plugin is here @ Github.

For people in hurry,

  • Get the latest code
  • Build the Spring Web Flow Test Sonar plugin using “mvn package”
  • Deploy sonar plugin jar located in the target folder into Sonar extension location and Start sonar
  • Run “mvn test sonar:sonar” on Spring Web Flow Coverage project
  • Finally you can go to Sonar and see the report as below

Problem Statement: Measuring Spring Web flow test coverage

In any web application there will be a page navigation flow. Frameworks like Spring Web flow, can externalize the page navigation as well as session management between each page in the navigation. In the below example, there is a simple usecase, where in we are presented with a food menu. Based on the menu presented, we can drill down further to findout more or we can cancel if we are not interested.

Details

Based on the usecase there are overall 13 distinct navigation paths in the Spring Web Flow. We can simulate all these paths by first writing the JUnit test and use the library to keep track of all the paths tested. In our test case only 7 paths are covered, out of 13. The sonar report is as below,

Spring WebFlow flow coverage

Utility to measure the navigation flow covered in a web application

We will adopt the Test first approach, by building the JUnit test to test the flow as per this blog.  The psedocode is as below,

private XMLFlowCoveragePathListenerAdapter listener = new XMLFlowCoveragePathListenerAdapter("test-flow");

public void testStartFlow() {
XMLFlowCoveragePathListenerAdapter.setTestName("testStartFlow");
XMLFlowCoveragePathListenerAdapter.iniliatizePath();

//test and assert the flow
}

public void testNextItemInFlow() {
XMLFlowCoveragePathListenerAdapter.setTestName("testNextItemInFlow");
XMLFlowCoveragePathListenerAdapter.iniliatizePath();

//test and assert the flow
}

public void testSecondItemInFlow() {
XMLFlowCoveragePathListenerAdapter.setTestName("testSecondItemInFlow");
XMLFlowCoveragePathListenerAdapter.iniliatizePath();

//test and assert the flow

//FINALLY END THE COVERAGE
XMLFlowCoveragePathListenerAdapter.endCoverage();
}

Conclusion

This utility is in a pre-alpha state. Currently it only show information on how many navigation flows it has covered against the total number as a percentage. There are few enhancements we need to take care of. For example, we need to provide a drilldown on what are the navigation flow that is not covered. We also need to build a threshold, where if it is less than that, it should break the maven build. Ofcourse, we need to cleanup the code.

I feel even in its present form this utility is useful. Feel free to use it.

JUnit testing with Spring Webflow

Spring Webflow sample

I love SpringSource related technologies because of the supporting classes they provide for Test Driven Development (TDD). I do TDD for 2 reasons,

  • I design the objects using spring webflow application from nothing, writing the tests first and evolving the code efficiently to support the tests and making the test success and finally the building the application
  • In the process, I am building a regression testing framework, a “safety net”. The advantage of this is when we change the backend, I always make sure the tests I have build will surely succeed and in the process the design matures

All the spring frameworks Spring WebFlow being no exception do a good job of this. In this blog I will show you how I do TDD for a simple Spring WebFlow. Those who are new to Spring WebFlow, it is a framework that support data/session management between pages, when you navigate in a workflow within a user session. For example, in the below example, I will talk about the hotel booking application where there is a submit booking, proceed/confirm or cancel booking(2 step webflow). Spring WebFlow helps in managing the session between these 2 steps (pages) without you worrying about session/request data management.

Luckily someone has already built this simple hotel booking application. The way I would build this application is, I will start off with a test as below,

public class BookingFlowExecutionTests extends AbstractXmlFlowExecutionTests {

private BookingService bookingService;

protected void setUp() {
bookingService = EasyMock.createMock(BookingService.class);
}

@Override
protected FlowDefinitionResource getResource(
FlowDefinitionResourceFactory resourceFactory) {
return resourceFactory
.createFileResource("src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml");
}

@Override
protected void configureFlowBuilderContext(
MockFlowBuilderContext builderContext) {
builderContext.registerBean("bookingService", bookingService);
}

public void testStartBookingFlow() {
Booking booking = createTestBooking();

EasyMock.expect(bookingService.createBooking(1L, "keith")).andReturn(
booking);

EasyMock.replay(bookingService);

MutableAttributeMap input = new LocalAttributeMap();
input.put("hotelId", "1");
MockExternalContext context = new MockExternalContext();
context.setCurrentUser("keith");
startFlow(input, context);

assertCurrentStateEquals("enterBookingDetails");
assertResponseWrittenEquals("enterBookingDetails", context);
assertTrue(getRequiredFlowAttribute("booking") instanceof Booking);

EasyMock.verify(bookingService);
}

public void testEnterBookingDetails_Proceed() {
setCurrentState("enterBookingDetails");
getFlowScope().put("booking", createTestBooking());

MockExternalContext context = new MockExternalContext();
context.setEventId("proceed");
resumeFlow(context);

assertCurrentStateEquals("reviewBooking");
assertResponseWrittenEquals("reviewBooking", context);
}

public void testReviewBooking_Confirm() {
setCurrentState("reviewBooking");
getFlowScope().put("booking", createTestBooking());
MockExternalContext context = new MockExternalContext();
context.setEventId("confirm");
resumeFlow(context);
assertFlowExecutionEnded();
assertFlowExecutionOutcomeEquals("bookingConfirmed");
}

private Booking createTestBooking() {
Hotel hotel = new Hotel();
hotel.setId(1L);
hotel.setName("Jameson Inn");
User user = new User("keith", "pass", "Keith Donald");
Booking booking = new Booking(hotel, user);
return booking;
}
}

AbstractXmlFlowExecutionTests is an interesting class as a part of spring-webflow.jar which provides all the plumbing for testing the flow and asserting the next steps within a spring webflow. Every test method that tests the flow will start with a “testXXX” method name. To begin with, there are no flow configuration file “src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml”. The test will obviously will fail. As a first step, we need to define the spring flow as below,

<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="checkinDate" />
<binding property="checkoutDate" />
<binding property="beds" />
<binding property="smoking" />
<binding property="creditCard" />
<binding property="creditCardName" />
<binding property="creditCardExpiryMonth" />
<binding property="creditCardExpiryYear" />
<binding property="amenities" />
</binder>
<on-render>
<render fragments="body" />
</on-render>
<transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="cancel" bind="false" />
</view-state>

<view-state id="reviewBooking" model="booking">
<on-render>
<render fragments="body" />
</on-render>
<transition on="confirm" to="bookingConfirmed">
<evaluate expression="bookingService.persistBooking(booking)" />
</transition>
<transition on="revise" to="enterBookingDetails" />
<transition on="cancel" to="cancel" />
</view-state>

<end-state id="bookingConfirmed"/>

<end-state id="cancel" />

Once you add this configuration in “src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml” file, and run the test again, the test will succeed.

If you notice carefully, transition on=”confirm” will call the business logic on the service layer, in this example, it will call “bookingService.persistBooking(booking)” business logic.

You also see that there are different controller methods like, enterBookingDetails and reviewBooking. These are associated with different view jsp file. But right now they are mocked and we just assert the controllers as in setCurrentState(“enterBookingDetailsreviewBooking”); and assertCurrentStateEquals(“enterBookingDetailsreviewBooking”); in JUnit test.

Now if we have to build views, we will have 2 views jsps, and configure them in view.xml as below,

<definition name="enterBookingDetails" extends="standardLayout">
<put-attribute name="body" value="/WEB-INF/hotels/booking/enterBookingDetails.jsp"/>
</definition>

<definition name="reviewBooking" extends="standardLayout">
<put-attribute name="body" value="/WEB-INF/hotels/booking/reviewBooking.jsp" />
</definition>

You can see the jsp pages in the location as mentioned above, but in the JSP file the key thing that contribute to the webflow is adding,

<form:form modelAttribute="booking" action="${flowExecutionUrl}">

The flowExecutionUrl spring webflow expression language variable will control the flow to next page. There are several such variable within Spring Webflow, for more details click here.

If you want to run this example, you just need to do

mvn tomcat:run

Other References: