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

Beginning Apache Struts - From Novice To Professional (2006)

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

178

C H A P T E R 1 4 T I L E S

_hasErrors = false; return null;

}

/*********** Status codes ************/

public int getStatusRegular(){ return REGULAR; } public int getStatusNewUser(){ return NEW_USER; } public int getStatusLogOff() { return LOG_OFF; }

/********** Error status ************/

public boolean hasErrors(){ return _hasErrors; }

/********** clear form data ********/

public void clear(){ _status = UNDEFINED; _hasErrors = false;

_userid = _password = _password2 = null;

}

}

Listing 14-15 should hold no surprises, apart from the hasErrors() function and the three functions for status codes: getStatusRegular(), getStatusNewUser(), and getStatusLogOff().The need for these functions will be apparent shortly.

Next, there are three views for the Login component. Listing 14-16 shows the “regular” view (Figure 14-3).

Listing 14-16. regular.jsp

<%@ taglib uri="/tags/struts-bean-el" prefix="bean-el" %> <%@ taglib uri="/tags/struts-html-el" prefix="html-el" %>

<html-el:form action="/Login.do">

<table>

<tr>

<td><bean-el:message key="userlogin.prompt.userid"/></td> <td>

<html-el:text property="userid"/> <html-el:errors property="userid"/>

</td>

</tr>

<tr>

C H A P T E R 1 4 T I L E S

179

<td><bean-el:message key="userlogin.prompt.password"/></td> <td>

<html-el:password property="password"/> <html-el:errors property="password"/>

</td>

</tr>

<tr colspan="2"> <td>

<html-el:hidden property="status" value="${LogonForm.statusRegular}"/>

<html-el:submit>

<bean-el:message key="userlogin.prompt.submit"/> </html-el:submit>

</td>

</tr>

</table> </html-el:form>

<html-el:link action="/Login.do?command=${LogonForm.statusNewUser}"> <bean-el:message key="userlogin.prompt.new"/>

</html-el:link>

Listing 14-16 has a form and a link. The form accepts two fields: the user ID and password fields. The link allows new users to register with the system. This JSP page also explains how the “status codes” functions of LogonForm.java (Listing 14-15) are used. For example:

<html-el:hidden property="status" value="${LogonForm.statusRegular}"/>

uses EL to determine the value of the regular status code by calling the getStatusRegular() function on the LogonForm. The naive alternative would be to hardcode the value:

<html-el:hidden property="status" value="3"/> //don't do this!

Ugh! The “new user” view (Figure 14-4) is similar to the regular view, with the addition of the second “confirm password” field. Listing 14-17 shows the JSP code for this page.

180

C H A P T E R 1 4 T I L E S

Listing 14-17. newuser.jsp

<%@ taglib uri="/tags/struts-bean-el" prefix="bean-el" %> <%@ taglib uri="/tags/struts-html-el" prefix="html-el" %>

<html-el:form action="/Login.do"> <table>

<tr>

<td><bean-el:message key="userlogin.prompt.userid"/></td> <td>

<html-el:text property="userid"/> <html-el:errors property="userid"/>

</td>

</tr>

<tr>

<td><bean-el:message key="userlogin.prompt.password"/></td> <td>

<html-el:password property="password"/> <html-el:errors property="password"/>

</td>

</tr>

<tr>

<td><bean-el:message key="userlogin.prompt.password2"/></td> <td>

<html-el:password property="password2"/> <html-el:errors property="password2"/>

</td>

</tr>

<tr colspan="2"> <td>

<html-el:hidden property="status" value="${LogonForm.statusNewUser}"/>

<html-el:submit>

<bean-el:message key="userlogin.prompt.submit"/> </html-el:submit>

</td>

</tr>

</table> </html-el:form>

Lastly, the “logoff” view (see Listing 14-18) has just a single logoff button, as Figure 14-5 shows.

C H A P T E R 1 4 T I L E S

181

Listing 14-18. success.jsp

<%@ taglib uri="/tags/struts-bean-el" prefix="bean-el" %> <%@ taglib uri="/tags/struts-html-el" prefix="html-el" %>

<html-el:form action="/Logoff.do">

<bean-el:message key="userlogin.prompt.loggedonas" arg0="${LogonForm.userid}"/>

<html-el:submit>

<bean-el:message key="userlogin.prompt.logoff"/> </html-el:submit>

<html-el:hidden property="status" value="${LogonForm.statusLogOff}"/>

</html-el:form>

Notice that the logoff view (success.jsp) submits data to the form handler called Logoff.do, unlike the previous two views, which submitted data to Login.do. This is because we don’t need to perform validation for a logoff, so the declaration for Logoff.do in struts-config.xml has validate="false" and no input page.

Next, we examine the struts-config.xml file (see Listing 14-19). For clarity, I’ll only show the form-beans and action-mappings sections.

