Java Webapps

Overview

A Java web application (webapp) is a web application built along the lines of the Servlet Specification. A webapp is usually part of a larger enterprise application. The (one and only) framework for Java enterprise applications is called Java Platform, Enterprise Edition, or Java EE. The main competitor to Java EE is .NET (dot net).

The Java webapp is run within a servlet container, which expects the webapp to have the following structure:

simplewebapptree.png

The webapp is normally bundled into a .war file, but some containers will run your webapp in exploded form.

Diving Right In

A great way to learn about webapps is to install and run both a webserver and a database on your own computer. Let's do that. We'll install Tomcat and MySQL. For concreteness, we'll assume you're running Windows, though it's easy to adjust the instructions for another O.S. We're making a Windows Tomcat MySQL JSP.

  1. Download and install a JDK if you don't have one on your computer. It will go into a directory named something like C:\Program Files\Java\jdk1.5.0_06.
  2. Add the environment variable JAVA_HOME with the value of this directory, if it was not added as part of the install.
  3. Download and install Tomcat (version 5 or later). Install it into c:\tomcat5.
  4. Set the environment variable CATALINA_HOME with the value of this directory, if that was not done as part of the install.
  5. Run tomcat by executing c:\tomcat5\bin\startup.bat. You'll see a console window where Tomcat (the webserver) will write messages during the course of its run. (Messages will also be written to log files under c:\tomcat5\logs.)
  6. See if Tomcat is running fine. Startup a web browser and enter the URI http://localhost:8080/. You should see a page that says "If you're seeing this page via a web browser, it means you've setup Tomcat successfully. Congratulations!"
  7. Let's make a webapp that lets users browse a database of movies. We want to make a webapp called "movies". Normally, webapps are packaged into a .war file, so we'd like to make the file movies.war. However, during development, making the war file takes time, so Tomcat lets you have your webapp as simply a collection of files rooted at some directory. Make a directory c:\mywebapps\movies.
  8. We have to let Tomcat know about our new webapp. This is done by placing a context file into its configuration area. Create a new file called movies.xml in the directory c:\tomcat5\conf\Catalina\localhost. The file should look like this:
    <Context docBase="c:/mywebapps/movies" reloadable="true" />
    
  9. Write your own HTML page and see if the server can serve it up. Call it test.html and put it in c:\mywebapps\movies. It could say something like:
    <html>
      <head><title>Example</title></head>
      <body>
        <p>Hello, there.</p>
      </body>
    </html>
    

    Then go to http://localhost:8080/movies/test.html in the browser. See your page?

  10. Now, let's make a dynamic page. Make a file called numbers.jsp and put it in the same directory as your last HTML file. A JSP file looks like HTML but has additional special markup that executes Java code to (dynamically) generate a custom response. JSP includes an expression language called EL. Note the EL expressions inside the "${" and "}" delimiters.
    <html>
      <head><title>Numbers</title></head>
      <style>.footer {border-top:1px solid gray;margin-top:2em}</style>
      <body>
        <p>Hello from the ${pageContext.request.contextPath}
        web app at ${header.host}.
        Did you know two plus two is ${2 + 2}?</p>
        <p class="footer">Server: ${pageContext.servletContext.serverInfo}</p>
      </body>
    </html>
    
  11. Now go to http://localhost:8080/movies/numbers.jsp in the browser. See your page?
  12. Now let's make a JSP page that can accept parameters. We'll expect parameters named x and y, and we'll send back a page that reports the sum. Make the following as sum.jsp:
    <html>
      <head><title>The Sum</title></head>
      <body>
        <p>The sum is ${param.x + param.y}</p>
      </body>
    </html>
    
  13. Test by going to http://localhost:8080/movies/sum.jsp?x=20&y=139. Experiment with different values of x and y, including huge numbers, negative numbers, non-numeric values. Try extra paramters. Hit the page without x, without y, and without x and y.
  14. Now let's do some server-side programming. This used to be done by embedding Java code into a JSP page, but these days we use special things called taglibs instead. There are a few standard taglibs out there that everyone uses, or you can write your own. The Java Standard Tag Library, or JSTL, is somewhat of an official standard and is widely used. It comes in two files, standard.jar and jstl.jar. Fetch these files by going to the "Jakarta Taglibs" site and navigating to a page entitled "Standard 1.1 Taglib Downloads". Get the zip file and extract the two jar files into c:\mywebapps\movies\WEB-INF\lib.
  15. Create the file multiplicationtable.jsp as follows:
    <%--
      This page takes in a parameter called size, which
      should be an integer between 2 and 20.  If missing
      it assumes 10.  The page renders a multiplication
      table with size rows and size columns.
    --%>
    
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
    <html>
      <head>
        <title>Multiplication Table</title>
        <style>
          table {border-collapse: collapse}
          table, td {border: thin solid green}
          td {padding: 0.2em; text-align: right}
        </style>
      </head>
    
      <body>
        <c:choose>
          <c:when test="${empty param.size or param.size lt 2 or param.size gt 20}">
            <c:set var="n" value="${10}"/>
          </c:when>
          <c:otherwise>
            <c:set var="n" value="${param.size}"/>
          </c:otherwise>
        </c:choose>
    
        <table>
        <c:forEach var="i" begin="1" end="${n}">
          <tr>
            <c:forEach var="j" begin="1" end="${n}">
              <td>${i * j}</td>
            </c:forEach>
          </tr>
        </c:forEach>
        </table>
      </body>
    </html>
    
    and test with various values for the size parameter. The taglib jar files you added make the <c:forEach>, <c:when> and other tags work. (If Tomcat didn't automatically notice that the jar files just got dropped in, and gives you an error message, stop and restart Tomcat).
  16. Now download and install the MySQL database server. You can put it in c:\Program Files\MySQL\MySQL Server 5.0.
  17. Now download and install the MySQL Query client application, unless you like typing queries through a command line interface. You can put it in c:\Program Files\MySQL\MySQL Query Browser 1.1.
  18. Start the database server. We'll do it from a console window for simplicity, but you'd really want to launch it in the background and monitor the generated log file(s).
        cd "\Program Files\MySQL\MySQL Server 5.0"
        mysqld --console
    

    The server is now running on port 3306.

  19. Before you secure your setup by creating accounts and assigning passwords, go to another console and try running some programs to make sure you can connect to the database server. For example:
        cd "\Program Files\MySQL\MySQL Server 5.0"
        mysqlshow -u root
        mysqlshow -u root mysql
        mysql -u root -e "select host, db, user from db" mysql
    

    This shows you that these commands have to be run as user root. When you first install MySQL the user root has no password; if you have an existing MySQL installation and root has a password, add the "-p" option and you'll be prompted for a password.

  20. Launch MySQL Query. In the query window, type in
        create database movies
    

    and execute with Ctrl+Enter. Right-click in the schemata window and select "Refresh". You will see the new database added to the three initial databases.

  21. Double click on the new entry in the Schemata tab to make the movies database the working database.
  22. Execute the following script. You'll have to do "File | New Script Tab", paste in the script, then hit the Execute button.

    create_movie_database.sql
    -- This script creates the Movie database, wiping
    -- out any data thay may already exist.
    
    drop table if exists Actor;
    drop table if exists Director;
    drop table if exists Person;
    drop table if exists Movie;
    
    create table Movie (
      id integer not null primary key,
      title varchar(255),
      rating varchar(10),
      length integer,                 -- length in minutes
      releaseDate date);
      
    create table Person (
      id integer not null primary key, 
      name varchar(255));
    
    create table Director (
      personId integer not null,
      movieId integer not null,
      primary key (personId, movieId),
      foreign key (personId) references Person(id),
      foreign key (movieId) references Movie(id));
    
    create table Actor (
      personId integer not null,
      movieId integer not null,
      characterName varchar(255),
      primary key (personId, movieId),
      foreign key (personId) references Person(id),
      foreign key (movieId) references Movie(id));
    
    insert into Movie (id, title, rating, length, releaseDate)
      values (1, 'Snow White and the Seven Dwarfs', 'G', 84, '1937-12-21');
    insert into Movie (id, title, rating, length, releaseDate)
      values (2, 'Lilo and Stitch', 'PG', 85, '2002-07-21');
    insert into Movie (id, title, rating, length, releaseDate)
      values (3, 'Alice in Wonderland', 'G', 75, '1951-07-26');
    insert into Movie (id, title, rating, length, releaseDate)
      values (4, 'The Emporer''s New Groove', 'G', 77, '2000-12-15');
    insert into Movie (id, title, length, releaseDate)
      values (5, 'Saludos Amigos', 75, '1942-08-24');
    insert into Movie (id, title, rating, length, releaseDate)
      values (6, 'The Three Caballeros', 'G', 71, '1944-12-21');
    insert into Movie (id, title, rating, length, releaseDate)
      values (7, 'The Jungle Book', 'G', 78, '1967-10-18');
    
    insert into Person (id, name) values (1, 'Adriana Caselotti');
    insert into Person (id, name) values (2, 'Lucille LaVerne');
    insert into Person (id, name) values (3, 'David Hand');
    insert into Person (id, name) values (4, 'Amy Hill');
    insert into Person (id, name) values (5, 'Sterling Holloway');
    insert into Person (id, name) values (6, 'Verna Felton');
    insert into Person (id, name) values (7, 'David Ogden Stiers');
    insert into Person (id, name) values (8, 'Wolfgang Reitherman');
    insert into Person (id, name) values (9, 'Daveigh Chase');
    insert into Person (id, name) values (10, 'Chris Sanders');
    insert into Person (id, name) values (11, 'James Bodrero');
    
    insert into Director (movieId, personId) values (1, 3);
    insert into Director (movieId, personId) values (2, 10);
    insert into Director (movieId, personId) values (6, 11);
    insert into Director (movieId, personId) values (7, 8);
    
    insert into Actor (movieId, personId, characterName)
      values(1, 1, 'Snow White');
    insert into Actor (movieId, personId, characterName)
      values(2, 9, 'Lilo');
    insert into Actor (movieId, personId, characterName)
      values(2, 10, 'Stitch');
    insert into Actor (movieId, personId, characterName)
      values(1, 2, 'Queen');
    insert into Actor (movieId, personId, characterName)
      values(3, 6, 'Queen of Hearts');
    insert into Actor (movieId, personId, characterName)
      values(7, 6, 'Elephant');
    insert into Actor (movieId, personId, characterName)
      values(6, 5, 'Narrator');
    insert into Actor (movieId, personId, characterName)
      values(3, 5, 'Chesire Cat');
    insert into Actor (movieId, personId, characterName)
      values(7, 5, 'Kaa');
    
  23. The new tables should show up in the schemata tab; if they don't, select refresh from the right-click popup menu and expand the movies database.
  24. Try some sample queries (Enter them one at a time into the query pane and hit Ctrl+Enter for each one):
        select * from Movie;
    
        select title, releaseDate
        from Movie
        where releaseDate >= '2000-01-01';
    
        select p.name, m.title, a.characterName
        from Person p, Movie m, Actor a
        where p.id = a.personId
        and m.id = a.movieId
        order by p.name;
    
  25. Now we want to connect the webapp and the database together. First we need a database driver, so download MySQL Connector/J and get the file mysql-connector-java-3.1.11-bin.jar (where the 3.1.11 might be different) out of the downloaded zip file and move it to your webapp's WEB-INF/lib directory.
  26. Now we still have to tell Tomcat how we are going to use the database. Specifically we need to tell tomcat about a data source. It will be our movies database; we'll have to tell Tomcat a resource name for it and a bunch of other related parameters. This information has to be added to the webapp's configuration file we started before (c:\tomcat5\conf\Catalina\localhost\movies.xml). Make that file look like this:
    <Context
      docBase="c:/mywebapps/movies"
      reloadable="true">
      <Resource
        name="jdbc/movies"
        auth="Container"
        type="javax.sql.DataSource"
        maxActive="100"
        maxIdle="30"
        maxWait="10000"
        username="root"
        password=""
        driverClassName="com.mysql.jdbc.Driver"
        url="jdbc:mysql://localhost:3306/movies"
        description="Simple movie database"
      />
    </Context>
    

    You might have to adjust the jdbc: string above depending on where you created your database files. And if you were smart and already decided using user "root" with no password was bad and you created another user, fill in your user and password.

  27. The simplest way, but NOT the best way, to make a webapp using the database, is to embed SQL statements inside a JSP page. Make a page called simpleExamples.jsp like this:

    simpleExamples.jsp
    <%--
      This page renders a simple table of movies read from a database.  It does things
      the wrong way, by embedding SQL into the page.  Ick.
    --%>
    
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
    
    <html>
      <head>
        <title>Movies</title>
        <style>
          table {border-collapse: collapse}
          table, td {border: thin solid grey}
          td {padding: 0.3em;}
        </style>
      </head>
    
      <body>
        <sql:setDataSource dataSource="jdbc/movies"/>
    
        <h1>All the Movies in the Database</h1>
    
        <sql:query var="movies">
          select * from Movie order by releaseDate
        </sql:query>
        <table>
          <c:forEach var="m" items="${movies.rows}">
            <tr>
              <td><c:out value="${m.title}"/></td>
              <td><c:out value="${m.releaseDate}"/></td>
            </tr>
          </c:forEach>
        </table>
    
        <h1>Actors in Movies since 1940</h1>
    
        <sql:query var="actingCredits">
          select p.name, m.title, a.characterName
          from Person p, Movie m, Actor a
          where p.id=a.personId and m.id=a.movieId
          and m.releaseDate > '1940-01-01'
          order by m.title
        </sql:query>
        <table>
          <c:forEach var="a" items="${actingCredits.rows}">
            <tr>
              <td><c:out value="${a.name}"/></td>
              <td><c:out value="${a.title}"/></td>
              <td><c:out value="${a.characterName}"/></td>
            </tr>
          </c:forEach>
        </table>
    
      </body>
    </html>
    
  28. Hit your new JSP page now. If you have problems, check your spelling and capitalization, make sure that the database exists, make sure your jar files are in the right places, try restarting Tomcat, or try anything. Or, ask someone for help.
  29. Notice in the code above that we wrote
      <td><c:out value="${a.name}"/></td>
    
    instead of
      <td>${a.name}</td>
    
    This is because the name might have nasty characters in it like "<" or "&" or even JavaScript, which would cause the final HTML to be horribly messed up. The c:out element takes care of "escaping" XML characters automatically. You should always write user-visible text with this element (unless the expression has type integer or something else "safe").
  30. To conclude the little introduction, shutdown the database server. Since we were running the server in a console window, you should open up a different console window and execute:
        cd "\Program Files\MySQL\MySQL Server 5.0"
        mysqladmin -u root shutdown
    

Doing Things Properly

We did some really lame things to get started. We should have:

  1. Not used the user name "root" (an administrator account) with no password! MySQL out of the box comes with one or more root accounts and one or more no-name user accounts. None of these accounts have passwords. The MySQL documentation shows how to add passwords, add and remove users. It's best to create one new user per webapp, granting to that user only those privileges necessary to run the webapp.
  2. Configured Tomcat and MySQL to run as operating system services, not as foreground processes in a console window.
  3. Never placed SQL code in the pages! Instead, we would have made servlets which would contain code to fetch the data and write it into a place where JSP pages would find it and render it. This organization is called Model-2 in the Java webapp world.

The Model-2 Architecture

Placing SQL code in a JSP page violates the principle of separation of concerns of the Model-View-Controller paradigm. The right way is to put the database access in a servlet which drops the returned data into the request object then forwards to a JSP page that just renders the data.

Let's make a servlet for fetching and rendering all the movies:

Here is a first pass:

AllMoviesServlet.java
package edu.lmu.cs.movies;

import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.sql.SQLException;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * A servlet that retrieves all the movies in the database and
 * forwards to a page that renders them.
 */
public class AllMoviesServlet extends HttpServlet {

    /**
     * Handles the HTTP GET request.
     */
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

        List movies;
        try {
            movies = MoviesService.getInstance().getAllMovies();
        } catch (SQLException e) {
            throw new ServletException (e);
        } catch (ClassNotFoundException e) {
            throw new ServletException (e);
        }

        // Add records to the request object
        request.setAttribute("movies", movies);

        // Continue to the JSP page - this is what makes this Model 2.
        request.getRequestDispatcher("fullList.jsp").forward(request, response);
    }

    /**
     * Handles the HTTP POST request.
     */
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        doGet(request, response);
    }
}

