rr_lesson_2.war Introduction
The first three lessons are for true beginners to Struts. Each lesson introduces some new concepts and each successive lesson required you to have mastered the content of the previous lesson. These first lessons are meant to be very basic to help the beginner get a handle on the Struts concepts without getting side-tracked. If you have previous experience coding Struts applications, you probably will want to jump to some of the more intermediate level lessons.
This lesson assumes you know the basic concepts presented in Lesson 1. I'll only be introducing a few new concepts in this lesson. I'm not going throw every file in this Lesson so you'll need to download the source code above if you want to follow along.
New Concepts in Lesson 2:
Requirements
This application requires an application server that implements the Servlet 2.4 and JavaServer Pages 2.0 specifications. The examples should all work on Tomcat 5.x (Discussed in next section). Please do not e-mail about getting your application to run on a server other than Tomcat. The source code (and an Ant build file) is provided for all the lessons so you should be able to build a war from the source and run it on you application server of choice.
struts-config.xml
This struts-config isn't too different from Lesson 1. You'll first notice the use of a global-forward. In this lesson I defined one called "error." This means that when I return an ActionForward based on the "error" lookup (ie. return (mapping.findForward("error")) ) from any Action method, I will forward to error.jsp. If we didn't use a global forward but wanted to always forward to this error page in case of an error, then all of our action mappings would have to declare this forward. Global forwards are nice for when you want to set up standard forward names that will always result in you going to the same page when they are returned. So for example, if returning "success" is always was going to forward to confirmation.jsp then we could simply define "success" as a global forward and not even bother to have to define it in our ActionMapping. Typically I don't like to set up global forwards for forwards like "success" or "failure" since they will often forward to different resulting pages depending on the Action being called. (In this small application there is only one success message so we could have made it a global forward definition, but since in reality you'd have other action mappings that might use this same name "success," I specifically defined it in my insertEmployee Action mapping.) I believe Struts will first look in the Action mapping for a forward name and then it will look for any global forward definition so you could in a sense 'over-ride' global forwards. I find this gets confusing to figure out what's going on when looking at the struts-config, so I tend to avoid that if possible.
In this Lesson there is also a more detailed mapping for "setUpEmployeeForm." In Lesson 1 our setupEmployeeForm (yes, I spelled it a bit differently in that lesson:) simply forwarded directly to our employeeForm.jsp. Imagine as time progressed, that we now needed to provide a list of departments to assign to our Employee when we wanted to do an insert. We would need to make sure that we have a list of Department objects in scope so that they will be available for our dropdown. There are several ways we could handle this, and in later lessons (the struts-crud or ibatis example) you'll see my prefered apporach which is similar to what I'm doing here (except that I'm using a DispatchAction and have a common "prep" method used by all the dispatch methods to 'prep' or setup my Request with things I need in there.) In this Lesson when we call setUpEmployeeForm we will forward to our SetUpEmployeeAction which will make sure our List of "Departments" is put into Request scope for our resulting employeeForm.jsp to use.
Notice also in the 'insertEmployee' mapping the validate attribute is set to 'true' and we have defined an input attribute with the value 'employeeForm.jsp.' By setting validate to true, Struts will automatically call the ActionForm's validate() method which it processes 'before' we even get to our InsertAction class. If validation fails in the validate method, we will be returned to the input value - in this case, back to our employeeForm.jsp. (You'll see the validate method in the next section.)
EmployeeForm.java
Our EmployeeForm now provides its own implementation of the validate method by overriding the base class' ActionForm validate method. Remember in our struts-config we set validate="true" in the insertEmployee action mapping. This means that Struts will take care of calling this validate method before we get to our Action class. (NOTE: After you are proficient with these Struts basics, check out the article on "validating manually" and you'll see why I actually don't really like to have validate set to true. I like to manually take care of validation either directly in the Action class or by calling the form's validate method from the Action.)
Our validate method above is set up to make sure that the user enters in a "name" and enters in an "age" that is also a number. When some of the validation tests do not pass we add a new ActionMessage object to our ActionErrors class. If you look at the MessageResources_en.properties file you'll see such definitions as:
errors.required={0} is required.
errors.integer={0} must be a whole number.
When we create new ActionMessage("errors.integer","Age")); the actual message will resolve to using the message defined for errors.integer in our resources file and "Age" will be passed in as the parameter for {0}. The actual ActionErrors object will be used in our employeeForm.jsp to display the error messages.
EmployeeService
Just like in Lesson 1 our EmployeeService mimics a call to real insert (typically handled by a DAO - see the Struts CRUD or Struts-Spring-iBATIS lesson for a more complete example). Since this lesson is also showing how you can see what happens when an error is thrown, you can uncomment the throw new DatabaseException() line in the doInsert method, to see how that error gets handled by our Action class.
SetUpEmployeeAction
Our SetUpEmployeeAction calls our Service class to get a Collection of Departments and puts them in Session scope. (I'm using Session scope for simplicity. In real life you should use Request scope, but this opens up some problems when you let Struts take care of calling validate automatically by setting validate="true". For further insight into this problem and my prefered approach to dealing with it read validate manually - keeping request scoped objects in scope when validation fails.) We also go ahead and set up a default departmentId for our form to "2" (maybe this is the most common Employee department so we want to preset that to be the one that shows up as selected by default in our drop down options. More on this in the employeeForm.jsp section.)
InsertEmployeeAction
The only difference between this Action and the one from Lesson 1 is the use of ActionMessages. If a DatabaseException is thrown from our backend we go ahead and create an ActionMessage and we call "saveErrors" to persist the ActionMessages as a set of Error messages. If things all go ok (which they will unless you uncommented the throw DatabaseException line in the Service doInsert method) we create an ActionMessage with the key from our MessageResources: message.employee.insert.success. Note that for our success message we call "saveMessages" as opposed to "saveErrors" - this saves our ActionMessages object into the Request with a different key, which helps for figuring out on the display side of things what kind of Messages we are dealing with (errors or general messages).
employeeForm.jsp
In our action mapping for inserEmployee we set validate="true" and provided this employeeForm.jsp page as our input page. If the EmployeeForm's validate method returns ActionErrors we will be forwarded back to this form and the <logic:messagesPresent> will evaluate to true. Since error exists, we display our Error's header and then use the html:messages tag to loop over the error messages and display them. This is just one way to display error messages. You'll have to look at the Struts docs to go over the other options.
The next thing of note is the select options setup for departments. Struts does provide some html tags for dealing with options but I just find it easier and more intutive to use a JSTL forEach loop within an html:select tag and build the html:option tags myself. The html:select tag not only maps our EmployeeForm property of "departmentId" to what is selected, but, in conjunction with the html:option, it will also make sure that if the value of one of our html options matches the select proerty, that the items shows up as the deafault one displayed in the options. So remember back in our setUpEmployeeAction we set deaprtmentId to "2." This means that the option with the value of 2 will be the one displayed in our drop down list.
confirmation.jsp
The confirmation page now has an html:messages section with the attribute 'message' set to 'true'. By default, html:messages actually displays messages saved as "saveErrors" which is a bit confusing. By setting message="true" we tell struts to display messages saved as "saveMessages." This will display the messages we saved in our InsertAction if successful - which in this case is only one Message.
error.jsp
This is the error page we will forward to if we returned an error in our Action (based on the fact that we are manually setting our Forward mapping lookup to "error"). (In the next lesson you'll see a cleaner approach using declarative Exception handling.)
If you look at the value for our exception.database.error key in our MessageResources file you'll see it defined with a <br/> tag: Problem occurred when performing database operation.<br/>Please contact support. By declaring the escapeXml attribute to "false" we allow the actual html tags to be used in our display (otherwise they are escaped and will show up in the display).
|