0 to Struts in 60 minutes – Part II

Our goal :

First Iteration :
* set up a login page, login formbean, login process Actionservlet stub

Second Iteration :
* set up log4j for logging purposes
* use a pattern – Data Access Object pattern
* talk to mysql database
* logic for authentication in login actionservlet

First Iteration :

Setting up login page, login process servlet stub, welcome page

Lets build this project iteratively. First the skeleton and then slowly we will start adding sinews adding more complexity. A simple login page that submits the values to the login servlet. ( Well it has to go through the controller servlet – we will see that in a minute ) – and the login process servlet right now doesnt do anything big – just accepts the values and displays the username being entered. It still does not have the ability to talk to database – it has to wait !!

We need to create 4 files ( 2 JSP,2 Java) and tamper with struts_config.xml

Login.jsp


<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html>

<body>

<html:form action="/actions/actionlogin">

Username : <html:text property="username"/><BR>

Password : <html:password property="password"/>

<html:submit>Submit</html:submit>

</html:form>

</body>

</html>

We are taking advantage of struts tags. The html code we have to write gets minimised a lot when we use struts tags – things like populating a select drop down menu and selecting default value can all be written in a single line – makes the html code look neat and maintenance is easy. Perhaps in the next blog entry I will write a page takes advantage of all the html tags, and other bells and whistles in struts. Save this file as login.jsp and let it sit in under webapps/antiPC/pages

The login ActionServlet stub


package net.kvrlogs.antiPC.actions;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.*;

public class Login extends Action {

public ActionForward execute(ActionMapping mapping, ActionForm form,

HttpServletRequest request, HttpServletResponse response)

throws Exception {

return mapping.findForward("success");

}

}

Save this under WEB-INF/src/net/kvrlogs/antiPC/actions as Login.java

If you notice this servlet returns a value called “success”. This is picked up by the controller servlet and based on the value it calls up the appropriate jsp page or another servlet.

We are slowly starting to get into struts.
How is this servlet (its called ActionServlet) different from a simple servlet.

1. We extend Action – instead of HTTPServlet.
2. We do not override doGet or doPost anymore. Instead we override a method called execute.
3. execute is given 4 important objects
mapping
form
request – same as a servlet
response – same as a servlet

What is mapping?

Mapping is the hotline to the controller servlet. This login servlet cannot ( and should not ) direct control to another JSP page. It it does like that then it blows away the main concept of MVC Architecture.

This mapping is done in struts-config.xml file from where controller servlet reads and gets its intelligence.

What is form?

Form is the ActionForm. Whenever a html form is submitted all the field values are put inside a JavaBean called FormBean. For now just type the following code and save it under /WEB-INF/src/net/kvrlogs/antiPC/forms/LoginFormBean.java

If you are using eclipse there is a short cut to generate this file :
Just type the following
private String username;
private String password;

Then eclipse can automatically generate getters and setters for the variables like this gettersetter

Point to note is the variable names username and password should be same as the html field names given in login.jsp page. When the login.jsp page is submitted, the values in username and password are put in this formbean and sent to the login servlet. This is done by Java reflection where it automatically discovers the right variable names.

Also note that the getters and setters follow javabean naming rules ( if you let eclipse do its job you will not be mistyping the getter setter names and would not later haunt you when the form variables are not seen in the login servlet ).

*the getter, setter should be named as get/set and First letter of variable capitalized.

if variable name is username
getter will be getUsername

* the variable should be private, whereas the getter setter should be public.


package net.kvrlogs.antiPC.forms;

import org.apache.struts.action.ActionForm;

public class LoginFormBean extends ActionForm {

private String username;

private String password;

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

}

In struts-config.xml
Go to the node called


<action

path="/Welcome"

forward="/pages/Welcome.jsp"/>

Change the above to this


<action

path="/Welcome"

forward="/pages/login.jsp"/>

Now if you open the index.jsp page there is a redirect command

