JSPs, Version 2

If you look at the original approach then you will no doubt be able to discern the complexity. While not a problem for someone like me who understands HTML as well as Java and JavaScript, it would be enough to give others nightmares! A different tack is one which completely separates the HTML author from the Java programmer. The general concept is that the JSPs don't include any Java code and the Java code doesn't generate any HTML.

This is an intelligent approach, allowing each group to focus on their particular area of expertise. It doesn't always work out so cleanly in the execution. After reading an excellent treatise on Advanced JavaServer Pages I marvelled at the clean division achieved in the early sections of the book. The case study demonstrated that it's not always that simple. I went back to the original code to see how I could clean it up. Here's the resulting JSP:

<%@ taglib uri="tc.tld" prefix="tc" %>
<head>
<title>Demo Page</title>
</head>
<body>
<tc:updateSkills/>
<form name="testForm" method="POST">
<p>
<h2>Select skill</h2>
<p>
<tc:getUnselectedSkills/>
<p>
<h2>Select skill level</h2>
<p>
<tc:getSelectedSkills/>
<p>
Click <input type="submit" name="submit" value=">>"> to go to next page.
</form>
</body>
So where did all the complexity go? It went into three custom tags. The tags are described in the tc.tld file which looks like this:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
	<tag>
		<name>getSelectedSkills</name>
		<tagclass>tags.GetSelectedSkills</tagclass>
		<bodycontent>JSP</bodycontent>
	</tag>
	<tag>
		<name>getUnselectedSkills</name>
		<tagclass>tags.GetUnselectedSkills</tagclass>
		<bodycontent>JSP</bodycontent>
	</tag>
	<tag>
		<name>updateSkills</name>
		<tagclass>tags.UpdateSkills</tagclass>
		<bodycontent>empty</bodycontent>
	</tag>
</taglib>
Now before I include the classes which implement the tags, it should be mentioned that there is a particular directory structure for web applications. The WAR files used by both J2EE and servlet containers such as Tomcat encapsulate the structure. Here's my structure, starting at the application root:
+-getSkill.jsp
+-tc.tld
+-WEB-INF
  +-web.xml
  +-lib
  | +-oracle.jar
  +-classes
    +-beans
    | +-SkillLevel.java
    +-tags
      +-UpdateSkills.java
      +-GetSelectedSkills.java
      +-GetUnselectedSkills.java
The oracle.jar file contains the classes required to interface to the local Oracle database and exists within the application structure in order to be accessible to application elements. The beans directory name is a bit of a misnomer as SkillLevel.java doesn't extend java.beans.Beans at all. It extends java.lang.Object to provide me with a convenience class. The gist of the application is located in the tags directory.

The three tags extend javax.servlet.jsp.tagext.BodyTagSupport even though the updateSkills tag specifies that the body is empty. It was just easier to be consistent. I should also mention that I use a SELECT as well as various FORM fields in this demonstration. You might also note that you don't see the field names specified anywhere. This is because these particular tags are designed to work together. I could have cobbled together more flexible naming but it would have come at the cost of more cumbersome specification on the part of the HTML author. I'll come back to the customization issue later. So here's what UpdateSkills.jsp looks like:

package	tags;

import	java.util.Vector;
import	javax.servlet.http.HttpSession;
import	javax.servlet.http.HttpServletRequest;
import	javax.servlet.http.HttpServletResponse;
import	javax.servlet.jsp.JspException;
import	javax.servlet.jsp.tagext.BodyTagSupport;
import	beans.SkillLevel;

public class UpdateSkills extends BodyTagSupport {

	private static final String	prefix = "skill_";
	private static final String	select = "skills";
	private static final String	deleteCmd = "Delete";
	private static final String	addCmd = "Add";