We made use of a business object bean (following modern practices, a POJO):

Movie.java
package edu.lmu.cs.movies;

import java.util.Date;

/**
 * Trivial movie bean.
 */
public class Movie {
    private int movieId;
    private String title;
    private Date releaseDate;

    public Movie(int movieId, String title, Date releaseDate) {
        this.movieId = movieId;
        this.title = title;
        this.releaseDate = releaseDate;
    }

    public int getMovieId() {return movieId;}
    public String getTitle() {return title;}
    public Date getReleaseDate() {return releaseDate;}
}

and a service object (sometimes called a DAO):

MoviesService.java
package edu.lmu.cs.movies;

import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.naming.*;

/**
 * A service for the movies webapp.  It's a singleton.
 */
public class MoviesService {
    private static final String DATASOURCE = "jdbc/movies";
    private static MoviesService instance;
    private DataSource dataSource;

    /**
     * Creates the service.
     */
    private MoviesService() throws ServletException {
        try {
            Context env = (Context)new InitialContext().lookup("java:comp/env");
            dataSource = (DataSource) env.lookup(DATASOURCE);
            if (dataSource == null) {
                throw new ServletException("Unknown data source: " + DATASOURCE);
        }
        } catch (NamingException e) {
            throw new ServletException(e);
        }
    }

