Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Apache Struts - From Novice To Professional (2006)

.pdf
Скачиваний:
57
Добавлен:
17.08.2013
Размер:
11.68 Mб
Скачать

68

C H A P T E R 7 P R O C E S S I N G B U S I N E S S L O G I C

on the HTML form. Your Action subclass can use these getXXX() functions to read the form data. Figure 7-1 illustrates this data flow.

Figure 7-1. Data flow from form to Action

In fact, for each Action subclass you define, you may associate any number of ActionForm subclasses, all of which are potential inputs into that Action subclass. You’ll see how this association is made in Chapter 9.

The Statelessness of Action

One very important thing to know about Action is that it must be stateless. You must never store data in your Action subclass. In other words, your Action subclass must never have instance variables (variables within a function are OK). Listing 7-1 illustrates what you should never do.

Listing 7-1. Never Use Instance Variables in an Action Subclass!

public class MyBadAction extends Action{

private String myBadVariable; // NEVER EVER do this!

protected void myFunction(){

String s = null; //variables in a function are OK.

...

C H A P T E R 7 P R O C E S S I N G B U S I N E S S L O G I C

69

}

...//rest of Action

}

Struts manages the creation of your Action subclasses, and these are pooled (reused) to efficiently service user requests. For this reason, you can’t use instance variables. There is no way to guarantee that the data you store in an instance variable applies to a given request.

Subclassing Action

There is just one method you need to override in your Action subclass, and that’s execute() (see Listing 7-2).

Listing 7-2. The execute() Function

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response)

Note In older versions of Struts (version 1.0 and earlier), execute() was called perform().

After the validate() method on the ActionForm passes, Struts calls execute() on the Action associated with the form. As you can see, five classes play a role in this function:

ActionForward: Represents the “next” page to be displayed. This is the return value of execute().

ActionMapping: Represents associations between this Action and its possible “next” pages.

ActionForm: Contains input form data.

HttpServletRequest: Contains request-scoped and session-scoped data. You can also place data on the session using this class. Note that not all data goes on the submitted form. Other data could be placed on the form’s “action” URL. You can read these with HttpServletRequest.

70

C H A P T E R 7 P R O C E S S I N G B U S I N E S S L O G I C

HttpServletResponse: Allows you to write data to the user’s web browser. You are less likely to use this class, unless you want to send, say, a generated PDF report back to the user. ()

The important functions of each of these classes are listed in Appendix B.

Business Logic in the Registration Webapp

We’ll now take up our earlier example of the Registration webapp (see Chapters 5 and 6), and see how we can process business logic. For the Registration webapp, this involves the following:

Complex validation: Checking if the given user ID exists. I gave you a solution in Chapter 6, Listing 6-2, which we’ll use here.

Data transformation: Saving the user ID and password into the database, using the User data object (see Listing 5-1 in Chapter 5).

Navigation: Displaying the “You’re Registered!” page (Figure 7-2) if registration succeeds, or redisplaying the form with an error message (Figure 7-3) if the user ID exists.

Figure 7-2. The “You’re Registered!” page

C H A P T E R 7 P R O C E S S I N G B U S I N E S S L O G I C

71

Figure 7-3. Redisplay of form to user with error message

We only need one Action subclass, called RegistrationAction, which is shown in Listing 7-3.

Listing 7-3. RegistrationAction.java

package net.thinksquared.registration.struts;

import javax.servlet.http.*; import org.apache.struts.action.*;

import net.thinksquared.registration.data.User;

public class RegistrationAction extends Action{

public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){

//get userid and password

RegistrationForm rForm = (RegistrationForm) form; String userid = rForm.getUserId();

String password = rForm.getPassword();

72

C H A P T E R 7 P R O C E S S I N G B U S I N E S S L O G I C

//complex validation: check if userid exists if(User.exists(userid)){

ActionMessages errors = new ActionMessages(); errors.add("userid",

new ActionMessage("reg.error.userid.exists"));

saveErrors(request,errors);

//navigation: redisplay the user form.

return mapping.getInputForward();

}else{

//data transformation: save the userid and password //to database:

User user = new User(); user.setUserId(userid); user.setPassword(password); user.save();

//navigation: display "you're registered" page

return mapping.findForward("success");

}

}

}