	public int doStartTag() throws JspException {
		HttpServletRequest	req = (HttpServletRequest)
		  pageContext.getRequest();
		HttpServletResponse	resp = (HttpServletResponse)
		  pageContext.getResponse();
		HttpSession		sess = req.getSession( true );
		String			user =
		  (String) pageContext.getAttribute( "user" );
		int	userId = 0;
		try {
			userId = Integer.parseInt( user );
		}
		catch( NumberFormatException e ) {
			throw( new JspException( e.toString() ) );
		}

		/*
		 * if we don't already have a vector of skills
		 * then load them from the database
		 */

		Vector	skills = (Vector) sess.getValue( "skills" );
		if( skills == null ) {
			try {
				skills = getSkills( userId );
			}
			catch( SQLException e ) {
				throw( new JspException( getClass().getName() +
				  ": SQLException: " + e.getSQLState() ) );
			}
			catch( ClassNotFoundException e ) {
				throw( new JspException(
				  getClass().getName() + ": " + e.getMessage() +
				  ": not found" ) );
			}
			sess.putValue( "skills", skills );
		}

		String	cmd = req.getParameter( "submit" );
		String	target = null;
		Enumeration	e = req.getParameterNames();
		Connection	conn = null;
		boolean		modified = false;
		while( e.hasMoreElements() ) {
			String	name = (String) e.nextElement();
			if( ! name.startsWith( prefix ) )
				continue;
			String	vals[] = req.getParameterValues( name );
			name = name.substring( prefix.length() );
			for( int i = 0; i < vals.length; i++ ) {
				if( vals[i].equals( deleteCmd ) )
					conn = setSkill( conn, skills, userId,
					  name, -1 );
				else
					conn = setSkill( conn, skills, userId,
					  name, vals[i] );
				modified = true;
			}
		}
		if( ( cmd != null ) && ( cmd.equals( addCmd ) ) ) {
			target = req.getParameter( select );
			conn = setSkill( conn, skills, userId, target, 3 );
			modified = true;
		}
		if( conn != null ) {
			try {
				conn.close();
			}
			catch( SQLException f ) {
				throw( new JspException( getClass().getName() +
				  ": SQLException: " + f.getSQLState() ) );
			}
		}
		if( modified )
			sess.putValue( "skills", skills );
		return( SKIP_BODY );
	}

	public int doEndTag() throws JspException {
		return( EVAL_PAGE );
	}
}
I didn't include getSkills or setSkill since it's standard JDBC code. It would be properly done with beans but the important concept here is that I create a Vector of SkillLevel objects which I then store in the session context. And you can also see why I didn't leave it up to the HTML programmer to choose field identifiers. I require certain patterns for the application to function properly. Here's the SkillLevel.java source:
package	beans;

public class SkillLevel {

	private	int	id;
	private String	desc;
	private int	level;

	public SkillLevel( int id, String desc, int level ) {
		this.id = id;
		this.desc = desc;
		this.level = level;
	}

	public int getId() {
		return( id );
	}

	public String getDesc() {
		return( desc );
	}

	public int getLevel() {
		return( level );
	}

	public void setLevel( int level ) {
		this.level = level;
	}
}
So it's just a container for a skill identifier, description and the level of a particular user. Now we need to create the selection for those skills which haven't already been selected by the user. Here's that code:
package	tags;

import	beans.SkillLevel;
import	java.io.IOException;
import	java.util.Vector;
import	javax.servlet.http.Cookie;
import	javax.servlet.http.HttpSession;
import	javax.servlet.http.HttpServletRequest;
import	javax.servlet.http.HttpServletResponse;
import	javax.servlet.jsp.JspWriter;
import	javax.servlet.jsp.JspException;
import	javax.servlet.jsp.tagext.BodyTagSupport;

public class GetUnselectedSkills extends BodyTagSupport {

	public int doStartTag() throws JspException {
		HttpServletRequest	req = (HttpServletRequest)
		  pageContext.getRequest();
		HttpServletResponse	resp = (HttpServletResponse)
		  pageContext.getResponse();
		HttpSession		sess = req.getSession( true );
		String			user =
		  (String) pageContext.getAttribute( "user" );
		if( user == null ) {
			throw( new JspException( "No user defined" ) );
		}
		Vector	skills = (Vector) sess.getValue( "skills" );
		if( skills == null ) {
			throw( new JspException( "No skills defined" ) );
		}

		JspWriter	out = pageContext.getOut();

		SkillLevel	skill = null;
		try {
			out.println( "<select name=\"skills\">" );
			for( int i = 0; i < skills.size(); i++ ) {
				skill = (SkillLevel) skills.elementAt( i );
				if( skill.getLevel() >= 0 )
					continue;
				out.println( "<option value=\"" +
				  skill.getId() + "\">" + skill.getDesc() +
				  "</option>" );
			}
			out.println( "</select>" );
			out.println( "<input type=\"submit\" name=\"submit\" value=\"Add\">" );
		}
		catch( IOException e ) {
			throw( new JspException( getClass().getName() + ": " +
			  e.getMessage() ) );
		}
		return( SKIP_BODY );
	}

	public int doAfterBody() throws JspException {
		if( true )
			return( EVAL_BODY_BUFFERED );
		else
			return( SKIP_BODY );
	}

	public int doEndTag() throws JspException {
		return( EVAL_PAGE );
	}
}
This is pretty simple: we obtain the Vector of SkillLevels from the session context and walk through it, creating an option for any skills which the user has not previously selected. I use the skill level of -1 to indicate unselected which is a fairly common practice. The code for GetSelectedSkills isn't that much more complex:
package	tags;