    /**
     * Returns the sole instance of the service.
     */
    public static MoviesService getInstance() throws ServletException {
        if (instance == null) {
            instance = new MoviesService();
        }
        return instance;
    }

    /**
     * Returns a list of all movies in the database.
     */
    public List getAllMovies() throws SQLException, ClassNotFoundException {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet rs = null;
        List movies = new ArrayList();

        try {
            connection = dataSource.getConnection();
            statement = connection.prepareStatement(
                "select id, title, releaseDate from Movie order by releaseDate");
            rs = statement.executeQuery();
            while (rs.next()) {
                movies.add(new Movie(rs.getInt(1), rs.getString(2),
                        rs.getDate(3)));
            }
        } finally {
            if (rs != null) rs.close();
            if (statement != null) statement.close();
            if (connection != null) connection.close();
        }
        return movies;
    }
}

Place these three Java source code files in C:\mywebapps\movies\src\edu\lmu\cs\movies. The compiled versions have to go in WEB-INF\classes. So make your durrent directory the src directory and do the following:

C:\mywebapps\movies\src>javac -cp .;c:\tomcat5\common\lib\servlet-api.jar -d ..\WEB-INF\classes edu\lmu\cs\movies\*.java

The JSP page we want now is just:

fullList.jsp
<%--
  This page renders a simple table from a collection of movies placed into
  request scope.