<logic:redirect forward="welcome"/>

When index.jsp page is typed in the browser, when the page loads it sees the redirect to “welcome” action. The controller servlet picks this up – finds the action mapping in struts-config.xml as directing to forward to /pages/login.jsp – and thats what the browser will see – login.jsp

Next we have to define the action for /actions/actionlogin

<action

path="/actions/actionlogin"

type="net.kvrlogs.antiPC.actions.Login"

parameter="/pages/index.jsp">

<forward

name="success"

path="/pages/homepage.jsp"/>

</action>

path : Its the path we type in the form action
type : which actionservlet is called to execute this action
parameter : from where this request is coming from

forward : After the actionservlet does it job it sends a message – “success” in our case. Heres where we decide where the “success” should take us to – to /pages/homepage.jsp

Lets create homepage.jsp


<html>

<body>

You are in

</body>

</html>

Save this in pages/homepage.jsp
Now this is what we have done so far in our first iteration.

*Created login.jsp, Login.java, homepage.jsp
* Mapped the logic flow in struts-config.xml under the node

Time to build, and try it out in your browser.

Save everything.
Click on the directiory WEB-INF/src ( or where the build.xml file is )
Select the little run button from eclipse and choose Run as ant build.
Pray 🙂

I got a build error saying this :

[javac] /usr/local/tomcat/webapps/antiPC/WEB-INF/src/net/kvrlogs/antiPC/actions/Login.java:3: package javax.servlet.http does not exist

I added this to the pathelement in build.xml

<pathelement path ="/Library/Tomcat/common/lib/servlet-api.jar"/>

Once it builds, restart antiPC in tomcat manager. And type http://localhost:8080/antiPC in webbrowser.

You will see this
login

Hit submit and you will see this youarein

Second Iteration :

* set up log4j for logging purposes
* use a pattern – Data Access Object pattern
* talk to mysql database
* logic for authentication in login actionservlet

Setting up log4j

log4j – is a replacement for system.out.printlns developers write for debugging. Production level code should not spit out these. So these printlns have to be hunted and commented out,source code compiled – and also has to be uncommented later in maintenance mode when something breaks – its a pain.

Log4j allows us to set logging on or off globally – so when code goes to production, all we have to do is set the logging level we need by editing a configuration file. Also there are lots of other advantages

* granularity can be controlled – debug,warn,info,error,fatal,log
* output can be anything – sysout, file etc.
* since its only job is to log it does an efficient job of it

Get log4j from good old apache.
http://logging.apache.org/log4j/docs/download.html

Under dist directory there is the jar file : log4j-1.2.9.jar

Copy it to TOMCAT_HOME/common/lib directory and also in antiPC/WEB-INF/lib.

Inside TOMCAT_HOME/common/classes, create a file called log4j.properties and type these into it.

log4j.rootLogger=info, R
log4j.appender.R=org.apache.log4j.ConsoleAppender
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%-5p %-30.30c{1} %x – %m%n

( I have no clue what all these appenders and layouts are – I just got it from log4j website. But it works 🙂 )

There are lot of steps involved before we can talk to the database. Heres what we are trying to accomplish.

When the server loads the application it will load a listener defined in WEB.XML file under the node . When the container initializes this class is called.

Now the listener reads the entries in WEB.XML for database driver name,database name,database username,password and created a DBHandler object – which is kind of a helper class that accepts sql statements and executes them for you. The listener puts this DBHandler object as an attribute in context. Think of context variables as a global variable for the entire application. So whenever any servlet when it wants to talk to the database it can get the DBHandler object from the context and use it.

Advantages of this long winded approach?

* Database connection code sits at one place
* Database details are stored in web.xml – so application need not be recompiled when any of the values change. Just a reload is sufficient.

So to start the long winded road- lets first enter the database related stuff in web.xml


<context-param>

<param-name>dbDriver</param-name>