import	beans.SkillLevel;
import	java.io.IOException;
import	java.util.Vector;
import	javax.servlet.http.Cookie;
import	javax.servlet.http.HttpSession;
import	javax.servlet.http.HttpServletRequest;
import	javax.servlet.http.HttpServletResponse;
import	javax.servlet.jsp.JspWriter;
import	javax.servlet.jsp.JspException;
import	javax.servlet.jsp.tagext.BodyTagSupport;

public class GetSelectedSkills extends BodyTagSupport {

	public int doStartTag() throws JspException {
		HttpServletRequest	req = (HttpServletRequest)
		  pageContext.getRequest();
		HttpServletResponse	resp = (HttpServletResponse)
		  pageContext.getResponse();
		HttpSession		sess = req.getSession( true );
		String			user =
		  (String) pageContext.getAttribute( "user" );
		if( user == null ) {
			throw( new JspException( "No user defined" ) );
		}
		Vector	skills = (Vector) sess.getValue( "skills" );
		if( skills == null ) {
			throw( new JspException( "No skills defined" ) );
		}

		JspWriter	out = pageContext.getOut();

		SkillLevel	skill = null;
		String		skillName = null;
		try {
			boolean	printed = false;
			for( int i = 0; i < skills.size(); i++ ) {
				skill = (SkillLevel) skills.elementAt( i );
				if( skill.getLevel() < 0 )
					continue;
				if( !printed ) {
					out.println( "<table border=\"2\">" );
					out.println( "<tr>" );
					out.println( "<th rowspan=\"2\">Skill</th>" );
					out.println( "<th colspan=\"5\" align=\"center\">Level of experience</th>" );
					out.println( "<th rowspan=\"2\">Remove</th>" );
					out.println( "</tr>" );
					out.println( "<tr>" );
					out.println( "<th align=\"center\">Exposure</th>" );
					out.println( "<th> </th>" );
					out.println( "<th align=\"center\">Working</th>" );
					out.println( "<th> </th>" );
					out.println( "<th align=\"center\">Expert</th>" );
					out.println( "</tr>" );
					printed = true;
				}
				skillName = "skill_" + skill.getId();
				out.println( "<tr>" );
				out.println( "<td>" + skill.getDesc() + "</td>" );
				for( int j = 1; j <= 5; j++ ) {
					out.println( "<td width=\"15%\" align=\"center\">" );
					out.print( "<input type=\"RADIO\" name=\"" + skillName + "\" value=\"" + j + "\"" );
					if( skill.getLevel() == j )
						out.print( " checked" );
					out.println( ">" );
					out.println( "</td>" );
				}
				out.println( "<td>" );
				out.println( "<input type=\"Submit\" name=\"" +
				  skillName + "\" value=\"Delete\">" );
				out.println( "</td>" );
				out.println( "</tr>" );
			}
			if( printed )
				out.println( "</table>" );
			else
				out.println( "<h3>No skills selected</h3>" );
		}
		catch( IOException e ) {
			throw( new JspException( getClass().getName() + ": " +
			  e.getMessage() ) );
		}
		return( SKIP_BODY );
	}

	public int doAfterBody() throws JspException {
		if( true )
			return( EVAL_BODY_BUFFERED );
		else
			return( SKIP_BODY );
	}

	public int doEndTag() throws JspException {
		return( EVAL_PAGE );
	}
}
If you ignore the code which generates the actual HTML for the table, it's essentially the same as GetUnselectedSkills except for the insertion of the selection criteria. I also skip the table completely if no skills have been selected and generate a level 3 header.

So you might be wondering what's left for the HTML programmer. Quite a bit, actually! Remember, their concern is the "look and feel" of pages and ensuring consistency within a website. This JSP can be easily included as merely a component of a complete page which might include fancy headers, navigation bars, etc. There's also the ability to customize appearance using cascading stylesheets (CSS). I used the following in my testing:

<style type="text/css">
th { font: bold 16px Arial }
td { font: 14px Arial }
</style>
Very simple but it serves to prove the point. I could have set things like the default paragraph font, background colours or images, etc. The HTML author actually has quite a lot of control, even though they can't choose the field names. It would be simple to modify the custom tags to generate custom style identifiers for the generated elements, providing even more customization possibilities.

This was a fairly simple example but I feel that it demonstrates both the capabilities and limitations of JSP. It's difficult to achieve a completely clean demarcation between JSP and the tags. On balance, I'd rather that the Java programmer be required to generate HTML tags than expect HTML programmers to learn the Java language so that they can insert it into their own JSP code. But then that's just my personal opinion.

Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 by Phil Selby