Raible's Wiki

Raible Designs
Wiki Home
News
Recent Changes

AppFuse

Homepage
  - Korean
  - Chinese
  - Italian
  - Japanese

QuickStart Guide
  - Chinese
  - French
  - German
  - Italian
  - Korean
  - Portuguese
  - Spanish
  - Japanese

User Guide
  - Korean
  - Chinese

Tutorials
  - Chinese
  - German
  - Italian
  - Korean
  - Portuguese
  - Spanish

FAQ
  - Korean

Latest Downloads

Other Applications

Struts Resume
Security Example
Struts Menu

Set your name in
UserPreferences


Referenced by
Articles
Articles_cn
Articles_pt
Articles_zh
CreateActions
CreateActions_pt
CreateActions_zh
SpringControllerUnit...
SpringControllers_ko
ValidationAndListSpr...
...and 1 more




JSPWiki v2.2.33

[RSS]


Hide Menu

SpringControllers


Difference between version 33 and version 32:

At line 6 changed 1 line.
This tutorial will show you how to create a Spring Controller and JSP. It'll also demonstrate writing a JUnit Test to test the Controller. The Controller we create will talk to the PersonManager we created in the [Creating Managers|CreateManager] tutorial. This tutorial will simplify everything - we will not actually be rendering any data or making the UI look pretty. The [next tutorial|ConfiguringTiles] will show you how to integrate your new JSPs into your webapp.
This tutorial will show you how to create Spring Controllers and JSPs. It'll also demonstrate writing JUnit Tests to test your Controllers. The Controller we create will talk to the PersonManager we created in the [Creating Managers|CreateManager] tutorial.
At line 13 changed 2 lines.
* [1] Create a skeleton JSP using XDoclet
* [2] Create PersonFormControllerTest to test our Controller
* [1] Create skeleton JSPs using XDoclet
* [2] Create PersonFormControllerTest to test PersonFormController
At line 16 changed 1 line.
* [4] Run the PersonFormControllerTest
* [4] Run PersonFormControllerTest
* [5] Clean up the JSP to make it presentable
* [6] Create Canoo WebTests to test browser-like actions
At line 19 changed 1 line.
In this step, we'll generate a ''skeleton'' or our JSP for displaying information from the Person object. I say ''skeleton'' because it'll just be the <form> itself. It will contain table rows with Spring's "bind" tags for each property in Person.java. The tool that we use to do this was written by [Erik Hatcher|http://www.blogscene.org/erik/]. It's basically just a single class (FormTagsHandler.java) and a couple of XDoclet templates (FormKeys.xdt and Form_jsp.xdt). All these files are located in extras/viewgen.
In this step, you'll generate a ''skeleton'' JSP to display information from the Person object. I say ''skeleton'' because it'll just be the <form> itself. It will contain table rows with Spring's "bind" tags for each property in Person.java. The tool that we use to do this was written by [Erik Hatcher|http://www.blogscene.org/erik/]. It's basically just a single class (FormTagsHandler.java) and a couple of XDoclet templates (FormKeys.xdt and Form_jsp.xdt). All these files are located in extras/viewgen.
At line 23 removed 4 lines.
%%(border: 1px solid silver; background: #eee; padding: 5px; padding-left: 10px; border-left: 5px solid maroon;)
__WARNING:__ There is [a bug|https://appfuse.dev.java.net/issues/show_bug.cgi?id=32] in AppFuse 1.5 - in the Spring version of viewgen. You can fix it by changing "<c:url page" to <c:url value" in extras/spring/extras/viewgen/src/Form_jsp.xdt.
%%
At line 45 changed 1 line.
The web application security for AppFuse specifies that all *.html url-patterns should be protected. This guarantees 1) all Controllers are protected, and 2) you must go through a Controller to get to a JSP (or at least the ones in ''pages'').
The web application security for AppFuse specifies that all *.html url-patterns should be protected (except for /signup.html and /passwordHint.html). This guarantees that clients must go through an Action to get to a JSP (or at least the ones in ''pages'').
At line 47 changed 1 line.
At this point, you won't be able to view the JSP in your browser because spring's "bind" tags require that the JSP is invoked from the DispatchServlet. Therefore, we need to create a Controller for this JSP, and we should practice TDD and write our Test before we write our Controller.
%%note __NOTE:__ If you want to customize the CSS for a particular page, you can add <body id="pageName"/> to the top of the file. This will be slurped up by SiteMesh and put into the final page. You can then customize your CSS on a page-by-page basis using something like the following:
{{{body#pageName element.class { background-color: blue } }}}%%
At line 49 changed 2 lines.
!!Create PersonFormControllerTest to test our Controller [#2]
To create a JUnit Test for our Controller, start by creating a PersonFormControllerTest.java file in the test/web/**/action directory.
* Add keys in ApplicationResources_en.properties the titles and headings in the JSPs
In the generated JSPs, there are two keys for the title (top of the browser window) and the header (heading in the page). We now need to add these two keys (personDetail.title and personDetail.heading) to ApplicationResources_en.properties.
At line 51 added 14 lines.
{{{
# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
}}}
;:''Just above, we added "personForm.*" keys to this file, so why do I use personForm ''and'' personDetail? The best reason is because it gives a nice separation between form labels and text on the page. Another reason is because all the *Form.* give you a nice representation of all the fields in your database.
I recently had a client who wanted all fields in the database searchable. This was fairly easy to do. I just looked up all the keys in ApplicationResources.properties which contained "Form." and then put them into a drop-down. On the UI, the user was able to enter a search term and select the column they wanted to search. I was glad I followed this Form vs. Detail distinction on that project!''
!!Create PersonFormControllerTest to test PersonFormController [#2]
To create a JUnit Test for the PersonFormController, start by creating a PersonFormControllerTest.java file in the test/web/**/action directory.
At line 56 changed 2 lines.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.appfuse.model.Person;
At line 72 added 2 lines.
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
At line 62 removed 1 line.
At line 64 removed 1 line.
private static Log log = LogFactory.getLog(PersonFormControllerTest.class);
At line 82 changed 1 line.
request.addParameter("id", "1");
request.addParameter("username", "tomcat");
At line 88 removed 2 lines.
}
}]
At line 91 changed 1 line.
Nothing will compile at this point (ant compile) since we need to create the PersonFormController that we're referring to in this test.
public void testSave() throws Exception {
request = newGet("/editPerson.html");
request.addParameter("id", "1");
At line 93 changed 1 line.
We also need to add the constants under src/dao in the file org.appfuse.Constants(.java) otherwise later our build will not work.
mv = c.handleRequest(request, new MockHttpServletResponse());
At line 95 changed 5 lines.
[{Java2HtmlPlugin
/**
* The request scope attribute under which a person form is stored
*/
public static final String PERSON_KEY = "personForm";
Person person = (Person) mv.getModel().get(c.getCommandName());
assertNotNull(person);
request = newPost("/editPerson.html");
super.objectToRequestParameters(person, request);
request.addParameter("lastName", "Updated Last Name");
mv = c.handleRequest(request, new MockHttpServletResponse());
Errors errors =
(Errors) mv.getModel().get(BindException.ERROR_KEY_PREFIX + "person");
assertNull(errors);
assertNotNull(request.getSession().getAttribute("messages"));
}
At line 101 changed 4 lines.
/**
* The request scope attribute that holds the person list
*/
public static final String PERSON_LIST = "personList";
public void testRemove() throws Exception {
request = newPost("/editPerson.html");
request.addParameter("delete", "");
request.addParameter("id", "2");
mv = c.handleRequest(request, new MockHttpServletResponse());
assertNotNull(request.getSession().getAttribute("messages"));
}
}
At line 132 added 1 line.
Nothing will compile at this point (ant compile) because you need to create the PersonFormController that you're referring to in this test.
At line 134 added 1 line.
At line 110 changed 1 line.
Now we have to create a Controller to talk to our Manager and retrieve/save our data. In src/web/**/action, create a PersonFormController.java file with the following contents:
In src/web/**/action, create a PersonFormController.java file with the following contents:
At line 144 added 1 line.
import javax.servlet.http.HttpServletResponse;
At line 119 removed 2 lines.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
At line 149 added 3 lines.
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
At line 124 removed 1 line.
At line 126 removed 2 lines.
private static Log log = LogFactory.getLog(PersonFormController.class);
At line 129 changed 1 line.
At line 133 changed 1 line.
At line 135 changed 1 line.
throws Exception {
throws Exception {
At line 145 removed 3 lines.
// for unit tests - so we can grab an object to save
request.setAttribute(Constants.PERSON_KEY, person);
At line 173 added 45 lines.
public ModelAndView processFormSubmission(HttpServletRequest request,
HttpServletResponse response,
Object command,
BindException errors)
throws Exception {
if (request.getParameter("cancel") != null) {
return new ModelAndView(new RedirectView(getSuccessView()));
}
return super.processFormSubmission(request, response, command, errors);
}
public ModelAndView onSubmit(HttpServletRequest request,
HttpServletResponse response, Object command,
BindException errors)
throws Exception {
if (log.isDebugEnabled()) {
log.debug("entering 'onSubmit' method...");
}
Person person = (Person) command;
boolean isNew = (person.getId() == null);
String success = getSuccessView();
Object[] args =
new Object[] { person.getFirstName() + ' ' + person.getLastName() };
if (request.getParameter("delete") != null) {
mgr.removePerson(person.getId().toString());
saveMessage(request, getText("person.deleted", args));
} else {
mgr.savePerson(person);
String key = (isNew) ? "person.added" : "person.updated";
saveMessage(request, getText(key, args));
if (!isNew) {
success = "editPerson.html?id=" + person.getId();
}
}
return new ModelAndView(new RedirectView(success));
}
At line 153 changed 1 line.
We're not putting much in PersonController at this point because we just want to 1) render the JSP and 2) verify our Test runs. Now we need to add a url-mapping for this controller in the web/WEB-INF/action-servlet.xml file. In the block below, the new line is at the bottom, with __<prop key="/editPerson.html">__:
In the class above, there are a few methods you might not be familiar with. The {{formBackingObject()}} method is used to supply the object this Controller operates on. The {{processFormSubmission()}} method is used to detect the cancel button, and {{onSubmit()}} is called on POST requests and handles delete/add/update of a user.
At line 223 added 15 lines.
There are a few keys that we need to add to ApplicationResources_en.properties to display the success messages. This file is located in ''web/WEB-INF/classes'' - open it and add the following:
;:''I usually add these under the {{# -- success messages --}} comment.''
{{{person.added=Information for <strong>{0}</strong> has been added successfully.
person.deleted=Information for <strong>{0}</strong> has been deleted successfully.
person.updated=Information for <strong>{0}</strong> has been updated successfully.
}}}
You could use generic ''added'', ''deleted'' and ''updated'' messages, whatever works for you. It's nice to have separate messages in case these need to change on a per-entity basis.
You might notice that the code we're using to call the PersonManager is the same as the code we used in our PersonManagerTest. Both PersonAction and PersonManagerTest are ''clients'' of PersonManagerImpl, so this makes perfect sense.
Now you need to add a url-mapping for this controller in the web/WEB-INF/action-servlet.xml file. In the block below, the new line is at the bottom, with __&lt;prop key="/editPerson.html"&gt;__:
At line 174 changed 1 line.
We also need to add the &lt;bean&gt; definition for personFormController in this same file:
You also need to add the &lt;bean&gt; definition for personFormController in this same file:
At line 186 removed 1 line.
At line 191 removed 2 lines.
Everything is almost done for this tutorial, let's get to running our tests!
At line 274 added 1 line.
If you look at our PersonFormControllerTest, all the tests depend on having a record with id=1 in the database (and testRemove depends on id=2), so let's add those records to our sample data file (metadata/sql/sample-data.xml). I'd just add it at the bottom - order is not important since it (currently) does not relate to any other tables.
At line 195 changed 1 line.
Now, if you stop Tomcat, save everything, cd ../.. to the base directory and run __ant test-web -Dtestcase=PersonFormController__, the "testEdit" method should pass and you should see something like the following in your console:
{{{
<table name='person'>
<column>id</column>
<column>first_name</column>
<column>last_name</column>
<row>
<value>1</value>
<value>Matt</value>
<value>Raible</value>
</row>
<row>
<value>2</value>
<value>James</value>
<value>Davidson</value>
</row>
</table>
}}}
At line 197 changed 6 lines.
{{{ [echo] Testing web...
[copy] Copying 1 file to C:\Source\appfuse-springmvc\build\appfuse\WEB-INF
[junit] [appfuse] DEBUG [main] PersonFormControllerTest.testEdit(27) | testing edit...
[junit] [appfuse] DEBUG [main] AbstractFormController.showNewForm(268) | Displaying new form
[junit] Testsuite: org.appfuse.webapp.action.PersonFormControllerTest
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.297 sec
DBUnit loads this file before we running any of the tests, so this record will be available to your Controller test.
At line 204 changed 1 line.
}}}
Make sure are in the base directory of your project. If you run __ant test-web -Dtestcase=PersonFormController__ - everything should work as planned.
At line 206 changed 1 line.
If you see the message below - ''Congratulations'' you've written your first Spring Controller!
%%(color:green)BUILD SUCCESSFUL\\
Total time: 21 seconds%%
At line 208 removed 2 lines.
%%(color: green)BUILD SUCCESSFUL\\
Total time: 11 seconds%%
At line 302 added 150 lines.
!!Clean up the JSP to make it presentable [#5]
Now let's clean up the generated personForm.jsp by making the "id" property a hidden field. Remove the following code block from web/pages/personForm.jsp:
[{Java2HtmlPlugin
<tr>
<th>
<appfuse:label key="person.id"/>
</th>
<td>
<spring:bind path="person.id">
<input type="text" name="<c:out value="${status.expression}"/>"
id="<c:out value="${status.expression}"/>"
value="<c:out value="${status.value}"/>" />
<span class="fieldError"><c:out value="${status.errorMessage}"/></span>
</spring:bind>
</td>
</tr>
}]
And add the following before the &lt;table&gt; tag:
[{Java2HtmlPlugin
<spring:bind path="person.id">
<input type="hidden" name="<c:out value="${status.expression}"/>" value="<c:out value="${status.value}"/>" />
</spring:bind>
}]
If you want to add a usability enhancement to your form, you can set the cursor to focus on the first field when the page loads. Simply add the following JavaScript at the bottom of your form:
{{{<script type="text/javascript">
document.forms["person"].elements["firstName"].focus();
</script>}}}
Now if you execute __ant db-load deploy__, start Tomcat and point your browser to [http://localhost:8080/appfuse/editPerson.html?id=1], you should see something like this:
%%(border: 1px solid black; margin: 0 auto; height: 166px; width: 337px)
[personForm-final.png]
%%
Finally, to make this page more user friendly, you may want to add a message for your users at the top of the form, but this can easily be done by adding text (using &lt;fmt:message&gt;) at the top of the personForm.jsp page.
!![[Optional] Create a Canoo WebTest to test browser-like actions [#7]
The final (optional) step in this tutorial is to create a [Canoo WebTest|http://webtest.canoo.com] to test the JSPs.
;:''I say this step is optional, because you can run the same tests through your browser.''
You can use the following URLs to test the different actions for adding, editing and saving a user.
* Add - [http://localhost:8080/appfuse/editPerson.html].
* Edit - [http://localhost:8080/appfuse/editPerson.html?id=1] (make sure and run __ant db-load__ first).
* Delete - [http://localhost:8080/appfuse/editPerson.html?method=Delete&amp;id=1] (or edit and click on the Delete button).
* Save - Click [edit|http://localhost:8080/appfuse/editPerson.html?id=1] and then click the Save button.
Canoo tests are pretty slick in that they're simply configured in an XML file. To add tests for add, edit, save and delete, open test/web/web-tests.xml and add the following XML. You'll notice that this fragment has a target named ''PersonTests'' that runs all the related tests.
;:''I use CamelCase target names (vs. the traditional lowercase, dash-separated) because when you're typing ''-Dtestcase=Name'', I've found that I'm used to doing CamelCase for my JUnit Tests.''
[{Java2HtmlPlugin
<!-- runs person-related tests -->
<target name="PersonTests"
depends="EditPerson,SavePerson,AddPerson,DeletePerson"
description="Call and executes all person test cases (targets)">
<echo>Successfully ran all Person JSP tests!</echo>
</target>
<!-- Verify the edit person screen displays without errors -->
<target name="EditPerson"
description="Tests editing an existing Person's information">
<webtest name="editPerson">
&config;
<steps>
&login;
<invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
<verifytitle description="we should see the personDetail title"
text=".*${personDetail.title}.*" regex="true"/>
</steps>
</webtest>
</target>
<!-- Edit a person and then save -->
<target name="SavePerson"
description="Tests editing and saving a user">
<webtest name="savePerson">
&config;
<steps>
&login;
<invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
<verifytitle description="we should see the personDetail title"
text=".*${personDetail.title}.*" regex="true"/>
<setinputfield description="set lastName" name="lastName" value="Canoo"/>
<clickbutton label="Save" description="Click Save"/>
<verifytitle description="Page re-appears if save successful"
text=".*${personDetail.title}.*" regex="true"/>
</steps>
</webtest>
</target>
<!-- Add a new Person -->
<target name="AddPerson"
description="Adds a new Person">
<webtest name="addPerson">
&config;
<steps>
&login;
<invoke description="click Add Button" url="/editPerson.html"/>
<verifytitle description="we should see the personDetail title"
text=".*${personDetail.title}.*" regex="true"/>
<setinputfield description="set firstName" name="firstName" value="Abbie"/>
<setinputfield description="set lastName" name="lastName" value="Raible"/>
<clickbutton label="${button.save}" description="Click button 'Save'"/>
<verifytitle description="Main Menu appears if save successful"
text=".*${mainMenu.title}.*" regex="true"/>
<verifytext description="verify success message"
text="Information for &lt;strong&gt;Abbie Raible&lt;/strong&gt; has been added successfully."/>
</steps>
</webtest>
</target>
<!-- Delete existing person -->
<target name="DeletePerson"
description="Deletes existing Person">
<webtest name="deletePerson">
&config;
<steps>
&login;
<invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
<clickbutton label="${button.delete}" description="Click button 'Delete'"/>
<verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/>
<verifytext description="verify success message"
text="Information for &lt;strong&gt;Matt Canoo&lt;/strong&gt; has been deleted successfully."/>
</steps>
</webtest>
</target>
}]
After adding this, you should be able to run __ant test-canoo -Dtestcase=PersonTests__ with Tomcat running or __ant test-jsp -Dtestcase=PersonTests__ if you want Ant to start/stop Tomcat for you. To include the PersonTests when all Canoo tests are run, add it as a dependency to the "run-all-tests" target.
You'll notice that there's no logging in the client-side window by Canoo. If you'd like to see what it's doing, you can add the following between &lt;/webtest&gt; and &lt;/target&gt; at the end of each target.
{{{<loadfile property="web-tests.result"
srcFile="${test.dir}/data/web-tests-result.xml"/>
<echo>${web-tests.result}</echo>}}}
%%(color:green)BUILD SUCCESSFUL\\
Total time: 10 seconds%%
At line 213 changed 1 line.
''Next Up:'' __Part IV:__ [Configuring Tiles and FormController|ConfiguringTilesSpring] - Integrating personForm.jsp with Tiles, adding methods for saving, customizing the JSP so it looks good and finally - writing a WebTest to test the JSPs functionality.
''Next Up:'' __Part IV:__ [Adding Validation and List Screen|ValidationAndListSpring] - Adding validation logic to the personForm so that firstName and lastName are required fields and adding a list screen to display all person records in the database.

Back to SpringControllers, or to the Page History.