<param-value>org.gjt.mm.mysql.Driver</param-value>

</context-param>

<context-param>

<param-name>dbDatabase</param-name>

<param-value>jdbc:mysql://localhost/antiPC</param-value>

</context-param>

<context-param>

<param-name>dbUsername</param-name>

<param-value>yourusername</param-value>

</context-param>

<context-param>

<param-name>dbPassword</param-name>

<param-value>yourpassword</param-value>

</context-param>


<listener>

<listener-class>

net.kvrlogs.antiPC.init.MyServletContextListener

</listener-class>

</listener>

Next save DBHandler.java under /WEB-iNF/src/net/kvrlogs/antipc/utils/DBHandler.java

package net.kvrlogs.antiPC.utils;

import java.sql.*;

import org.apache.log4j.*;

import java.util.*;

public class DBHandler {

static Logger _logger = Logger.getLogger(DBHandler.class.getName());

String _driverName = null;

String _databaseUrl = null;

Connection _dbConn = null;

PreparedStatement _pstmt = null;

String _queryString = null;

public DBHandler(String driverName, String databaseUrl, String _username,

String _password) {

String methodsig = "DBHandler.DBHandler()";

_logger.info(driverName + "," + databaseUrl + ","

+ _username + "," + _password);

//Verify that the driver class exists

try {

Class.forName(driverName);

} catch (Exception e) {

e.printStackTrace();

_logger.error("Driver could not be found: " + e);

return;

}

//Get a connection

try {

_dbConn = DriverManager.getConnection(databaseUrl, _username,

_password);

_driverName = driverName;

_databaseUrl = databaseUrl;

} catch (SQLException sqle) {

_logger.error( "Couldn't connect to database. "

+ sqle);

return;

}

//Log it

_logger.info("Connected to " + _databaseUrl);

}

public boolean setQueryString(String queryString) {

String methodsig = "DBHandler.setQueryString()";

if (_dbConn == null) {

_logger.error("Error!!! - Couldn't set the query string because the Connection was null. The DBHandler was not constructed correctly or the database does not exist.");

return false;

}

try {

_pstmt = _dbConn.prepareStatement(queryString);

_queryString = queryString;

} catch (SQLException sqle) {

_logger.error("Couldn't create PreparedStatement. " + sqle);

return false;

}

return true;

}

public void close() {

try {

_dbConn.close();

} catch (SQLException sqle) {

}

}

/**

*

* Do a select query on a prepared statement with no arguments

*

*/

public ResultSet lookup() {

String methodsig = "DBHandler.lookup(String)";

_logger.debug("Looking up query: '" + _queryString

+ "'");

try {

if (!_pstmt.execute()) {

_logger.debug("The resultset was empty on the lookup..");

}

return _pstmt.getResultSet();

} catch (SQLException sqle) {

_logger.debug("Couldn't connect to database, or create PreparedStatement. "

+ sqle);

return null;

}

}

/**

*

* Do a select query on a prepared statement with single argument,

*

* where the argument is a String

*

*/

public ResultSet lookup(String arg) {

String methodsig = "DBHandler.lookup(String)";

_logger.debug("Looking up: '" + arg + "' with query '"

+ _queryString + "'");

try {

_pstmt.setString(1, arg);

if (!_pstmt.execute()) {

_logger.debug("The resultset was empty on the lookup..");

}

return _pstmt.getResultSet();

} catch (SQLException sqle) {

_logger.debug("Couldn't connect to database, or create PreparedStatement. "

+ sqle);

return null;

}

}

/**

*

* Do a select query on a prepared statement with single argument,

*

* where the argument is an integer

*

*/

public ResultSet lookup(int arg) {

final String methodsig = "DBHandler.lookup(int)";

ResultSet returnval = lookup(Integer.toString(arg));

return returnval;

}

/**

*

* Do a select query on a prepared statement with a vector of arguments,

*

*/

public ResultSet lookup(Vector v) {

final String methodsig = "DBHandler.lookup(Vector)";

try {

//i is the a counter that places each variable in the vector into

// the

//proper place in the prepared statement

int i = 1;

for (Enumeration enum = v.elements(); enum.hasMoreElements();) {

_pstmt.setString(i++, (String) enum.nextElement());

}

if (!_pstmt.execute()) {

_logger.debug("The resultset was empty on the lookup..");

}

return _pstmt.getResultSet();

} catch (SQLException sqle) {

_logger.debug("Couldn't connect to database, or create PreparedStatement. "

+ sqle);

return null;

}

}

/**

*

* Insert a row of values (Vector of args) into a table

*

*/

public int insert(Vector args) {

String methodsig = "DBHandler.insert()";

//Create a string out of the args so we can pass it into the logfile

// message

String argString = new String("(");

for (Enumeration enum = args.elements(); enum.hasMoreElements();) {

Object o = enum.nextElement();

if (o instanceof Integer) {

argString = argString.concat((Integer) o + ",");

}

else {

argString = argString.concat((String) o + ",");

}

}

argString = argString.substring(0, argString.length() - 1);

argString = argString.concat(")");

_logger.debug("Inserting: '" + argString

+ "' with query '" + _queryString + "'");

//Create the prepared statement by passing in each argument of the

// vector

try {

for (int i = 0; i < args.size(); i++) {

Object o = args.elementAt(i);

// if (o == null) {

// _pstmt.setNull(i+1, 1);

// }

if (o instanceof Integer) {

_pstmt.setInt(i + 1, ((Integer) o).intValue());

}

else {

_pstmt.setString(i + 1, (String) o);

}

}

int updateResult = _pstmt.executeUpdate();

_logger.debug("The insert updated " + updateResult

+ " rows");

return updateResult;

} catch (SQLException sqle) {

_logger.debug("Couldn't connect to database, or create PreparedStatement. "

+ sqle);

//sqle.printStackTrace();

return 0;

}

}

/**

*

* Execute a query with no args

*

*/

public boolean execute() {

String methodsig = "DBHandler.execute()";

_logger.debug("Executing query: '" + _queryString

+ "'");

try {

boolean returnval = _pstmt.execute();

return returnval;

} catch (SQLException sqle) {

_logger.debug("Couldn't connect to database. "

+ sqle);

return false;

}

}

/**

*

* Execute an update query with a String as an arg

*

*/

public int executeUpdate(String arg) {

String methodsig = "DBHandler.executeUpdate(String)";

_logger.debug("Updating '" + arg

+ "' with query string '" + _queryString + "'");

try {

_pstmt.setString(1, arg);

int updateResult = _pstmt.executeUpdate();

_logger.debug("The updated affected "

+ updateResult + " rows");

return updateResult;

} catch (SQLException sqle) {

_logger.debug("Couldn't connect to database. "

+ sqle);

return 0;

}

}

/**

*

* Execute an update query with a Vector of Strings as args

*

*/

public int executeUpdate(Vector args) {

String methodsig = "DBHandler.executeUpdate(Vector)";

//Create a string out of the args so we can pass it into the logfile

// message

String argString = new String("(");

for (Enumeration enum = args.elements(); enum.hasMoreElements();) {

Object o = enum.nextElement();

if (o instanceof Integer) {

argString = argString.concat((Integer) o + ",");

}

else {

argString = argString.concat((String) o + ",");

}

}

argString = argString.substring(0, argString.length() - 1);

argString = argString.concat(")");

_logger.debug("Executing: '" + argString

+ "' with query '" + _queryString + "'");

//Create the prepared statement by passing in each argument of the

// vector

try {

for (int i = 0; i < args.size(); i++) {

Object o = args.elementAt(i);

if (o instanceof Integer) {

_pstmt.setInt(i + 1, ((Integer) o).intValue());

}

else {

_pstmt.setString(i + 1, (String) o);

}

}

int executeResult = _pstmt.executeUpdate();

_logger.debug("The execute updated returned '"

+ executeResult + "'");

return executeResult;

} catch (SQLException sqle) {

_logger.debug("Couldn't connect to database, or create PreparedStatement. "

+ sqle);

//sqle.printStackTrace();

return 0;

}

}

public String getDatabaseUrl() {

return _databaseUrl;

}

public String getDriverName() {

return _driverName;

}

public String getQueryString() {

return _queryString;

}

}

