Login | Register   
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Simplify Your Web App Development Using the Spring MVC Framework : Page 5

Struts is in fairly widespread use in the Java world, but the Spring MVC framework promises to provide a simpler alternative to Struts for separating presentation layer and business logic. Learn how to build a simple stock trading Web application using Spring's MVC framework.


advertisement
The Trade Wizard
Often times we have more than one screen that a user interacts with in order to complete a given task. This sequence of screens is often called a "wizard." The last MVC component I will introduce you to is the AbstractWizardFormController. This controller allows you to carry the same command object through an entire flow of Web pages, thus allowing you to break up the presentation into multiple stages that act on the same model.

For the example, I'll create a wizard that allows a user to trade stock. This wizard will start with a page that takes the order and then goes to a confirmation page where the user will choose to either execute or cancel the order. If the order is cancelled, the user will be returned to the portfolio page, if the order is executed the user is sent to an acknowledgement page to tell them that their order was filled.

First I need a way to get to the trade page, so I'll put a link to the trade page on the portfolio page.



/WEB-INF/jsp/portfolio.jsp <br> <a href="<core:url value="trade.htm"/>">Make a trade</a><br/> <a href="<core:url value="logon.htm"/>">Log out</a> <br> </body> </html>

Now I'll update the tradingapp-servlet.xml so that it knows what to do when the user clicks on the trade.htm link.

/WEB-INF/tradingapp-servlet.xml <bean id="tradeForm" class="com.devx.tradingapp.web. TradeFormController"> <constructor-arg index="0"> <ref bean="portfolio"/> </constructor-arg> </bean> <!-- you can have more than one handler defined --> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/portfolio.htm"> <ref bean="portfolioController" /> </entry> <entry key="/logon.htm"> <ref bean="logonForm"/> </entry> <entry key="/trade.htm"> <ref bean="tradeForm"/> </entry> </map> </property> </bean>

The TradeFormController should exist as /WEB-INF/src/com/devx/tradingapp/web/TradeFormController.java. But there is a lot to the TradeFormController, so I'll explain it in segments.