Take your time studying Listing 7-3, because it is a pattern for every Action you’ll ever write.

The first thing to note is the cast from ActionForm to RegistrationForm:

RegistrationForm rForm = (RegistrationForm) form;

Recall that the ActionForm passed into execute() is really a RegistrationForm instance (see Listing 6-1), and that it contains form data that has passed simple validation. The cast is done so that the userid and password properties of the form can be read.

Note This isn’t the best way to read data from an ActionForm. In Chapter 17, I’ll describe a better

alternative.

C H A P T E R 7 P R O C E S S I N G B U S I N E S S L O G I C

73

I haven’t yet shown you how Struts knows that the form data goes into and is validated by RegistrationForm. And I also haven’t shown you how Struts knows that the populated RegistrationForm needs to be sent to RegistrationAction for further processing. You’ll see how both these mappings are made in Chapter 9.

I’ll analyze the rest of Listing 7-3 using the three broad tasks every Action subclass performs: complex validation, data transformation, and navigation.

Complex Validation

The relevant section performing complex validation in Listing 7-3 is shown in Listing 7-4.

Listing 7-4. Complex Validation in RegistrationAction

if(User.exists(userid)){

ActionMessages errors = new ActionMessages(); errors.add("userid",

new ActionMessage("reg.error.userid.exists"));

saveErrors(request,errors);

//navigation: redisplay the user form.

return mapping.getInputForward();

}else{ ...

The User.exists() function checks if the user ID given exists. This is typical of what I mean by “well-defined” interfaces between the Controller (RegistrationAction) and Model (User class).

Struts does not prevent you from using raw SQL to check if a user ID exists, but this would be a poorer implementation of the MVC design pattern. Having a Model class handle data access like this makes your code cleaner, much more maintainable, and more future-proof. If you switched to a database that used a different variant of SQL, you would need to change at most the implementation of the exists() function and not every single SQL statement checking for a duplicate user ID.

The next statement creates a blank list to hold error messages:

ActionMessages errors = new ActionMessages();

The use of ActionMessages to hold error messages instead of ActionErrors is rather unfortunate. In earlier versions of Struts, it would have indeed been ActionErrors, but later versions have deprecated the use of ActionErrors everywhere except as the return value of validate() in ActionForm.

74

C H A P T E R 7 P R O C E S S I N G B U S I N E S S L O G I C

After creating the holder for error messages, I next add an error message to it:

errors.add("userid", new ActionMessage("reg.error.userid.exists"));

This should be old hat to you by now. If you have difficulty understanding this statement, you should reread the “Using ActionErrors” section in Chapter 6.

The last statement I’ll consider is

saveErrors(request,errors);

You signal a complex validation failure by using saveErrors() to save a list of errors. However, unlike simple validation where the form is automatically redisplayed, here the list of errors is used to populate the <html:errors> tags on the “next” page.

Note saveErrors() actually saves the errors onto the request object, which is an instance of HttpServletRequest that was passed as a parameter into execute().

After execute() completes, Struts examines the request object for errors, then tries to display them in the “next” page indicated by the ActionForward instance returned by execute().

Struts knows which error messages to display and where to display them because the “next” page is assumed to contain our old friend, the <html:errors> tags. We’ll revisit this in detail the next chapter. If there are no <html:errors> tags, then no error messages are displayed.

The astute reader may be wondering why it’s saveErrors(request, errors) instead of simply saveErrors(errors). The answer is that Action subclasses must be stateless. The errors object must therefore be stored on request.

Data Transformation

In the RegistrationAction class, the only data transformation involved is saving the user ID and password to the database, as Listing 7-5 illustrates.

Listing 7-5. Data Transformation in RegistrationAction

User user = new User(); user.setUserId(userid); user.setPassword(password); user.save();

C H A P T E R 7 P R O C E S S I N G B U S I N E S S L O G I C

75

As you can see, using Model classes to write data results in clean code. Model classes are not part of Struts, because this is a task best left to dedicated persistence frameworks like Hibernate or Torque. You should refer to Appendix A for details.

Navigation

Navigation refers to programming logic deciding what page to display next to the user. In RegistrationAction, there are just two possible “next” pages: the page containing the submitted form (redisplayed with error messages) and the “You’re Registered!” page indicating successful registration.

The code used to reach each is slightly different. Listing 7-6 shows the navigation logic involved.

Listing 7-6. Navigation in RegistrationAction

if(User.exists(userid)){

...

//navigation: redisplay the user form. return mapping.getInputForward();

}else{

...

//navigation: display "You're registered" page

return mapping.findForward("success");

}

ActionForward, the return value of execute(), is a reference to the “next” page. Illustrated in Listing 7-6 are the two most important ways of instantiating it.

Note If you look up the JavaDocs for ActionForward, you’ll find that there are in fact a number of different constructors for ActionForward. Each constructor allows you to change defaults such as whether or not the page is a redirect. There are even subclasses of ActionForward that create ActionForwards with the more common choices.

The first involves creating an ActionForward reference to the page containing the submitted form. Possibly the only reason you’d want to do this is to redisplay a form if it fails complex validation:

76

C H A P T E R 7 P R O C E S S I N G B U S I N E S S L O G I C

mapping.getInputForward()

To get a reference to the input page you use the getInputForward() function on the ActionMapping instance, mapping. Recall that mapping is supplied for you by Struts, because it’s a parameter in execute().

Notice that unlike simple validation where redisplay was automatically handled by Struts, here you’re on “manual,” meaning you have to specify the error page yourself. This is because of the very different natures of simple and complex validation. For example, you might check a user’s account balance before performing a task. If there are insufficient funds, it makes no sense to automatically redisplay the input page. A more appropriate response would be to display an “insufficient funds” page.

The second way you’d want to instantiate ActionForward is for it to point to a named “next” page:

mapping.findForward("success")

Here, the name success refers to a named page, which is one possible “next” page I’ve bound to RegistrationAction. I’ll describe how this is done in Chapter 9.

This indirect way of referring to a page might seem overly complicated. Why not just use a direct reference like

new ActionForward("/mywebapp/registered.jsp")

There are two good reasons why you should avoid using a direct reference. The first is because the indirect method makes your webapp more maintainable, and the second is that it promotes code reuse.

To understand the first reason, you’d have to know that the named pages are specified in one file called struts-config.xml. So, if you moved a page, you’d only have to amend this one file. If the paths were hardwired, you’d have to change several Actions and recompile your app! Not good.

The second reason is more subtle, but more compelling. It is often the case that you want to reuse an Action in different contexts. Depending on the context, you would want a different “success” page to be displayed. Hardwiring a “next” page into an Action obviates the possibility of doing this.

To recap, there are two important ways to display the “next” page:

The input page: mapping.getInputForward()

A named page: mapping.findForward(...)

Lab 7: Implementing ContactAction for LILLDEP

This lab continues where we left off in Lab 6. ContactAction is the Action subclass associated with ContactForm of Lab 6.

C H A P T E R 7 P R O C E S S I N G B U S I N E S S L O G I C

77

In this lab, you will complete the implementation of ContactAction. Before you begin, ensure that you can compile the LILLDEP webapp without errors. Then complete the following:

1.Get the Contact data object from the ContactForm and save it. You might want to peruse the source code for BaseContact to see how to save a Contact.

2.If an Exception is thrown while saving a Contact, redisplay the input page with the error message key lilldep.error.save, for the property

ActionMessages.GLOBAL_MESSAGE.

3.If all goes well, forward to a page named “success.”

4.Run compile.bat to ensure your work compiles with no errors.

Summary

In this chapter, you’ve learned how business logic is processed by your Action subclass. The highlights of this chapter are as follows:

org.apache.struts.action.Action subclasses must be stateless.

You need to override just the execute() method of Action.

Complex validation errors are flagged using the saveErrors() function.

Redisplay of a page with errors is “manual.”

Use mapping.getInputForward() to redisplay the input page.

Use mapping.findForward(...) to display a named page.