Next we define the listener.

Save the following code into WEB-INF/src/net.kvrlogs.antiPC.init as MyServletContextListener


package net.kvrlogs.antiPC.init;

import javax.servlet.*;

import net.kvrlogs.antiPC.utils.DBHandler;

public class MyServletContextListener implements ServletContextListener{

public void contextInitialized(ServletContextEvent event){

ServletContext cx = event.getServletContext();

String _databaseUrl = cx.getInitParameter("dbDatabase");

String _driverName = cx.getInitParameter("dbDriver");

String _user = cx.getInitParameter("dbUsername");

String _password = cx.getInitParameter("dbPassword");

DBHandler dbHandler = new DBHandler(_driverName,_databaseUrl,_user,_password);

cx.setAttribute("dbhandle",dbHandler);

}

public void contextDestroyed (ServletContextEvent event){

}

}

For now I am skipping writing DAO pattern – this post is getting long. Will just go ahead and put the dbhandler calls inside login action servlet itself.

Now add the following lines into Login.java


ServletContext context = getServlet().getServletContext();

DBHandler db = (DBHandler)context.getAttribute("dbhandle");

LoginFormBean loginBean = (LoginFormBean) form;

String username = loginBean.getUsername();

String password = loginBean.getPassword();

String sql = "SELECT * FROM antiPC_users where user_name = '" + username + "'" +

" and user_password = '" + password +"'" ;

db.setQueryString(sql);

ResultSet rs = db.lookup();

if(rs != null && rs.next())

return mapping.findForward("success");

else

return mapping.findForward("failure");

Now I have added a new mapping called failure. Lets define it in struts-config.xml and we are all set.

Add this below or above success mapping.

<forward

name="failure"

path="/pages/login.jsp"/>

Save all. Build. Stop antiPC. Start antiPC. Go to browser. Enter tester,tester ( or whatever value you have given in user table ) – see if it goes to You are in page. Enter wrong values – see if it takes you back to login page again.

You are now free to move about struts.ting.

ps: If you need the war file email me to kvrlogs@gmail.com

0 to struts in 60 minutes

Steps to build a struts application

Struts has become a standard for web applications. It lets you fit your web application in a MVC framework – which earlier I had wrongly thought – MVC is only for desktop GUI clients.

What is MVC?

M-Model
V-View
C-Controller

Model deals with the data – JavaBeans containing the data.
View deals with how you want to display the model
Controller – How the model and view are to be controlled

Lets take the example of a login process.

Here is the scenario : User enters login name, password hits submit. The form data are sent to a servlet that talks to the database to see if the information is valid. If it is valid, it puts the name, role information in a javabean and sends the browser to a welcome page. If not it sends the browser an error page.

Now with MVC scenario. When the user hits submit, the control does not directly go to the servlet but to a controller servlet. The form values are put in a javabean ( model ). The controller’s main job is to see what the request is and finds the correct servlet to work on it – in this case the login servlet. The login servlet then authenticates and sends either of these two words : success or failure. Also if the authentication succeeds it puts the name,role information into a javabean ( the model ). The controller if it sees the return value as “success” sends the browser the welcome page ( view ) . The welcome page takes the value from the javabean ( model ) and displays it. You might be thinking what do we call the login servlet – the model or the controller.You might also think it lies somewhere between the model and the controller – but actually its the model. It just tells the controller where the logic flow should be going next – but it cannot change the logic flow by itself. Only the almighty controller has the power to change the logic flow.