public class TradeFormController extends AbstractWizardFormController { private Portfolio portfolio; public TradeFormController(Portfolio portfolio) { this.portfolio = portfolio; setPages(new String[] { "trade", "trade-confirm" }); setCommandName("trade"); }

The TradeFormController extends AbstractWizardFormController. I pass in a Portfolio object so that the trade form knows what my buying limit is and can add and remove stock from the portfolio. The setPages method in the constructor tells the form the sequence of pages we are going to call.

The strings in this array relate to views that are resolved by the ViewResolver just like in the other controllers. These pages are indexed by Spring starting at 0. Thus the trade view is index 0 and the trade-confirm view is index 1. The indexes exist because we may have an event that causes us to skip or go back to a previous page. The setCommandName method sets the name of the command object that we are going to use for this form. This object will be created in the formBackingObject method.

protected Object formBackingObject(HttpServletRequest request) { Trade trade = new Trade(); trade.setBuySell(Trade.BUY); return trade; }

This method will create the command object and set it in its initial state. This method is called before the user is directed to the first page of the wizard. Subsequent submits will call several methods on the abstract controller. The main methods that are called are onBind, validatePage, and getTargetPage.

onBind protected void onBind(HttpServletRequest request, Object command, BindException errors) { Trade trade = (Trade) command; if (symbolIsInvalid(trade.getSymbol())) { errors.rejectValue("symbol", "error.trade.invalid-symbol", new Object[] { trade.getSymbol() }, "Invalid ticker symbol."); } else { Quote quote = null; try { quote = new QuoteFactory().getQuote(trade.getSymbol()); } catch (QuoteException e) { throw new RuntimeException(e); } trade.setPrice(quote.getValue()); trade.setSymbol(trade.getSymbol().toUpperCase()); } }

The onBind method is called before any validation occurs for each submit. I override the onBind method in order to get a quote on the symbol the user is trying to purchase and set the price and symbol on the command object. Keep in mind that even if onBind adds errors to the BindException, the validatePage method will still be called.

protected void validatePage(Object command, Errors errors, int page) { Trade trade = (Trade) command; if (tradeIsBuy(trade)) { if (insufficientFunds(trade)) { errors.reject("error.trade.insufficient-funds", "Insufficient funds."); } } else if (tradeIsSell(trade)) { if (portfolio.contains(trade.getSymbol()) == false) { errors.rejectValue("symbol", "error.trade.dont-own", "You don't own this stock."); } else if (notEnoughShares(trade)) { errors.rejectValue("quantity", "error.trade.not-enough-shares", "Not enough shares."); } } }

validatePage
The validatePage method is called after the onBind method. It acts in the same way as the validator did in the logon controller. The validatePage method is passed the page number for the page in the flow that you are validating. This can be used if you need to do custom validation for each page in the flow. If you wanted to, you could create validator objects for each page and use the validatePage method to delegate to the appropriate validator based on the page index that was passed in.

getTargetPage
The getTargetPage method will be called in order to find out which page to navigate to. This method can be overridden in your controller, but I use the default implementation, which looks for a request parameter starting with "_target" and ending with a number (e.g. "_target1"). The JSP pages should provide these request parameters so that the wizard knows which page to go to even when the user uses the Web-browser's back button.

The processFinish method is called if there is a submit that validates and contains the "_finish" request parameter. The processCancel method is called if there is a submit that contains the "_cancel" request parameter.

protected ModelAndView processFinish(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) { Trade trade = (Trade) command; if (trade.isBuySell() == Trade.BUY) { portfolio.buyStock(trade.getSymbol(), trade.getShares(), trade .getPrice()); } else { portfolio.sellStock(trade.getSymbol(), trade.getShares(), trade .getPrice()); } return new ModelAndView("trade-acknowledge", "trade", trade); } protected ModelAndView processCancel(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) { return new ModelAndView(new RedirectView("portfolio.htm")); }

Listing 7 is the code for the Trade class (our command for this form). And now all you need are the JSP pages (see Listing 8 for the three JSP pages).

As you can see in the code, the Spring-specific request parameters are specified in the submit button name attribute. If all goes well, you should be able to pull up the first page of the trade wizard by going to http://localhost:8080/tradingapp/trade.htm (see Figure 5).

Figure 5. Ready to Trade: The first page of the trade wizard allows you place an order for a stock trade.

In summary, here is the flow through this form:

  1. The user goes to the trade.htm page.
  2. He is routed to the dispatcher servlet (because all .htm pages are directed to the dispatcher servlet).
  3. The DispatcherServlet loads the tradingapp-servlet.xml file and routes the user to the TradeFormController.
  4. The TradeFormController loads the command bean by calling the formBackingObject method and routes the user to the first page defined in the setPages call in the constructor (trade.jsp).
  5. The user fills out the form and submits it.
  6. The user is directed back to the controller, which parses the target page off of the request parameters from trade.jsp, binds and validates the command object, and forwards the user to the next page in the wizard.
  7. If the validator fails, the user is sent back to the form view and error messages are displayed (back to trade.jsp).
  8. If the validator doesn't fail, the controller forwards to the trade-confirm.jsp page.
  9. The user submits an execute or cancel command, which will in turn tell the controller to execute either the processFinish or processCancel commands, respectively.
  10. The user is forwarded to the page that the processFinish or processCancel command sends them to.

I've gone over three different types of controllers provided by Spring, which should be enough to get you started on your own Web projects. The Spring framework contains quite a few more features than those I have gone over in this article and its predecessor. I have found Spring to be a very valuable tool in my software development toolbox, and I urge you to discover its vast utility for yourself.



Javid Jamae consults for Valtech, a global consulting group specializing in delivering advanced technology solutions. Valtech endeavors to help its customers through its global delivery model to create and/or maintain an affordable competitive advantage.
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap