Lab 4: JSP EL and JSTL
An expression language makes it possible to easily access application data stored in JavaBeans components. For example, the JSP expression language allows a page author to access a bean using simple syntax such as
${name}
for a simple variable or ${name.foo.bar}
for a nested property.Implicit Objects
The JSP expression language defines a set of implicit objects:
- pageContext: The context for the JSP page. Provides access to various objects including:
- servletContext: The context for the JSP page's servlet and any web components contained in the same application.
- session: The session object for the client.
- request: The request triggering the execution of the JSP page.
- response: The response returned by the JSP page.
- param: Maps a request parameter name to a single value
- paramValues: Maps a request parameter name to an array of values
- header: Maps a request header name to a single value
- headerValues: Maps a request header name to an array of values
- cookie: Maps a cookie name to a single cookie
- initParam: Maps a context initialization parameter name to a single value
- pageScope: Maps page-scoped variable names to their values
- requestScope: Maps request-scoped variable names to their values
- sessionScope: Maps session-scoped variable names to their values
- applicationScope: Maps application-scoped variable names to their values
${pageContext}
returns the PageContext
object, even if there is an existing pageContext
attribute containing some other value.Gradle
Modify the default created gradle file to match the following:
group 'mis283' apply plugin: 'java' apply plugin: 'war' apply from: 'https://raw.github.com/akhikhl/gretty/master/pluginScripts/gretty.plugin' sourceCompatibility = 1.8 repositories { mavenCentral() } gretty { httpPort = 8081 integrationTestTask = 'test' } dependencies { compile 'javax.servlet:jstl:1.2' compile 'javax.mail:mail:1.5.0-b01' compile 'javax.servlet:jstl:1.2' compile 'javax.servlet.jsp:javax.servlet.jsp-api:2.3.2-b02' testCompile 'junit:junit:4.12' testCompile 'org.httpunit:httpunit:1.7.2' }
CSS
Use the following style file for this lab session.
body { font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif; margin-left: 5em; margin-right: 5em; } label { float: left; font-weight: bold; width: 10em; } input[type="text"], input[type="password"], input[type="email"] { width: 20em; margin-left: 0.5em; margin-bottom: 0.5em; } input[type="submit"] { margin-left: 0.5em; margin-bottom: 0.5em; } table { border: 1px solid black; border-collapse: collapse; } th, td { border: 1px solid black; text-align: left; padding: 0.5em; } .bold { font-weight: bold; } #warning { font-size: 80%; color: tan; }
Obsolete JSP
We will look at primitive JSP that contains scriptlets, methods and code that should never be in a JSP. Add a file named bad.jsp under src/main/webapp.
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.text.SimpleDateFormat, java.util.Date, java.util.Enumeration" %> <!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" type="text/css" href="styles.css"/> <title>Time Of Day</title> </head> <body> <h1>Time of day</h1> <label>ISO 8601 date time:</label><aside id="dateTime"><%= dateTime() %></aside> <label>ISO 8601 date:</label><aside id="date"><%= date() %></aside> <label>Year:</label><aside id="year"><%= year() %></aside> <label>Month:</label><aside id="month"><%= month() %></aside> <label>Day:</label><aside id="day"><%= day() %></aside> <label>Time:</label><aside id="time"><%= time() %></aside> <label>Local date time:</label><aside id="local"><%= localDate() %></aside> <label>Numbers:</label><aside id="numbers"><% for ( int i = 0; i < 10; ++i ) { %><%= i %>, <% } %></aside> <table> <% final Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { final String name = (String) headerNames.nextElement(); final String value = split(request.getHeader(name)); %> <tr><td><%= name %></td><td><%= value %></td></tr> <% } %> </table> </body> </html> <%! private String year() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy"); final Date date = new Date(); return sdf.format(date); } private String month() { final SimpleDateFormat sdf = new SimpleDateFormat("MMMMM"); final Date date = new Date(); return sdf.format(date); } private String day() { final SimpleDateFormat sdf = new SimpleDateFormat("dd"); final Date date = new Date(); return sdf.format(date); } private String date() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); final Date date = new Date(); return sdf.format(date); } private String time() { final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSXXX"); final Date date = new Date(); return sdf.format(date); } private String dateTime() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); final Date date = new Date(); return sdf.format(date); } private String localDate() { return new Date().toString(); } private String split(String value) { final StringBuilder sb = new StringBuilder(value.length() + 32); final String[] parts = value.split(";"); for (final String part : parts) { sb.append(part); if (!part.equals(parts[parts.length-1])) sb.append("<br/>"); } return sb.toString(); } %>
We can adapt the unit test from lab2 to do some validation on the output of the JSP page. Create mis283.BadTest under src/test/java
package mis283; import com.meterware.httpunit.GetMethodWebRequest; import com.meterware.httpunit.WebConversation; import com.meterware.httpunit.WebRequest; import com.meterware.httpunit.WebResponse; import org.junit.BeforeClass; import org.junit.Test; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import static java.lang.String.format; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class BadTest { private static final Map<String, String> components = new HashMap<>(); private static WebConversation conversation; private static WebRequest request; private static WebResponse response; static { final Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy"); components.put("year", sdf.format(date)); sdf = new SimpleDateFormat("MM"); components.put("month", sdf.format(date)); sdf = new SimpleDateFormat("MMMMM"); components.put("Month", sdf.format(date)); sdf = new SimpleDateFormat("dd"); components.put("day", sdf.format(date)); sdf = new SimpleDateFormat("HH"); components.put("hour", sdf.format(date)); sdf = new SimpleDateFormat("EEE"); components.put("dayOfWeek", sdf.format(date)); } @BeforeClass public static void init() throws Exception { conversation = new WebConversation(); request = new GetMethodWebRequest("http://localhost:8081/lab4/bad.jsp"); response = conversation.getResponse( request ); } @Test public void dateTime() throws Exception { final String value = response.getElementWithID("dateTime").getText(); assertTrue("Wrong year in response", value.startsWith(components.get("year"))); assertTrue("Wrong month in response", value.contains(format("-%s-", components.get("month")))); assertTrue("Wrong day in response", value.contains(format("-%sT", components.get("day")))); assertTrue("Wrong hour in response", value.contains(format("T%s:", components.get("hour")))); } @Test public void date() throws Exception { final String value = response.getElementWithID("date").getText(); assertTrue("Wrong year in response", value.startsWith(components.get("year"))); assertTrue("Wrong month in response", value.contains(format("-%s-", components.get("month")))); assertTrue("Wrong day in response", value.endsWith(components.get("day"))); } @Test public void year() throws Exception { final String value = response.getElementWithID("year").getText(); assertEquals("Wrong year in response", value, components.get("year")); } @Test public void month() throws Exception { final String value = response.getElementWithID("month").getText(); assertEquals("Wrong month in response", value, components.get("Month")); } @Test public void day() throws Exception { final String value = response.getElementWithID("day").getText(); assertEquals("Wrong day in response", value, components.get("day")); } @Test public void time() throws Exception { final String value = response.getElementWithID("time").getText(); assertTrue("Wrong hour in response", value.startsWith(components.get("hour"))); } @Test public void local() throws Exception { final String value = response.getElementWithID("local").getText(); assertTrue("Wrong day of week in response", value.startsWith(components.get("dayOfWeek"))); } }
Bean
We will create a JavaBean style utility class that will provide the date functions we need to access in our JSP. Create a Java class mis283.DateUtil under src/main/java
package mis283; import java.text.SimpleDateFormat; import java.util.Date; public class DateUtil { public String getYear() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy"); final Date date = new Date(); return sdf.format(date); } public String getMonth() { final SimpleDateFormat sdf = new SimpleDateFormat("MMMMM"); final Date date = new Date(); return sdf.format(date); } public String getDay() { final SimpleDateFormat sdf = new SimpleDateFormat("dd"); final Date date = new Date(); return sdf.format(date); } public String getDate() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); final Date date = new Date(); return sdf.format(date); } public String getTime() { final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSXXX"); final Date date = new Date(); return sdf.format(date); } public String getDateTime() { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); final Date date = new Date(); return sdf.format(date); } public String getLocalDate() { return new Date().toString(); } }
Modern JSP
We can reimplement the JSP using Expression Language (EL) and JSTL as follows. Note that we are using the DateUtil bean class to provide the date information to be displayed on screen.
This site gives a good tutorial to JSTL and the Functions tag library available for use with JSP.
This site gives a good tutorial to JSTL and the Functions tag library available for use with JSP.
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <jsp:useBean id="dateUtil" scope="page" class="mis283.DateUtil"/> <!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" type="text/css" href="styles.css"/> <title>Time Of Day</title> </head> <body> <h1>Time of day</h1> <label>ISO 8601 date time:</label><aside id="dateTime">${dateUtil.dateTime}</aside> <label>ISO 8601 date:</label><aside id="date">${dateUtil.date}</aside> <label>Year:</label><aside id="year">${dateUtil.year}</aside> <label>Month:</label><aside id="month">${dateUtil.month}</aside> <label>Day:</label><aside id="day">${dateUtil.day}</aside> <label>Time:</label><aside id="time">${dateUtil.time}</aside> <label>Local date time:</label><aside id="local">${dateUtil.localDate}</aside> <label>Numbers:</label><aside id="numbers"><c:forEach begin="0" end=“9” varStatus="loop">${loop.index}, </c:forEach></aside> <table> <c:forEach items="${headerValues}" var="keyValue"> <tr> <td>${keyValue.key}</td> <td><c:forEach items="${keyValue.value}" var="headerValue"> <c:forTokens items="${headerValue}" delims=";" var="value"><c:out value="${value}"/></br></c:forTokens> </c:forEach></td> </c:forEach> </table> </body> </html>
Tests
We can refactor the test we used for bad.jsp and make it run against both the JSP files. We will extract a Base super class, from which we will inherit BadTest and GoodTest.
Base
Base
package mis283; import com.meterware.httpunit.GetMethodWebRequest; import com.meterware.httpunit.WebConversation; import com.meterware.httpunit.WebRequest; import com.meterware.httpunit.WebResponse; import org.junit.BeforeClass; import org.junit.Test; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import static java.lang.String.format; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public abstract class Base { private static final Map<String, String> components = new HashMap<>(); protected static WebConversation conversation; protected static WebRequest request; protected static WebResponse response; static { final Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy"); components.put("year", sdf.format(date)); sdf = new SimpleDateFormat("MM"); components.put("month", sdf.format(date)); sdf = new SimpleDateFormat("MMMMM"); components.put("Month", sdf.format(date)); sdf = new SimpleDateFormat("dd"); components.put("day", sdf.format(date)); sdf = new SimpleDateFormat("HH"); components.put("hour", sdf.format(date)); sdf = new SimpleDateFormat("EEE"); components.put("dayOfWeek", sdf.format(date)); } @Test public void dateTime() throws Exception { final String value = response.getElementWithID("dateTime").getText(); assertTrue("Wrong year in response", value.startsWith(components.get("year"))); assertTrue("Wrong month in response", value.contains(format("-%s-", components.get("month")))); assertTrue("Wrong day in response", value.contains(format("-%sT", components.get("day")))); assertTrue("Wrong hour in response", value.contains(format("T%s:", components.get("hour")))); } @Test public void date() throws Exception { final String value = response.getElementWithID("date").getText(); assertTrue("Wrong year in response", value.startsWith(components.get("year"))); assertTrue("Wrong month in response", value.contains(format("-%s-", components.get("month")))); assertTrue("Wrong day in response", value.endsWith(components.get("day"))); } @Test public void year() throws Exception { final String value = response.getElementWithID("year").getText(); assertEquals("Wrong year in response", value, components.get("year")); } @Test public void month() throws Exception { final String value = response.getElementWithID("month").getText(); assertEquals("Wrong month in response", value, components.get("Month")); } @Test public void day() throws Exception { final String value = response.getElementWithID("day").getText(); assertEquals("Wrong day in response", value, components.get("day")); } @Test public void time() throws Exception { final String value = response.getElementWithID("time").getText(); assertTrue("Wrong hour in response", value.startsWith(components.get("hour"))); } @Test public void local() throws Exception { final String value = response.getElementWithID("local").getText(); assertTrue("Wrong day of week in response", value.startsWith(components.get("dayOfWeek"))); } }
Bad Test
package mis283; import com.meterware.httpunit.GetMethodWebRequest; import com.meterware.httpunit.WebConversation; import org.junit.BeforeClass; public class BadTest extends Base { @BeforeClass public static void init() throws Exception { conversation = new WebConversation(); request = new GetMethodWebRequest("http://localhost:8081/lab4/bad.jsp"); response = conversation.getResponse( request ); } }
Good Test
package mis283; import com.meterware.httpunit.GetMethodWebRequest; import com.meterware.httpunit.WebConversation; import org.junit.BeforeClass; public class GoodTest extends Base { @BeforeClass public static void init() throws Exception { conversation = new WebConversation(); request = new GetMethodWebRequest("http://localhost:8081/lab4/good.jsp"); response = conversation.getResponse( request ); } }
Custom Tag
We will develop a custom tag that will display the details relevant to a Person class (the same that was used in lab3). Copy over the class and the servlet for handling form posts along with the html and jsp files and test class from lab3. You will need to modify the PersonCreateTest with the new URL for lab4.
Modify the personView.jsp as follows:
Modify the personView.jsp as follows:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="lab4" uri="http://morainevalley.edu/mis283/tlds/person" %> <html> <head> <link rel="stylesheet" type="text/css" href="../styles.css"/> <title>Person - ${person.name}</title> </head> <body> <section> <h1>Newly created Person</h1> <lab4:person bean="${person}"/> </section> <footer> Demo application that illustrates JSP EL and custom tags </footer> </body> </html>
We will now write our tag library descriptor (tld) file.
Create a directory named WEB-INF under src/main/webapp and create a file named person.tld under it.
Create a directory named WEB-INF under src/main/webapp and create a file named person.tld under it.
<taglib version="2.0" xmlns="http://java.sun.com/xml/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"> <tlib-version>2.1</tlib-version> <jsp-version>2.2</jsp-version> <short-name>Person Tag</short-name> <tag> <name>person</name> <tag-class>mis283.tag.PersonTag</tag-class> <body-content>empty</body-content> <attribute> <name>bean</name> <required>true</required> <type>mis283.model.Person</type> <rtexprvalue>true</rtexprvalue> </attribute> </tag> </taglib>
Create the web application deployment descriptor file web.xml under the same directory.
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>JSP EL and Custom Tags</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> <jsp-config> <taglib> <taglib-uri>http://morainevalley.edu/mis283/tlds/person</taglib-uri> <taglib-location>/WEB-INF/person.tld</taglib-location> </taglib> </jsp-config> </web-app>
Tag
Create the mis283.tag.PersonTag class under src/main/java as follows.
package mis283.tag; import mis283.model.Person; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.SimpleTagSupport; import java.io.IOException; import static mis283.model.Person.notEmpty; public class PersonTag extends SimpleTagSupport { private Person bean; @Override public void doTag() throws JspException, IOException { final JspWriter writer = getJspContext().getOut(); writeId(writer); writeFirstName(writer); writeMiddleName(writer); writeLastName(writer); writeEmail(writer); writeUsername(writer); } public void setBean(Person bean) { this.bean = bean; } private void writeId(final JspWriter writer) throws IOException { writer.print("<label>Person ID:</label><q id=\"id\">"); writer.print(bean.getId()); writer.println("</q><br/>"); } private void writeFirstName(final JspWriter writer) throws IOException { writer.print("<label>First Name:</label><q id=\"firstName\">"); writer.print(bean.getFirstName()); writer.println("</q><br/>"); } private void writeMiddleName(final JspWriter writer) throws IOException { if (notEmpty(bean.getMiddleName())) { writer.print("<label>Middle Name:</label><q id=\"middleName\">"); writer.print(bean.getMiddleName()); writer.println("</q><br/>"); } } private void writeLastName(final JspWriter writer) throws IOException { writer.print("<label>Last Name:</label><q id=\"lastName\">"); writer.print(bean.getLastName()); writer.println("</q><br/>"); } private void writeEmail(final JspWriter writer) throws IOException { writer.print("<label>E-mail:</label><q id=\"email\">"); writer.print(bean.getEmail()); writer.print("</q><br/>"); } private void writeUsername(final JspWriter writer) throws IOException { writer.print("<label>Username:</label><q id=\"username\">"); writer.print(bean.getUsername()); writer.println("</q><br/>"); } }