What are the advantages of MVC?

* The control logic is in one place. Otherwise the servlets, jsps have fun throwing the logic between one another – and when it comes to maintenance mode its a headache when one has to follow the trial. Now if we have all the logic flows in one place its enough to go to that one place and see how things flow.
In Struts this logic flow, controller, is held in an xml file – struts-config.xml. Now you can play with the logic flow – lets say for some reason you do not want to show the welcome page but want to directly take the users to another page. You need not touch the servlets ( means compiling, testing etc. ) – vs – you touch the struts-config.xml file – make the change you want to do – and restart the server. ( no recompiling, testing is necessary yes – but need not worry about the servlets throwing any exception etc.. as we never touched the servlets ).

* Lets say you want to toss out the current JSP view and have a new design. All you have to make sure is you display the javabeans in the right places. The Model and controller need not be touched at all. Isnt it beautiful !!

* Lets say you change the model – for instance your application goes the EJB way. Authentication is not done with a simple servlet but by a complex session bean. As long as the session bean creates the same javabean with name,role – the View(JSP page) need not be touched at all.

* Lets say tomorrow a new version of Struts comes – or another alternative to Struts comes along – it has a new controller – instead of struts-config.xml file they change a couple of things. No problem your model, view never get affected.

What you need?

Tomcat
MySQL
Eclipse
A web browser
Coffee/Tea

Where to start?

Go to the wonderful Apache site.
http://struts.apache.org/download.cgi

Download the binaries. Unzip it.

There will be a directory called webapps. There is a nice little template file called struts-blank.war. Rename it as antiPC.war ( anti ProCrastinator – thats the name of our struts app – you can name it whatever you want )

Start tomcat.
Go to tomcat manager ( http://localhost:8080 ) and deploy this war file. Tomcat will neatly unzip the war file and deploy the application too. Click on the link for antiPC

http://localhost:8080/antiPC

You should be able to see the welcome message. If not try to fix the problem. Look at catalina.out to see if there is any error messages, missing class library anything.

And then?

Time to start working on our struts application.
Fire up eclipse.
If you havent installed sysdeo eclipse plugin its a good idea to install it. Google for sysdeo plugin and load it in. Its useful.
Start a new Java Tomcat project (antiPC) and make it point to webapps/antiPC directory.

PS: If its not windows OS – eclipse might start throwing tantrums saying file not found. In your terminal editor navigate to the directory where webapps is. See the ownership of antiPC directory – it might be root. So do a
sudo chown -R youruserid antiPC

build.xml – the oxygen for your struts app !! It cannot live without it.

In eclipse navigate to antiPC/WEB-INF/src/build.xml

The build.xml cleans,compiles,generates javadoc and creates war file for deploying. We can remove the last 2 steps for now.

So make these changes

from :

<!-- Build entire project -->

<target name="project" depends="clean,prepare,compile,javadoc"/>

to:

<!-- Build entire project -->

<target name="project" depends="clean,prepare,compile"/>

and line 1 :

from :

<project name="blank" basedir="../" default="all">

to:

<project name="blank" basedir="../" default="project">

Click on the WEB-INF/src directory and click on Run as ant build. It should say something like this :

Buildfile: /usr/local/tomcat/webapps/antiPC/WEB-INF/src/build.xml
clean:
[delete] Deleting directory /usr/local/tomcat/webapps/antiPC/WEB-INF/classes
[mkdir] Created dir: /usr/local/tomcat/webapps/antiPC/WEB-INF/classes
prepare:
resources:
[copy] Copying 1 file to /usr/local/tomcat/webapps/antiPC/WEB-INF/classes
compile:
project:
BUILD SUCCESSFUL
Total time: 4 seconds

And thennnnn?

You are all set to play with your struts application. Next I will explain how to set up a login page, connect to database, etc.