Listing 14-19. form-beans and action-mappings Sections of struts-config.xml

<form-beans>

<form-bean name="LogonForm" type="net.thinksquared.login.struts.LogonForm"/>

</form-beans>

<action-mappings>

<action path="/Login" type="org.apache.struts.actions.ForwardAction" name="LogonForm"

scope="session"

validate="true"

input="/index.jsp"

parameter="/index.jsp"/>

182

C H A P T E R 1 4 T I L E S

<action path="/Logoff" type="org.apache.struts.actions.ForwardAction" name="LogonForm"

scope="session"

validate="false"

parameter="/index.jsp"/>

<action path="/LoginController" type="net.thinksquared.login.struts.UserLoginAction" name="LogonForm"

scope="session"

validate="false">

<forward name="success" path="/userlogin/success.jsp"/> <forward name="new-user" path="/userlogin/newuser.jsp"/> <forward name="regular" path="/userlogin/regular.jsp"/>

</action>

</action-mappings>

The declarations here are a little tricky, so be sure to pay careful attention. First, the LogonForm is declared. This should be familiar to you and doesn’t require further explanation. Next come the three form handlers. The first two, Login.do and Logoff.do, are called directly by the forms on the three views. The regular and new user views submit to Login.do,

and the logoff view submits to Logoff.do.

The only difference between the two form handlers is that Logoff.do does not validate the form data (since we’re logging off).

Both Login.do and Logoff.do are associated with ForwardAction, which forwards to the page specified in the parameter attribute of the form handler. This parameter attribute is required for ForwardAction. No processing is done by ForwardAction apart from this.

So, from the declarations in Listing 14-19, it is apparent that both Login.do and Logoff.do do nothing but immediately redisplay the input page!

This might seem a little strange. Why immediately redisplay a submitted page with apparently no processing? The answer, of course, is that the input page (Listing 14-14) contains an embedded Login <tiles:insert>. When the input page is redisplayed, the embedded Tiles component is updated too. In this updating, the associated Tiles controller is called, and the form data is actually processed.

We see from Listing 14-14 that the called definition is .login. The declaration for this definition is

<definition name=".login" path="/LoginController.do" />

So, the Tiles controller is the form handler called LoginController.do:

C H A P T E R 1 4 T I L E S

183

<action path="/LoginController" type="net.thinksquared.login.struts.UserLoginAction" name="LogonForm"

scope="session" validate="false">

<forward name="success" path="/userlogin/success.jsp"/> <forward name="new-user" path="/userlogin/newuser.jsp"/> <forward name="regular" path="/userlogin/regular.jsp"/>

</action>

UserLoginAction is a TilesAction subclass, which I’ll describe shortly. LoginController itself performs no simple validation (validate="false"), which should not raise any eyebrows, since simple validation has been done by the main Login.do form handler for the page.

All the form handlers—Login.do, Logoff.do, and LoginController.do—are all set for session scope. This makes sense because we want the login information to persist for as long as the user interacts with the system. The added bonus to this is that if another JSP page embeds the Login component, the correct view will be displayed. The user need only log on once, and all other pages with the Login component will correctly reflect this too. Lastly, we come to the UserLoginAction class (see Listing 14-20), where the processing

of user data and flow control is done.

Listing 14-20. UserLoginAction.java

package net.thinksquared.login.struts;

import java.io.IOException; import javax.servlet.*; import javax.servlet.http.*;

import org.apache.struts.tiles.*;

import org.apache.struts.tiles.actions.*; import org.apache.struts.action.*;

public final class UserLoginAction extends TilesAction{

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

throws IOException,ServletException{

184

C H A P T E R 1 4 T I L E S

LogonForm lForm = (LogonForm) form;

String cmd = request.getParameter("command"); if(cmd != null){

return mapping.findForward("new-user");

}

if(lForm.getStatus() == LogonForm.UNDEFINED){ return mapping.findForward("regular");

}

if(lForm.getStatus() == LogonForm.LOGGED_ON){ return mapping.findForward("success");

}

if(lForm.getStatus() == LogonForm.LOG_OFF){ System.out.println("Logging off " + lForm.getUserid()); lForm.clear();

return mapping.findForward("regular");

}

if(lForm.getStatus() == LogonForm.NEW_USER){ if(lForm.hasErrors()){

return mapping.findForward("new-user"); }else{

System.out.println("Creating New User " + lForm.getUserid()); return mapping.findForward("success");

}

}

if(lForm.getStatus() == LogonForm.REGULAR){ if(lForm.hasErrors()){

return mapping.findForward("regular"); }else{

System.out.println("Logging on regular user " + lForm.getUserid());

return mapping.findForward("success");

}

}

C H A P T E R 1 4 T I L E S

185

//catch-all

return mapping.findForward("regular");

}

}