--%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
  <head>
    <title>Movies</title>
    <style>
      table {border-collapse: collapse}
      table, td {border: thin solid grey}
      td {padding: 0.3em;}
    </style>
  </head>

  <body>
    <h1>All the Movies in the Database</h1>

    <table>
      <c:forEach var="m" items="${requestScope.movies}">
        <tr>
          <td><c:out value="${m.title}"/></td>
          <td><c:out value="${m.releaseDate}"/></td>
        </tr>
      </c:forEach>
    </table>

  </body>
</html>

Note how there is no SQL at all in the page. It is pure presentation!

To get this servlet to run, we have to tell the webapp about it. This goes in the web application deployment descriptor which is always called web.xml and goes in WEB-INF. The syntax for web.xml files is described in the servlet specification; for now just use this as WEB-INF/web.xml

web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
  version="2.4">

  <display-name>Movies</display-name>
  <description>
    A trivial web application used as an introduction to webapps
  </description>

  <servlet>
    <servlet-name>allmovies</servlet-name>
    <servlet-class>edu.lmu.cs.movies.AllMoviesServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>allmovies</servlet-name>
    <url-pattern>/allmovies</url-pattern>
  </servlet-mapping>
</web-app>

Now hit http://localhost:8080/movies/allmovies/ — cool, huh?

Making a Slightly Bigger Application

We have an "application" that does only one thing — delivers a boring table of movies and release dates. But we have this big (ahem) database so why not let users search it, and for that matter, post reviews. Where do we start?

Webapps are still applications so to design big ones we go through all the usual phases, including writing use cases, and doing the analysis and design thing. One thing we'll soon come up with is a list of actions an end-user can perform, expressed as URIs. We might get a list like this:

    movies/allmovies                  -- list all movies
    movies/find?director_id=x         -- list all movies directed by x
    movies/find?actor_id=x            -- list all movies x was in
    movies/find?year=x                -- list all movies released in x
    movies/details?id=x               -- provide a full detail page for x
    movies/director/find?name=x       -- get details on the director with name x
    movies/review/start?id=x          -- prepare a page for entering a review for x
    movies/review/store?id=x&review=y -- submit a review for movie x with text y
    .
    .
    .

So we add

Moving Forward

There have been hundreds of full length books written on this stuff. We've only just begun. Here are some things we should do: