WebWork's Supported Views - JSP
JavaServer Pages (JSP) is an important view technology that WebWork (WW) supports. This section provides you the building blocks you need to use JSP as a view technology.
Reference Pages
Also check out the WebWork 2 UI Tag Guide.
Background
So what is JSP? From a technical perspective, JSP is "a page created by the web developer that includes JSP technology-specific tags, declarations, and possibly scriptlets, in combination with other static (HTML or XML) tags. A JSP page has the extension .jsp; this signals to the web server that the JSP engine will process elements on this page." See JSP for more info.
In addition, JSP "provides a simplified, fast way to create web pages that display dynamically-generated content. The JSP specification, developed through an industry-wide initiative led by Sun Microsystems, defines the interaction between the server and the JSP page, and describes the format and syntax of the page." See JSP for more info.
So you create your first JSP and deploy it to your web server. The first time a user requests your page, your servlet runner such as Tomcat will compile the JSP into a servlet class and use this class to render your web page to the user. So, if the end result of this work is a servlet, why not just write a servlet? Because servlets don't offer you an easy means of abstracting web presentation from web content. In the early days, people developed web sites exclusively with servlets. A developer would use the servlet to dynamically output HTML code. The typical way of doing this was to write out HTML code - out.write("<HTML>..);. This approach became a nightmare to maintain and costly to develop since it required a Java developer.
When JSP technology arrived, developers began using this new technology for their presentation. The early JSP specification had limited support for custom tags so early adopters would write Java scriptlets and JavaScript to create dynamic sites. Mixing Java and HTML was less than ideal because the Java code itself is within tags. This makes the JSP difficult to read and comprehend. This approach does work and may be fine for simple sites but it is less than adequate for complex sites. Complex sites require the ability to abstract the design and its logic to a form that is manageable and maintainable.
JSP has since matured and now provides rich support for custom tags. Developers are now writing libraries of custom tags to perform simple and complex tasks. These custom tags abstract functionality in a simple semantic form that is consistent with HTML or XML. With well-written JSP tags, web developers can develop and maintain complex web sites with NO Java code in their JSP.
WebWork's JSP tags
Writing custom tags can at first be a daunting task but no worries, WW provides an extensive JSP tag library to help you develop web sites from simple to complex. This tag library is grouped into Non-UI Tags and UI Tags. The major difference between the two is that UI tags have an implied JSP template associated with them that will render HTML form controls or a set of HTML tags to produce a composite output such as a table. These templates are just defaults and will probably be all you need for your development needs. But if you need more, WW gives you the ability to override the default and insert your own template. In addition, you can group templates together under a directory and logically refer to the directory as a theme.
Themes give you the ability to skin your web site. A skin doesn't just mean different graphics, colors, etc. but it can mean different views for different browsing technologies. For instance, you might have a theme for WAP, HTML, and DHTML.
Non-UI tags provide support for control flow, internationalization, WW's Actions, iterators, text, JavaBeans and more.
A more exchaustive description of the tags can be found in the appendix on the WW taglib: Non-UI Tags, UI Tags.
An example
The best way to introduce how to use JSP as a view technology with WW is to walk through a simple but complete example. The example we will examine in detail is Webshop app which you can find on the index.jsp page if you deployed WW's examples. WW provides many examples to give you a better understanding of its features and capabilities. Webshop app is a good example to review because it utilizes an array of WW features.
Before we jump into the details of the example, lets look at the application's use case. Listed below is a simple normal flow of a shopper purchasing a CD.
- Webshop app use case flow User selects desired language.
- User is presented with a list of CDs. This list is an aggregate of album, artist, and price for each CD.
- User adds CD to their shopping cart by selecting desired CD and quantity.
- User continues to add or remove CDs from their shopping cart.
- User checks out when they want to make a purchase and the shopping cart contents are displayed to the user.
- Use can shop again if desired.
Step 1 - User selects desired language
So lets begin walking through this application. When you select this application from the example index page, its link points to webwork/i18n/index.jsp. Upon examining this page, we learn that it forwards the user to an action - i18n.Language.action. By default, WW maps URIs with *.action pattern to servlet ServletDispatcher. This class is responsible to retrieve an appropriate Action class and execute it. If the result of the execution is assigned a view (JSP), then the user will be forwarded to this view.
ServletDispatcher's task of determining an appropriate action begins with the URI of the request. In our case, the dispatcher is passed the URI i18n.Language.action. This URI maps directly to an action class named Language in the webwork.action.test.i18n package. Notice that our URI was not webwork.action.test.i18n.Language.action as it actually is packaged but just i18n.Language.action. The reason is that we redefined webwork.action.packages property in our webwork.properties file with the line shown below. This override allows us to drop webwork.action.test prefix from any action reference.
webwork.action.packages=webwork.action.test, webwork.action.standard
Now lets examine action Language. The first thing we discover is that it extends Shop which extends ActionSupport. This class is a base class that you will extend most of the time. This class gives you access to a set of useful functionality that we will see used later. In addition, it provides the default flow of execution. Actions by default will have their execute method called when an URI or alias maps to it. When execute is called on ActionSupport, it determines if the URI was suffixed with a command such as !foo. foo in this example is the command you want executed. This command will be translated into a doFoo() method call that ActionSupport will call on your action. This allows you to call specific methods on your action.
Now lets examine action Language. The first thing we discover is that it extends Shop which extends ActionSupport. This class is a base class that you will extend most of the time. This class gives you access to a set of useful functionality that we will see used later. In addition, it provides the default flow of execution. Actions by default will have their execute method called when an URI or alias maps to it. When execute is called on ActionSupport, it determines if the URI was suffixed with a command such as !foo. foo in this example is the command you want executed. This command will be translated into a doFoo() method call that ActionSupport will call on your action. This allows you to call specific methods on your action.
Our URL was not a command, so ActionSupport will first call doValidate() method if you provide one. This allows you to perform validation before execution begins. Since we don't have this method, ActionSupport then calls our doExecute(). For our example, Language checks to see if you have set a desired language. Since we have not, it returns ERROR. The servlet dispatcher will now check the view mappings for i18n.Language.error. Looking at views.properties located in WEB-INF/classes, we find a section of mappings for our application shown below. For our error, we see that the alias maps to langauge.jsp. So the dispatcher will forward the user to that page.
Webshop view mapping:
# Webshop (I18N example adaptation) i18n.Shop.success=shop.jsp i18n.Add.success=shop.jsp i18n.Delete.success=shop.jsp i18n.Checkout.success=checkout.jsp i18n.Language.success=shop.jsp i18n.Language.error=language.jsp i18n.Restart.success=shop.jsp i18n.Cart.success=cart.jsp i18n.CDList.success=cdlist.jsp
So what really happens when my action is fetched? By default, WW is setup with DefaultActionFactory as your ActionFactory. This factory chains together other factories that get called trying to fulfill the request of retrieving the desired action.
Take for example our Language action. The servlet dispatcher will call DefaultActionFactory and ask it to return the appropriate action. The factory then places this request on the chain. Once on the chain, each factory will either ignore the request and pass it up the chain, do some processing on the action and pass it up the chain, and/or return the action. Listed below is the chain of factory proxies that comprises DefaultActionFactory.
- ParametersActionFactoryProxy - This proxy will call action setters for matching parameters.
- PrepareActionFactoryProxy - This proxy will call prepare() method on action. This will allow actions to do any preparation work needed before validation or execution.
- ContextActionFactoryProxy - This proxy will set action's context. Actions can implement *Aware interfaces that will tell this proxy what to set on your action.
- CommandActionFactoryProxy - This proxy will strip the command from the URI and set the command on the action.
- AliasingActionFactoryProxy - This proxy will retrieve the appropriate action name if the URI was an alias.
- JspActionFactoryProxy - This proxy will wrap a JSP and make it an action.
- PrefixActionFactoryProxy - This proxy will use the prefixes setup in the properties file to find the appropriate action class.
- XMLActionFactoryProxy - This proxy will retrieve the an XML action if required.
- ScriptActionFactoryProxy - This proxy will wrap a JavaScript and execute it.
- JavaActionFactory - This proxy will return the appropriate Java action object.
As you can see, a lot is happening under the hood of DefaultActionFactory. So lets return back to our example beginning on language.jsp where we are to select our desired langauge. On this page, the user is presented with a radio list of languages. Lets take a closer look at the code to render this control.
<webwork:action name="'i18n.LanguageList'"> <ui:radio label="'Language'" name="'language'" list="languages"/> </webwork:action>
First notice the non-UI action tag. This tag will execute the action LanguageList. On execution, this class will load a locale-to-language mapping and save it to its attribute languages. The tag will then place this action object on the ValueStack so its body can reference it.
Now look at the UI tag radio. This tag will create an HTML INPUT control of type radio. The name of the control will be language, the label for the control will be Language. The values to display in the control will come from a method getLanguages() called on LanguageList action. The tag then iterates over the map using the keys for the VALUE parameter of the INPUT tag and the values for the displayed text.
The user now selects their desired language and submits the form. The form is submitted to Language again. This time a request parameter language is sent with the request. When the appropriate action is fetched again, the ParametersActionFactoryProxy will call all setter methods on the action for the request parameters passed in. This means action Language will have its setLanguage() method called. Now, when doExecute() is called we discover that the user has selected a language, which we will use to create the appropriate locale and place it in the user's session.
session.put("locale", locale);
Notice how easy it was to put information into the session. But where did this attribute come from? Remember that our action extends Shop and this class implements SessionAware. Because it is SessionAware it will have its setSession() method called providing the action with a map of the user's session. Now that we placed the locale in the session, we return SUCCESS. The dispatcher will look up the alias i18n.Language.success and see it maps to shop.jsp. The user will now be forwarded to this page to begin shopping.
Step 2 - User is presented with a list of CDs
In this step the user has already selected their language and their locale is in their session. They are now on shop.jsp. Examining the code on this page we see several uses of a non UI tag text. This tag provides the ability to fetch strings from resource bundles. The tag will recognize our locale and use the appropriate resource bundle to retrieve the correct language text.
<webwork:text name="'main.title'"/>
We also notice that this page includes another page i18n.CDList.action. The non UI tag include will invoke action CDList and upon success will include view cdlist.jsp in the shop.jsp. Action CDList will load a list of CDs from a text file into a list. After the CDs are loaded we now turn our attention to cdlist.jsp
CD:<webwork:include page="i18n.CDList.action" />
cdlist.jsp will provide a HTML SELECT form control. In this example, we build an HTML control instead of using WW's UI tag select. We use WW's non UI tag iterator to build the select control. This tag allows us to iterate over the cd list. So where did the cd list come from? Remember, that we called action CDList beforehand. After the action was called, WW placed this action on the ValueStack for reference by the view. So, WW will look for a method named getCDList() which it will find on action CDList. This method returns a list that the iterator tag will iterate over. Each iteration will output a HTML OPTION tag. Also notice the non UI tag property. For each member in the list, the property tag is retrieving album, artist, and country from the CD. Also notice that the last property is using action ComputePrice to determine the appropriate price for the CD. The semantic @pricer/computePrice(price) means use the object defined by parameter pricer and call its method getComputePrice passing in the CD's price as a parameter. Pretty cool.
<webwork:action name="'webwork.action.test.i18n.ComputePrice'" id="pricer"/> <select name="album"> <webwork:iterator value="CDList"> <option value="<webwork:property value="album"/>"> <webwork:property value="album"/>, <webwork:property value="artist"/>, <webwork:property value="country"/>, <webwork:property value="@pricer/computePrice(price)"/> </option> </webwork:iterator> </select>
Now back to shop.jsp. Examining the code further reveals another interesting aspect. Look at the UI tag textfield's label attribute shown below. Notice the attribute's value is a method call text('main.qtyLabel'). This method will retrieve an appropriate string from our resource bundle. But what action has a getText() method? Remember that action Shop extends ActionSupport. This action provides this method.
<ui:textfield label="text('main.qtyLabel')" name="'quantity'" value="1" size="3"/>
The last thing of interest about JSP shop.jsp is it includes the users shopping cart at the end.
<webwork:include value="'cart.jsp'" />
When cart.jsp (shown below) is included it retrieves user's cart items shown in the code below. The attribute value of value translates into a couple of method calls getCart()/getItems(). The first method is called on action Shop which returns a Cart and then the next method is called on Cart. If the first method call does not retrieve a cart, Shop will create one with no items and place it in the user's session.
<webwork:property value="cart/items"> <webwork:if test="."> <center> <table border="0" cellpadding="0" width="100%" bgcolor='<webwork:text name="'cart.bgcolor'"/>'> <tr> <td><b><webwork:text name="'cd.albumLabel'"/></b></td> <td><b><webwork:text name="'cd.artistLabel'"/></b></td> <td><b><webwork:text name="'cd.countryLabel'"/></b></td> <td><b><webwork:text name="'cd.priceLabel'"/></b></td> <td><b><webwork:text name="'cd.quantityLabel'"/></b></td> <td></td> </tr> <webwork:action name="'i18n.ComputePrice'" id="pricer"/> <webwork:iterator> <tr> <webwork:property value="cd"> <td><b><webwork:property value="album"/></b></td> <td><b><webwork:property value="artist"/></b></td> <td><b><webwork:property value="country"/></b></td> <td><b><webwork:property value="@pricer/computePrice(price)"/></b></td> </webwork:property> <td><b><webwork:property value="quantity"/></b></td> <td> <form action="i18n.Delete.action" method="post"> <input type=submit value='<webwork:text name="'cart.delLabel'"/>'> <input type=hidden name="album" value='<webwork:property value="cd/album"/>'> </form> </td> </tr> </webwork:iterator> </table> <p> <p> <form action="i18n.Checkout.action" method="post"> <input type="submit" value='<webwork:text name="'cart.checkoutLabel'"/>'> </form> </center> </webwork:if> </webwork:property>
Step 3 - User adds CD to their shopping cart
Now the user sees a list of CDs specific to their locale. The user selects a CD and enters a quantity and submits the form. The form is submitted to i18n.Add.action which maps to action Add. Again, parameters posted will be set on this action. For our scenario, this will be quantity and album. Then the action's doExecute() method is called and it retrieve's the user's shopping cart and adds the cd to it. The action then returns SUCCESS which maps to view shop.jsp again. This time the user's shopping cart has contents so it will be displayed below the cd list.
Step 4 - User continues to add or remove CDs
At this point, the user repeats the procedure of adding to and removing from their shopping cart. When they are done shopping, they then checkout.
Step 5 - User checks out
At this stage, the user is done shopping and has decided to checkout. The post of this form is to i18n.Checkout.action. This maps to aciton Checkout. This action will determine the total cost of the contents in the user's shopping cart and sets its attribute totalPrice. The action returns SUCCESS which maps to to view checkout.jsp. This view will display the user's shopping cart contents along with its total price.
Step 6 - User can shop again if desired.
At this stage, the user has checked out and if they want they can begin shopping again. If they choose to start again, they select the HREF which points to i18n.Restart.action. This action will remove the user's shopping cart and return SUCCESS which will map to view shop.jsp to start this process all over again.
Summary
Hopefully, this example gave you a good start at understanding what a JSP might look like using WW's features. This example was not meant to be an exhaustive discovery of WW but just an introduction. There are other examples you can walk through on your own to provide additional insight into WW. In addition, you will want to read the sections referenced below for more specific documentation covering WW's features.