The first thing we do is check if there are any parameters on the URL:

String cmd = request.getParameter("command"); if(cmd != null){

return mapping.findForward("new-user");

}

The only reason why there might be a parameter on the URL is if the user clicked the “I’m a new user” link on regular.jsp (see Listing 14-21).

Listing 14-21. Link Snippet from regular.jsp

<html-el:link action="/Login.do?command=${LogonForm.statusNewUser}"> <bean-el:message key="userlogin.prompt.new"/>

</html-el:link>

In this case, we obviously want to display the new user view.

Note Since we’re merely checking for the presence of a command parameter, we could have just used

<html-el:link action="/Login.do?command="dummy"..>. However, if we add more linked views in the future, we’ll have to change the link to something along the lines of Listing 14-21.

The next two blocks of code simply redisplay the necessary views:

if(lForm.getStatus() == LogonForm.UNDEFINED){ return mapping.findForward("regular");

}

if(lForm.getStatus() == LogonForm.LOGGED_ON){ return mapping.findForward("success");

}

This is needed when the page first loads (the first block) or if the user refreshes the page after having logged on (second block).

The following block logs off the user, taking care to clear the form before the default regular view is displayed:

186

C H A P T E R 1 4 T I L E S

if(lForm.getStatus() == LogonForm.LOG_OFF){ System.out.println("Logging off " + lForm.getUserid()); lForm.clear();

return mapping.findForward("regular");

}

For clarity, I haven’t implemented the model code but used System.out.println() calls to indicate what should be done.

The last two blocks are perhaps the most interesting:

if(lForm.getStatus() == LogonForm.NEW_USER){ if(lForm.hasErrors()){

return mapping.findForward("new-user"); }else{

System.out.println("Creating New User " + lForm.getUserid()); return mapping.findForward("success");

}

}

if(lForm.getStatus() == LogonForm.REGULAR){ if(lForm.hasErrors()){

return mapping.findForward("regular"); }else{

System.out.println("Logging on regular user " + lForm.getUserid()); return mapping.findForward("success");

}

}

All these blocks of code do is display the logoff view, after the user correctly fills in either the regular login form or the new user login form. But notice that the form is checked for errors with hasErrors() (see Listing 14-15) before the logoff view is displayed.

This is the reason we created the hasErrors() function on the LogonForm—it enables the Login component to tell whether the form data passed simple validation.

We can’t rely on Struts automatically redisplaying a page upon simple validation failure, because the JSP for the Login Tiles component doesn’t exist when the page is redisplayed. It’s the Tiles controller (UserLoginAction) that decides which JSP to paste into the input page. So it has to know if there are validation errors in order to paste the right JSP. Creating a hasErrors() function on the LogonForm is a simple way of doing this.

That pretty much wraps up the Login Tiles component example. Please visit the Source Code section of the Apress website at http://www.apress.com to download the source code for the Login Tiles component. The WAR file, ready for deployment, is called login.war, and it’s also in the Source Code section of the Apress website. I encourage you to experiment with this simple Tiles component to gain a better understanding of how Tiles components work.

C H A P T E R 1 4 T I L E S

187

Getting External Form Data

What if there was more than one Tiles component on the page? Would the cast

LogonForm lForm = (LogonForm) form;

in Listing 14-20 throw an exception? The answer is no, because the type of ActionForm (more precisely, the form bean) passed to the Tiles controller must match the one declared in struts-config.xml. If no such form bean was submitted, then a blank one is created, and passed to the Tiles controller. This means the cast will always work as long as the declaration is correct.

This has an interesting implication: suppose form data is sent to a form handler associated with form bean X. Then, all Tiles components on that same page that also are associated with that form bean X will receive carbon copies of that form data.

For example, consider the following set of form-handler declarations:

<action path="MyAction"

name="MyFormBean" type="

...">

<action path="MyTileControllerA"

name="MyFormBean" type="...

">

<action path="MyTileControllerB"

name="MyFormBean" type="...

">

<action path="MyTileControllerC"

name="DifferentFormBean" type="...">

and the following JSP page snippet:

<html:form action="/MyAction.do">

...

</html:form>

<tiles:insert definition=".myTilesComponentA"/> <tiles:insert definition=".myTilesComponentB"/> <tiles:insert definition=".myTilesComponentC"/>

When the form data is submitted, it is first stored in an instance of MyFormBean and then processed by the Action subclass associated with MyAction.

What is less obvious is that the execute() functions on MyTileControllerA and

MyTileControllerB are passed separate copies of MyFormBean. The execute() function of MyTilesControllerC is passed a new instance of DifferentFormBean.

You will use this trick in the following lab session, to get a Tiles component to receive data from the external form.

Lab 14: The Find Facility

We want to add a Find function to both full.jsp and mnc.jsp, making minimal alterations to existing code. Specifically: