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:
The webapp is normally bundled into a .war file, but some containers will run your webapp in exploded form.
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.
C:\Program Files\Java\jdk1.5.0_06
.
c:\tomcat5
.
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.)
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!"
.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
.
movies.xml
in
the directory c:\tomcat5\conf\Catalina\localhost
.
The file should look like this:
<Context docBase="c:/mywebapps/movies" reloadable="true" />
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?
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>
http://localhost:8080/movies/numbers.jsp
in the browser. See your page?
sum.jsp
:
<html> <head><title>The Sum</title></head> <body> <p>The sum is ${param.x + param.y}</p> </body> </html>
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.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
.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).
c:\Program Files\MySQL\MySQL Server 5.0
.c:\Program Files\MySQL\MySQL Query
Browser 1.1
.cd "\Program Files\MySQL\MySQL Server 5.0" mysqld --console
The server is now running on port 3306.
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.
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.
-- 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');
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;
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.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.
<%-- 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>
<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").
cd "\Program Files\MySQL\MySQL Server 5.0" mysqladmin -u root shutdown
We did some really lame things to get started. We should have:
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:
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):
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):
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:
The JSP page we want now is just:
<%-- 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
<?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?
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
There have been hundreds of full length books written on this stuff. We've only just begun. Here are some things we should do: